Compare commits

..

15 Commits

25 changed files with 1417 additions and 282 deletions
+1 -1
View File
@@ -11,7 +11,7 @@
{
"name": "ecc",
"source": "./",
"description": "Harness-native ECC operator layer - 67 agents, 270 skills, 92 legacy command shims, reusable hooks, rules, selective install profiles, and production-ready workflows for Claude Code, Codex, OpenCode, Cursor, and related agent harnesses",
"description": "Harness-native ECC operator layer - 67 agents, 271 skills, 92 legacy command shims, reusable hooks, rules, selective install profiles, and production-ready workflows for Claude Code, Codex, OpenCode, Cursor, and related agent harnesses",
"version": "2.0.0",
"author": {
"name": "Affaan Mustafa",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "ecc",
"version": "2.0.0",
"description": "Harness-native ECC plugin for engineering teams - 67 agents, 270 skills, 92 legacy command shims, reusable hooks, rules, MCP conventions, and operator workflows for Claude Code plus adjacent agent harnesses",
"description": "Harness-native ECC plugin for engineering teams - 67 agents, 271 skills, 92 legacy command shims, reusable hooks, rules, MCP conventions, and operator workflows for Claude Code plus adjacent agent harnesses",
"author": {
"name": "Affaan Mustafa",
"url": "https://x.com/affaanmustafa"
+2
View File
@@ -100,3 +100,5 @@ ecc2/target/
.opencode/package-lock.json
.opencode/node_modules/
assets/images/security/badrudi-exploit.mp4
.aider*
+2 -2
View File
@@ -1,6 +1,6 @@
# Everything Claude Code (ECC) — Agent Instructions
This is a **production-ready AI coding plugin** providing 67 specialized agents, 270 skills, 92 commands, and automated hook workflows for software development.
This is a **production-ready AI coding plugin** providing 67 specialized agents, 271 skills, 92 commands, and automated hook workflows for software development.
**Version:** 2.0.0
@@ -152,7 +152,7 @@ Troubleshoot failures: check test isolation → verify mocks → fix implementat
```
agents/ — 67 specialized subagents
skills/ — 270 workflow skills and domain knowledge
skills/ — 271 workflow skills and domain knowledge
commands/ — 92 slash commands
hooks/ — Trigger-based automations
rules/ — Always-follow guidelines (common + per-language)
+21 -4
View File
@@ -20,7 +20,7 @@
> [!WARNING]
> **Official sources only.** Install ECC only from verified channels: the GitHub repository [github.com/affaan-m/ECC](https://github.com/affaan-m/ECC), the npm packages [`ecc-universal`](https://www.npmjs.com/package/ecc-universal) and [`ecc-agentshield`](https://www.npmjs.com/package/ecc-agentshield), the [GitHub App](https://github.com/apps/ecc-tools), the plugin slug `ecc@ecc`, and the project website [ecc.tools](https://ecc.tools). Third-party re-uploads and unofficial mirrors are not maintained or reviewed by the project and may contain malware.
> **211.9K+ stars** | **32.5K+ forks** | **230+ contributors** | **12+ language ecosystems** | **Cross-harness agent workflows**
**211.9K+ stars** | **32.5K+ forks** | **230+ contributors** | **12+ language ecosystems** | **Cross-harness agent workflows**
---
@@ -95,6 +95,12 @@ ECC v2.0.0 adds the public Hermes operator story on top of that reusable layer:
<strong>Greptile</strong>
</a>
</td>
<td align="center" width="220">
<a href="https://www.atlascloud.ai/?utm_source=github&utm_medium=link&utm_campaign=ECC">
<img src="assets/images/sponsors/atlascloud.png" width="96" alt="Atlas Cloud logo" /><br />
<strong>Atlas Cloud</strong>
</a>
</td>
</tr>
</table>
@@ -428,7 +434,7 @@ If you stacked methods, clean up in this order:
/plugin list ecc@ecc
```
**That's it!** You now have access to 67 agents, 270 skills, and 92 legacy command shims.
**That's it!** You now have access to 67 agents, 271 skills, and 92 legacy command shims.
### Dashboard GUI
@@ -1517,7 +1523,7 @@ The configuration is automatically detected from `.opencode/opencode.json`.
|---------|---------------------|----------|--------|
| Agents | PASS: 67 agents | PASS: 12 agents | **Claude Code leads** |
| Commands | PASS: 92 commands | PASS: 35 commands | **Claude Code leads** |
| Skills | PASS: 270 skills | PASS: 37 skills | **Claude Code leads** |
| Skills | PASS: 271 skills | PASS: 37 skills | **Claude Code leads** |
| Hooks | PASS: 8 event types | PASS: 11 events | **OpenCode has more!** |
| Rules | PASS: 29 rules | PASS: 13 instructions | **Claude Code leads** |
| MCP Servers | PASS: 14 servers | PASS: Full | **Full parity** |
@@ -1678,7 +1684,7 @@ ECC is the **first plugin to maximize every major AI coding tool**. Here's how e
|---------|-----------------------|------------|-----------|----------|----------------|
| **Agents** | 67 | Shared (AGENTS.md) | Shared (AGENTS.md) | 12 | N/A |
| **Commands** | 92 | Shared | Instruction-based | 35 | 5 prompts |
| **Skills** | 270 | Shared | 10 (native format) | 37 | Via instructions |
| **Skills** | 271 | Shared | 10 (native format) | 37 | Via instructions |
| **Hook Events** | 8 types | 15 types | None yet | 11 types | None |
| **Hook Scripts** | 20+ scripts | 16 scripts (DRY adapter) | N/A | Plugin hooks | N/A |
| **Rules** | 34 (common + lang) | 34 (YAML frontmatter) | Instruction-based | 13 instructions | 1 always-on file |
@@ -1808,6 +1814,17 @@ These configs work for my workflow. You should:
---
## Security
ECC takes supply-chain and agent safety seriously.
- **Official sources only.** Install ECC only from the verified channels listed in the banner at the top of this README — the [GitHub repo](https://github.com/affaan-m/ECC), the `ecc-universal` / `ecc-agentshield` npm packages, the [GitHub App](https://github.com/apps/ecc-tools), the plugin slug `ecc@ecc`, and [ecc.tools](https://ecc.tools). Third-party re-uploads and mirrors are unreviewed and may ship malware.
- **Report a vulnerability.** Use the private process in [SECURITY.md](SECURITY.md) (GitHub private vulnerability reporting). Please do not open public issues for security reports.
- **Built-in guardrails.** GateGuard gates destructive shell commands (including `rm`, force/path `git checkout`, and destructive `find -exec`) before they run; the supply-chain IOC scanner runs in CI; and [AgentShield](#agentshield--security-auditor) audits your own agent, hook, MCP, permission, and secret surfaces (`/security-scan`).
- **Deep dive.** See the [Security Guide](./the-security-guide.md).
---
## Sponsors
Featured sponsors are at the top of this README — full list and tiers in [SPONSORS.md](SPONSORS.md). [Become a sponsor](https://github.com/sponsors/affaan-m).
+1 -1
View File
@@ -164,7 +164,7 @@ Copy-Item -Recurse rules/typescript "$HOME/.claude/rules/"
/plugin list ecc@ecc
```
**完成!** 你现在可以使用 67 个代理、270 个技能和 92 个命令。
**完成!** 你现在可以使用 67 个代理、271 个技能和 92 个命令。
### multi-* 命令需要额外配置
+7 -5
View File
@@ -1,6 +1,6 @@
# Sponsoring ECC
ECC is maintained as an open-source agent harness performance system across Claude Code, Cursor, OpenCode, and Codex app/CLI.
ECC is maintained as an open-source agent harness operating system across Claude Code, Cursor, OpenCode, Codex, Gemini, Zed, and other agent workflows.
## Why Sponsor
@@ -12,13 +12,15 @@ Sponsorship directly funds:
## Sponsorship Tiers
These are practical starting points and can be adjusted for partnership scope.
These are practical public starting points. Sponsorship funds the public OSS layer and sponsor visibility, not private implementation work.
| Tier | Price | Best For | Includes |
|------|-------|----------|----------|
| Pilot Partner | $200/mo | First sponsor engagement | Monthly metrics update, roadmap preview, prioritized maintainer feedback |
| Growth Partner | $500/mo | Teams actively adopting ECC | Pilot benefits + monthly office-hours sync + workflow integration guidance |
| Strategic Partner | $1,000+/mo | Platform/ecosystem partnerships | Growth benefits + coordinated launch support + deeper maintainer collaboration |
| Team Sponsor | $200/mo | Teams that want visible OSS support without README placement | Company name/logo/link in SPONSORS.md |
| Business Sponsor | $800/mo | Companies that want README sponsor visibility | Featured README sponsor area + SPONSORS.md listing + one sponsor-placement review |
| Strategic Sponsor | $3,700/mo | Ecosystem partners that want top placement and tighter coordination | Top README sponsor placement + SPONSORS.md listing + one 30-minute placement call + optional launch mention if the integration is genuinely useful |
No public tier includes seats, support SLA, custom development, a dedicated channel, or guaranteed case study unless separately agreed in writing.
## Sponsor Reporting
+5 -4
View File
@@ -6,14 +6,15 @@ Thank you to everyone funding ECC's open-source work. Your sponsorship is what l
*Become a [Strategic sponsor](https://github.com/sponsors/affaan-m) to be featured here.*
## Business Sponsors — $500/mo
## Business Sponsors
| Sponsor | Logo | Since |
|---------|------|-------|
| [**CodeRabbit**](https://www.coderabbit.ai) | <img src="assets/images/sponsors/coderabbit.png" width="60" alt="CodeRabbit logo" /> | 2026 |
| [**Greptile**](https://www.greptile.com/go/ecc) | <img src="assets/images/sponsors/greptile.png" width="60" alt="Greptile logo" /> | 2026 |
| [**Atlas Cloud**](https://www.atlascloud.ai/?utm_source=github&utm_medium=link&utm_campaign=ECC) | <img src="assets/images/sponsors/atlascloud.png" width="60" alt="Atlas Cloud logo" /> | 2026 |
*[Become a Business sponsor](https://github.com/sponsors/affaan-m) to get README sponsor placement + SPONSORS.md listing. No seats, SLA, custom development, or preferential technical placement is bundled unless separately agreed.*
*[Become a Business sponsor](https://github.com/sponsors/affaan-m) to get README sponsor placement + SPONSORS.md listing. Current Business tier is $500/mo. No seats, SLA, custom development, or preferential technical placement is bundled unless separately agreed.*
## Team Sponsors — $200/mo
@@ -51,7 +52,7 @@ Thank you to everyone funding ECC's open-source work. Your sponsorship is what l
| Pro Sponsor | $50 | Above + listed in SPONSORS.md |
| Team Sponsor | $200 | SPONSORS.md listing |
| Business Sponsor | $500 | README sponsor placement + SPONSORS.md listing |
| Strategic Sponsor | $2,500 | Premium sponsor placement + custom partnership discussion |
| Strategic Sponsor | $2,500 | Premium sponsor placement + sponsor placement call |
[**Become a Sponsor →**](https://github.com/sponsors/affaan-m)
@@ -74,4 +75,4 @@ If you sponsored before May 2026, you keep your original perks at your original
---
*Updated by Hermes. Last sync: 2026-06-09*
*Updated by Hermes. Last sync: 2026-06-16*
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

+2 -2
View File
@@ -1,6 +1,6 @@
# Everything Claude Code (ECC) — 智能体指令
这是一个**生产就绪的 AI 编码插件**,提供 67 个专业代理、270 项技能、92 条命令以及自动化钩子工作流,用于软件开发。
这是一个**生产就绪的 AI 编码插件**,提供 67 个专业代理、271 项技能、92 条命令以及自动化钩子工作流,用于软件开发。
**版本:** 2.0.0
@@ -147,7 +147,7 @@
```
agents/ — 67 个专业子代理
skills/ — 270 个工作流技能和领域知识
skills/ — 271 个工作流技能和领域知识
commands/ — 92 个斜杠命令
hooks/ — 基于触发的自动化
rules/ — 始终遵循的指导方针(通用 + 每种语言)
+3 -3
View File
@@ -228,7 +228,7 @@ Copy-Item -Recurse rules/typescript "$HOME/.claude/rules/"
/plugin list ecc@ecc
```
**搞定!** 你现在可以使用 67 个智能体、270 项技能和 92 个命令了。
**搞定!** 你现在可以使用 67 个智能体、271 项技能和 92 个命令了。
***
@@ -1142,7 +1142,7 @@ opencode
|---------|---------------|----------|--------|
| 智能体 | PASS: 67 个 | PASS: 12 个 | **Claude Code 领先** |
| 命令 | PASS: 92 个 | PASS: 35 个 | **Claude Code 领先** |
| 技能 | PASS: 270 项 | PASS: 37 项 | **Claude Code 领先** |
| 技能 | PASS: 271 项 | PASS: 37 项 | **Claude Code 领先** |
| 钩子 | PASS: 8 种事件类型 | PASS: 11 种事件 | **OpenCode 更多!** |
| 规则 | PASS: 29 条 | PASS: 13 条指令 | **Claude Code 领先** |
| MCP 服务器 | PASS: 14 个 | PASS: 完整 | **完全对等** |
@@ -1250,7 +1250,7 @@ ECC 是**第一个最大化利用每个主要 AI 编码工具的插件**。以
|---------|-----------------------|------------|-----------|----------|
| **智能体** | 67 | 共享 (AGENTS.md) | 共享 (AGENTS.md) | 12 |
| **命令** | 92 | 共享 | 基于指令 | 35 |
| **技能** | 270 | 共享 | 10 (原生格式) | 37 |
| **技能** | 271 | 共享 | 10 (原生格式) | 37 |
| **钩子事件** | 8 种类型 | 15 种类型 | 暂无 | 11 种类型 |
| **钩子脚本** | 20+ 个脚本 | 16 个脚本 (DRY 适配器) | N/A | 插件钩子 |
| **规则** | 34 (通用 + 语言) | 34 (YAML 前页) | 基于指令 | 13 条指令 |
+1 -1
View File
@@ -366,7 +366,7 @@
"@opencode-ai/plugin": "^1.16.2",
"@types/node": "25.9.2",
"c8": "^11.0.0",
"eslint": "^10.5.0",
"eslint": "^9.39.2",
"globals": "^17.4.0",
"markdownlint-cli": "^0.48.0",
"typescript": "^6.0.3"
+1 -1
View File
@@ -51,4 +51,4 @@ const open = defineModel<boolean>('open', { default: false })
## Reference
- ECC skills: `frontend-patterns`, `vite-patterns`.
- Docs: https://vuejs.org/api/sfc-script-setup.html · https://vuejs.org/guide/essentials/reactivity-fundamentals.html · https://eslint.vuejs.org/
- Docs: <https://vuejs.org/api/sfc-script-setup.html> · <https://vuejs.org/guide/essentials/reactivity-fundamentals.html> · <https://eslint.vuejs.org/>
+1 -1
View File
@@ -42,4 +42,4 @@ vue-tsc --noEmit
## Reference
- ECC skills: `frontend-patterns`, `vite-patterns`.
- Docs: https://github.com/vuejs/language-tools (vue-tsc) · https://eslint.vuejs.org/ · https://github.com/feature-sliced/steiger
- Docs: <https://github.com/vuejs/language-tools> (vue-tsc) · <https://eslint.vuejs.org/> · <https://github.com/feature-sliced/steiger>
+1 -1
View File
@@ -53,4 +53,4 @@ queryClient.invalidateQueries({ queryKey: ['auction', id] })
## Reference
- ECC skills: `frontend-patterns`, `vite-patterns`.
- Docs: https://pinia.vuejs.org/ · https://router.vuejs.org/ · https://tanstack.com/query/latest/docs/framework/vue/overview · https://vuejs.org/guide/reusability/composables.html
- Docs: <https://pinia.vuejs.org/> · <https://router.vuejs.org/> · <https://tanstack.com/query/latest/docs/framework/vue/overview> · <https://vuejs.org/guide/reusability/composables.html>
+1 -1
View File
@@ -43,4 +43,4 @@ paths:
## Reference
- ECC skills: `frontend-patterns`, `vite-patterns`.
- Docs: https://vuejs.org/guide/best-practices/security.html · https://github.com/cure53/DOMPurify · https://github.com/braintree/sanitize-url
- Docs: <https://vuejs.org/guide/best-practices/security.html> · <https://github.com/cure53/DOMPurify> · <https://github.com/braintree/sanitize-url>
+1 -1
View File
@@ -50,4 +50,4 @@ expect(wrapper.emitted('bid')).toBeTruthy()
## Reference
- ECC skills: `frontend-patterns`, `vite-patterns`.
- Docs: https://test-utils.vuejs.org/api/ · https://pinia.vuejs.org/cookbook/testing.html · https://vitest.dev/
- Docs: <https://test-utils.vuejs.org/api/> · <https://pinia.vuejs.org/cookbook/testing.html> · <https://vitest.dev/>
+95 -2
View File
@@ -461,6 +461,75 @@ function collectExecutableBodies(raw) {
return bodies;
}
/**
* Detect destructive commands inside `find ... -exec` invocations.
* Handles `-exec rm {} \;`, `-exec rm -rf {} \;`, `-exec rmdir {} \;`,
* `-exec unlink {} \;`, `-exec git reset --hard {} \;`.
*
* @param {string} command
* @returns {boolean}
*/
function isDestructiveFindExec(command) {
const raw = String(command || '');
const trimmed = raw.trim();
if (!trimmed) {
return false;
}
// Tokenize the whole command line
const tokens = tokenize(trimmed);
if (!tokens || tokens.length === 0) {
return false;
}
// Must start with `find`
if (commandBasename(tokens[0]) !== 'find') {
return false;
}
// Find the `-exec` token
const execIndex = tokens.indexOf('-exec');
if (execIndex === -1) {
return false;
}
// Collect tokens after `-exec` until we hit a terminator (`;`, `\;`, or `+`)
const execTokens = [];
for (let i = execIndex + 1; i < tokens.length; i++) {
const token = tokens[i];
if (token === ';' || token === '\\;' || token === '+') {
break;
}
execTokens.push(token);
}
if (execTokens.length === 0) {
return false;
}
const baseCmd = commandBasename(execTokens[0]);
// Directly destructive commands inside -exec
if (baseCmd === 'rmdir' || baseCmd === 'unlink') {
return true;
}
// `rm` with any flags (including none) inside -exec is destructive
if (baseCmd === 'rm') {
return true;
}
// `git reset --hard` inside -exec
if (baseCmd === 'git') {
const sub = findGitSubcommand(execTokens);
if (sub && sub.command === 'reset' && sub.rest.includes('--hard')) {
return true;
}
}
return false;
}
function isDestructiveBash(command) {
// The SQL/dd phrases live in command bodies, not as flag-bearing
// arguments, so we still match them by regex — but on the input
@@ -476,6 +545,9 @@ function isDestructiveBash(command) {
const extra = getExtraDestructiveRegex();
if (extra && extra.test(flattened)) return true;
// Check for destructive find -exec patterns
if (isDestructiveFindExec(raw)) return true;
const segments = collectExecutableBodies(raw).flatMap(splitCommandSegments);
for (const segment of segments) {
const stripped = stripQuotedStrings(segment);
@@ -781,7 +853,10 @@ function isReadOnlyGitIntrospection(command) {
}
if (subcommand === 'diff') {
return args.length <= 1 && args.every(arg => ['--name-only', '--name-status'].includes(arg));
const allowedDiffArgs = new Set(['--name-only', '--name-status', '--cached', '--staged', '--stat']);
// git diff without arguments is read-only introspection
if (args.length === 0) return true;
return args.length <= 2 && args.every(arg => allowedDiffArgs.has(arg));
}
if (subcommand === 'log') {
@@ -789,7 +864,25 @@ function isReadOnlyGitIntrospection(command) {
}
if (subcommand === 'show') {
return args.length === 1 && !args[0].startsWith('--') && /^[a-zA-Z0-9._:/ -]+$/.test(args[0]);
// Permite: git show <ref>, git show --stat, git show --name-only,
// git show <ref> --stat, git show <ref> --name-only
if (args.length === 0) return false;
if (args.length === 1) {
const arg = args[0];
if (arg === '--stat' || arg === '--name-only') return true;
// ref
return !arg.startsWith('--') && /^[a-zA-Z0-9._:/ -]+$/.test(arg);
}
if (args.length === 2) {
const [first, second] = args;
// ref + flag
if (!first.startsWith('--') && /^[a-zA-Z0-9._:/ -]+$/.test(first) &&
(second === '--stat' || second === '--name-only')) {
return true;
}
return false;
}
return false;
}
if (subcommand === 'branch') {
+4 -7
View File
@@ -100,9 +100,8 @@ function getPluginRoot() {
return path.resolve(__dirname, '..', '..');
}
//Safely extract target context from hook stdin JSON for dry-run preview.
function extractTargetContext(raw) {
const result = { tool: '', filePath: '', command: '' };
if (!raw || typeof raw !== 'string') return result;
@@ -118,18 +117,16 @@ function extractTargetContext(raw) {
}
}
} catch {
// best-effort field extraction; ignore malformed input
}
return result;
}
// Build the [DryRun] preview line for stderr.
function buildDryRunPreview(hookId, relScriptPath, profilesCsv, raw) {
const ctx = extractTargetContext(raw);
const parts = [
`[DryRun] Hook "${hookId}" would execute: ${relScriptPath}`,
`(enabled=true, profiles=${profilesCsv || 'default'})`,
];
const parts = [`[DryRun] Hook "${hookId}" would execute: ${relScriptPath}`, `(enabled=true, profiles=${profilesCsv || 'default'})`];
if (ctx.tool) {
parts.push(`tool=${ctx.tool}`);
+11 -25
View File
@@ -7,17 +7,12 @@ function escapeRegExp(str) {
}
function normalizeBodyForComparison(body) {
return (body || '').replace(/"lastSyncAt"\s*:\s*[^,\}\n]+/g, '"lastSyncAt": NORMALIZED');
return (body || '').replace(/"lastSyncAt"\s*:\s*[^,}\n]+/g, '"lastSyncAt": NORMALIZED');
}
function extractCoordinationState(body, policy = DEFAULT_POLICY) {
const marker = escapeRegExp(policy.sectionMarker || DEFAULT_SECTION_MARKER);
const regex = new RegExp(
`<!--\\s*${marker}:start\\s*-->\\s*` +
'```json\\s*([\\s\\S]*?)\\s*```' +
`\\s*<!--\\s*${marker}:end\\s*-->`,
'm'
);
const regex = new RegExp(`<!--\\s*${marker}:start\\s*-->\\s*` + '```json\\s*([\\s\\S]*?)\\s*```' + `\\s*<!--\\s*${marker}:end\\s*-->`, 'm');
const match = String(body || '').match(regex);
if (!match) {
@@ -28,9 +23,7 @@ function extractCoordinationState(body, policy = DEFAULT_POLICY) {
const parsed = JSON.parse(match[1]);
return parsed && typeof parsed === 'object' ? parsed : null;
} catch (error) {
throw new SyntaxError(
`Malformed coordination JSON in body: ${error.message} — raw: ${match[1].slice(0, 120)}`
);
throw new SyntaxError(`Malformed coordination JSON in body: ${error.message} — raw: ${match[1].slice(0, 120)}`);
}
}
@@ -40,7 +33,9 @@ function extractIssueReferences(text) {
for (const match of source.matchAll(/(?:^|[^\d])#(\d+)\b/g)) {
refs.add(Number.parseInt(match[1], 10));
}
return Array.from(refs).filter(Number.isFinite).sort((a, b) => a - b);
return Array.from(refs)
.filter(Number.isFinite)
.sort((a, b) => a - b);
}
function extractTasks(body) {
@@ -62,7 +57,7 @@ function extractTasks(body) {
if (taskMatch) {
tasks.push({
title: taskMatch[2].trim(),
done: taskMatch[1].toLowerCase() === 'x',
done: taskMatch[1].toLowerCase() === 'x'
});
}
}
@@ -98,26 +93,17 @@ function renderCoordinationState(state, policy = DEFAULT_POLICY) {
lastAction: state.lastAction || 'sync',
lastActionAt: state.lastActionAt || new Date().toISOString(),
lastSyncAt: state.lastSyncAt || new Date().toISOString(),
notes: state.notes || null,
notes: state.notes || null
};
return [
`<!-- ${marker}:start -->`,
'```json',
JSON.stringify(payload, null, 2),
'```',
`<!-- ${marker}:end -->`,
].join('\n');
return [`<!-- ${marker}:start -->`, '```json', JSON.stringify(payload, null, 2), '```', `<!-- ${marker}:end -->`].join('\n');
}
function mergeIssueBody(issue, nextState, policy = DEFAULT_POLICY) {
const body = String(issue.body || '');
const markerEscaped = escapeRegExp(policy.sectionMarker || DEFAULT_SECTION_MARKER);
const rendered = renderCoordinationState(nextState, policy);
const regex = new RegExp(
`\\n?<!--\\s*${markerEscaped}:start\\s*-->[\\s\\S]*?<!--\\s*${markerEscaped}:end\\s*-->\\n?`,
'm'
);
const regex = new RegExp(`\\n?<!--\\s*${markerEscaped}:start\\s*-->[\\s\\S]*?<!--\\s*${markerEscaped}:end\\s*-->\\n?`, 'm');
if (regex.test(body)) {
return body.replace(regex, `\n${rendered}\n`).trim() + '\n';
@@ -139,5 +125,5 @@ module.exports = {
mergeIssueBody,
normalizeBodyForComparison,
parseStringList,
renderCoordinationState,
renderCoordinationState
};
+1 -1
View File
@@ -1,6 +1,6 @@
---
name: flox-environments
description: "Create reproducible, cross-platform development environments with Flox a declarative environment manager built on Nix. ALWAYS use this skill when the user needs to: set up a project with system-level dependencies (compilers, databases, native libraries like openssl, libvips, BLAS, LAPACK); configure reproducible toolchains for Python, Node.js, Rust, Go, C/C++, Java, Ruby, Elixir, PHP, or any language; manage environments that must work identically across macOS and Linux; pin exact package versions for a team; run local services (PostgreSQL, Redis, Kafka) alongside development tools; onboard new developers with a single command; or solve 'works on my machine' problems. Especially valuable for AI-assisted and vibe coding — Flox lets agents install tools into a project-scoped environment without sudo, system pollution, or sandbox restrictions, and the resulting environment is committed to the repo so anyone can reproduce it instantly. Use this skill even if the user doesn't mention Flox — if they describe needing reproducible, declarative, cross-platform dev environments with system packages, this is the right tool. Also use when the user mentions .flox/, manifest.toml, flox activate, or FloxHub."
description: "Create reproducible, cross-platform (macOS/Linux) development environments with Flox, a declarative Nix-based environment manager. Use when setting up project toolchains for any language, installing system-level dependencies (compilers, databases, native libs like openssl/BLAS), pinning exact package versions for a team, running local services (PostgreSQL, Redis, Kafka), onboarding developers with one command, or solving 'works on my machine' problems — including agent/vibe-coding setups that need project-scoped tools without sudo. Also use when the user mentions .flox/, manifest.toml, flox activate, or FloxHub."
metadata:
origin: Flox
---
+820
View File
@@ -0,0 +1,820 @@
---
name: generating-python-installer
description: "Commercial-grade Python installer expert for Windows: Nuitka extreme compilation, dist slimming, DLL footprint analysis, and Inno Setup packaging to ship the smallest, fastest installers. Use only for advanced packaging/optimization (minimal size, fast startup), not basic script-to-exe conversion. 中文触发:Nuitka 极限优化、Python 商业打包、极限编译 Python、dist 瘦身、DLL 分析、最小安装包、最快启动、商业级打包风格"
---
# Generating Python Installer (Commercial-Grade)
You are a **Python commercial deployment expert**. Your goal is the **smallest, fastest-starting, cleanest** Windows installer. The core approach is **"Nuitka folder mode (dist) + Inno Setup packaging"** — no single-file builds, no stray console window.
## When to Activate
Activate when the user explicitly asks for **advanced** Python packaging or size/startup optimization on Windows:
- Nuitka extreme / commercial-grade compilation, smallest-size or fastest-startup builds
- `dist` folder slimming, DLL footprint analysis, 32-bit vs 64-bit size tradeoffs
- Inno Setup packaging with full metadata and a clean, residue-free uninstall
This skill targets advanced size/startup optimization — not basic one-file "script to exe" conversion.
## How It Works
1. **Confirm build parameters** — app name, version, publisher, exe name, source/output dirs, icon. Never auto-fill; ask the user.
2. **Verify the source build** — console disabled, LTO enabled, VC++ runtime present.
3. **Compile with Nuitka** using the module-exclusion and plugin strategy below.
4. **Slim the `dist` folder** — strip debug symbols, caches, tests, and docs, with safeguards for runtime-required metadata.
5. **Analyze DLLs** to find and trim the largest dependencies.
6. **Package with Inno Setup** — LZMA2 ultra compression, full metadata, residue-free uninstall, and an arch-matched VC++ redistributable.
## Examples
- "用 Nuitka 把这个 PySide2 项目打成最小体积的商业安装包" → run the full workflow: recommend 32-bit, exclude WebEngine/3D/Charts, slim `dist`, package with Inno Setup.
- "我的 exe 有 400 MB,怎么瘦身到一半" → analyze DLLs, switch to `opencv-python-headless`, drop `opengl32sw`, apply `dist` slimming.
- "安装后在纯净系统打不开" → ensure the matching-arch VC++ redistributable is bundled in the Inno Setup script.
---
## 核心理念
坚持 **"Nuitka 文件夹模式(dist) + Inno Setup 封装"** 方案。拒绝单文件版,拒绝黑窗。
---
## 实战参考案例(生产级 PySide2 桌面应用,323 MB,含 OpenCV / Playwright
### 项目概况
- **总体积**: 323 MB
- **打包工具**: PyInstaller 4.7 (32位)
- **主要依赖**: PySide2 (22.52 MB), OpenCV (62.38 MB), Playwright (76.74 MB)
- **Python 版本**: Python 3.8 (32位)
- **DLL 数量**: 71 个,总计 93.23 MB
### 关键优化策略
1. PASS: **使用 32 位 Python** → 体积减少 20-30%
2. PASS: **base_library.zip 压缩标准库** → 0.74 MB
3. PASS: **精简模块排除** → 无 pytest/unittest/setuptools
4. PASS: **精简 Qt 插件** → 只保留必要插件
### 体积分布
| 组件 | 体积 | 占比 | 优化建议 |
|------|------|------|---------|
| playwright | 76.74 MB | 23.8% | 非必要可移除 |
| OpenCV | 62.38 MB | 19.3% | 用 opencv-python-headless |
| PySide2 | 22.52 MB | 7.0% | 排除 WebEngine/3D/Charts |
| 其他依赖 | 161.36 MB | 49.9% | - |
### 预期效果对比
| 项目类型 | Nuitka 原始 | 优化后 | 参考项目实测 |
|---------|------------|--------|----------------|
| Tkinter + 标准库 | 150-250 MB | **80-120 MB** | - |
| PyQt/PySide | 200-400 MB | **120-250 MB** | 323 MB (含 OpenCV 等) |
| 含 numpy/pandas | 300-600 MB | **180-350 MB** | - |
---
## 核心工作流 (Workflow) - WARNING: 严格执行
当用户请求打包时,按照以下步骤操作:
**步骤 1:强制参数确认(FAIL: 禁止使用默认值)**
> **WARNING: 重要规则:以下所有参数必须逐一向用户确认,禁止自动填充或使用默认值!**
必须向用户询问并确认以下信息(*等待用户明确回复后才能继续*):
| 参数 | 说明 | 示例 |
|------|------|------|
| **软件名称** (App Name) | 软件显示名称 | `红墨批注` |
| **版本号** (Version) | 语义化版本号 | `1.0.0` |
| **发布者/公司名** (Publisher) | 控制面板显示的发布者 | `YourCompany` |
| **主程序** (Exe Name) | 主可执行文件名 | `RedInk.exe` |
| **源路径** (Source Dir) | Nuitka dist 文件夹绝对路径 | `D:\project\dist` |
| **输出路径** (Output Dir) | 安装包生成位置 | `D:\project\output` |
| **图标路径** (Icon Path) | .ico 文件绝对路径(可选但推荐) | `D:\project\icon.ico` |
| **官网地址** (URL) | 可选,用于控制面板链接 | `https://example.com` |
**询问模板:**
> "请提供以下打包参数,我需要您逐一确认:
> 1. 软件名称:
> 2. 版本号:
> 3. 发布者/公司名:
> 4. 主程序文件名(如 xxx.exe):
> 5. 源路径(Nuitka dist 文件夹):
> 6. 输出路径(安装包保存位置):
> 7. 图标路径(.ico 文件,可留空):
> 8. 官网地址(可留空):
>
> 请逐一填写,或回复"跳过"表示使用空值。"
**步骤 2:源文件质量与编译检查(关键)**
在生成代码之前,必须向用户发出以下**关键确认**(因为 Inno Setup 只是打包工具,无法改变程序本身的运行属性):
> "WARNING: **编译参数检查**
> 1. **去黑窗**:请确认您的 dist 文件夹是使用 `nuitka --windows-console-mode=disable` 编译的。(否则安装后依然会有黑框)
> 2. **高性能**:请确认是否使用了 `--lto=yes`。(否则启动速度可能不理想)
> 3. **运行库**:请确保 dist 文件夹内已包含必要的 VC++ 运行库,防止在纯净系统上无法运行。
>
> **确认源文件已准备好请回复"确认",否则请先重新编译。**"
**步骤 3:生成代码**
用户确认后,输出包含 **完整元数据****卸载图标修复** 的代码。
---
## Nuitka 极限优化编译(基于 参考项目经验)
### 一、32 位 vs 64 位选择策略
**参考项目使用 32 位 Python 的原因**
| 组件 | 64位体积 | 32位体积 | 节省 |
|------|---------|---------|------|
| python3x.dll | ~4.5 MB | ~3.8 MB | 15% |
| Qt5Core.dll | ~8 MB | ~5 MB | 37% |
| numpy | ~30 MB | ~20 MB | 33% |
| **总体** | 基准 | **-20~30%** | - |
**推荐使用 32 位条件**
- PASS: 程序内存占用 < 2GB
- PASS: 不处理超大文件(< 2GB
- PASS: 目标用户是普通办公电脑
**32 位编译方法**
```bash
# 1. 安装 32 位 Python(和 64 位可以共存)
# 下载地址:https://www.python.org/downloads/windows/
# 2. 用 32 位 Python 安装依赖
py -3.12-32 -m pip install -r requirements.txt
# 3. 用 32 位 Python 编译
py -3.12-32 -m nuitka --standalone ...你的参数
```
### 二、模块排除清单(参考项目验证过的)
**安全排除列表**(运行时不需要):
```
unittest,test,pytest,_pytest,doctest,pdb,pdbpp,
setuptools,pip,distutils,pkg_resources,
email.mime,http.server,xmlrpc,pydoc
```
**预期效果**:节省 **30-50 MB**
### 三、GUI 框架专用优化
#### Tkinter 极限优化(推荐,最轻量)
```bash
nuitka --standalone --windows-console-mode=disable ^
--lto=yes ^
--jobs=8 ^
--enable-plugin=tk-inter ^
--enable-plugin=anti-bloat ^
--noinclude-pytest-mode=nofollow ^
--noinclude-setuptools-mode=nofollow ^
--nofollow-import-to=unittest,test,pytest,_pytest,doctest,pdb,pdbpp ^
--nofollow-import-to=setuptools,pip,distutils,pkg_resources ^
--nofollow-import-to=email.mime,http.server,xmlrpc,pydoc ^
--python-flag=no_docstrings ^
--output-dir=dist ^
--windows-icon-from-ico=icon.ico ^
--remove-output ^
main.py
```
**预期体积**80-120 MB(优化后)
#### PyQt5 / PySide2 优化
```bash
nuitka --standalone --windows-console-mode=disable ^
--lto=yes ^
--jobs=8 ^
--enable-plugin=pyqt5 ^
--enable-plugin=anti-bloat ^
--noinclude-pytest-mode=nofollow ^
--noinclude-setuptools-mode=nofollow ^
--nofollow-import-to=unittest,test,pytest,_pytest,doctest,pdb ^
--nofollow-import-to=setuptools,pip,distutils,pkg_resources ^
--nofollow-import-to=PyQt5.QtWebEngine,PyQt5.QtWebEngineWidgets ^
--nofollow-import-to=PyQt5.Qt3D,PyQt5.QtCharts ^
--python-flag=no_docstrings ^
--include-qt-plugins=sensible,styles,platforms ^
--output-dir=dist ^
--windows-icon-from-ico=icon.ico ^
--remove-output ^
main.py
```
**预期体积**120-250 MB(优化后)
### 四、一键编译脚本模板
**保存为 `build_optimized.bat`(项目根目录)**
```batch
@echo off
chcp 65001 >nul
setlocal enabledelayedexpansion
echo ========================================
echo Nuitka 极限优化编译(参考 参考项目)
echo ========================================
REM === 配置区域(请修改为你的实际值) ===
set APP_NAME=你的软件名
set MAIN_FILE=main.py
set ICON_FILE=icon.ico
REM === 自动检测 CPU 核心数 ===
REM 用 Windows 自带环境变量(wmic 在 Win11 22H2+ 已移除,探不到会让 --jobs=0 单线程编译)
set CPU_CORES=%NUMBER_OF_PROCESSORS%
if not defined CPU_CORES set CPU_CORES=4
set /a BUILD_JOBS=%CPU_CORES%
REM === 参考项目的模块排除清单 ===
set EXCLUDE_MODULES=unittest,test,pytest,_pytest,doctest,pdb,pdbpp
set EXCLUDE_MODULES=%EXCLUDE_MODULES%,setuptools,pip,distutils,pkg_resources
set EXCLUDE_MODULES=%EXCLUDE_MODULES%,email.mime,http.server,xmlrpc,pydoc
echo.
echo [1/4] 清理旧编译...
if exist dist rd /s /q dist
if exist build rd /s /q build
echo.
echo [2/4] Nuitka 编译中(应用 参考项目优化策略)...
echo - CPU 核心: %CPU_CORES% (使用 %BUILD_JOBS% 线程)
echo - 模块排除: %EXCLUDE_MODULES%
echo.
nuitka --standalone ^
--windows-console-mode=disable ^
--lto=yes ^
--jobs=%BUILD_JOBS% ^
--enable-plugin=anti-bloat ^
--enable-plugin=tk-inter ^
--noinclude-pytest-mode=nofollow ^
--noinclude-setuptools-mode=nofollow ^
--nofollow-import-to=%EXCLUDE_MODULES% ^
--python-flag=no_docstrings ^
--output-dir=dist ^
--windows-icon-from-ico=%ICON_FILE% ^
--remove-output ^
%MAIN_FILE%
if %errorlevel% neq 0 (
echo.
echo [错误] 编译失败!
pause
exit /b 1
)
echo.
echo [3/4] 统计编译结果...
for /f %%a in ('powershell -NoProfile -Command "(Get-ChildItem -LiteralPath 'dist\%APP_NAME%.dist' -Recurse -File | Measure-Object -Property Length -Sum).Sum"') do set TOTAL_SIZE=%%a
set TOTAL_SIZE=%TOTAL_SIZE:,=%
set /a SIZE_MB=%TOTAL_SIZE% / 1048576
echo - 编译后体积: %SIZE_MB% MB
echo.
echo [4/4] 执行瘦身清理(参考 参考项目策略)...
powershell -ExecutionPolicy Bypass -File slim_dist.ps1 -DistPath "dist\%APP_NAME%.dist"
echo.
echo ========================================
echo 编译完成!
echo ========================================
pause
```
### 五、dist 瘦身脚本(参考项目级别清理)
**保存为 `slim_dist.ps1`(和 build_optimized.bat 同目录)**
```powershell
param(
[string]$DistPath
)
$ErrorActionPreference = "Continue" # 不静默吞错:删除失败会显示出来,避免假成功
Write-Host "`n========================================" -ForegroundColor Cyan
Write-Host "dist 瘦身清理(参考 参考项目策略)" -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
if (-not (Test-Path $DistPath)) {
Write-Host "[错误] 找不到目录: $DistPath" -ForegroundColor Red
exit 1
}
# 统计初始体积
$InitialSize = (Get-ChildItem -Path $DistPath -Recurse -File | Measure-Object -Property Length -Sum).Sum / 1MB
Write-Host "`n初始体积: $([math]::Round($InitialSize, 2)) MB" -ForegroundColor Yellow
# 参考项目特征:没有 .pdb, .pyi, __pycache__, test 等
Write-Host "`n[应用 参考项目的清理策略...]" -ForegroundColor Green
# 1. 删除调试符号
Write-Host "`n[1/7] 删除 .pdb 调试符号..." -ForegroundColor Green
$pdbFiles = Get-ChildItem -Path $DistPath -Recurse -Include *.pdb -File
$pdbSize = ($pdbFiles | Measure-Object -Property Length -Sum).Sum / 1MB
if ($pdbFiles.Count -gt 0) {
$pdbFiles | Remove-Item -Force
Write-Host " 删除 $($pdbFiles.Count) 个文件,节省 $([math]::Round($pdbSize, 2)) MB"
} else {
Write-Host " 未发现 .pdb 文件(已优化)" -ForegroundColor Gray
}
# 2. 删除类型提示
Write-Host "`n[2/7] 删除 .pyi 类型提示..." -ForegroundColor Green
$pyiFiles = Get-ChildItem -Path $DistPath -Recurse -Include *.pyi -File
$pyiSize = ($pyiFiles | Measure-Object -Property Length -Sum).Sum / 1MB
if ($pyiFiles.Count -gt 0) {
$pyiFiles | Remove-Item -Force
Write-Host " 删除 $($pyiFiles.Count) 个文件,节省 $([math]::Round($pyiSize, 2)) MB"
} else {
Write-Host " 未发现 .pyi 文件(已优化)" -ForegroundColor Gray
}
# 3. 删除 __pycache__
Write-Host "`n[3/7] 删除 __pycache__ 缓存..." -ForegroundColor Green
$pycacheDirs = Get-ChildItem -Path $DistPath -Recurse -Directory -Filter "__pycache__"
$pycacheSize = 0
foreach ($dir in $pycacheDirs) {
$size = (Get-ChildItem -Path $dir.FullName -Recurse -File | Measure-Object -Property Length -Sum).Sum
$pycacheSize += $size
Remove-Item -Path $dir.FullName -Recurse -Force
}
if ($pycacheDirs.Count -gt 0) {
Write-Host " 删除 $($pycacheDirs.Count) 个目录,节省 $([math]::Round($pycacheSize / 1MB, 2)) MB"
} else {
Write-Host " 未发现 __pycache__(已优化)" -ForegroundColor Gray
}
# 4. 删除测试目录
Write-Host "`n[4/7] 删除 test/tests 测试目录..." -ForegroundColor Green
$testDirs = Get-ChildItem -Path $DistPath -Recurse -Directory | Where-Object { $_.Name -match '^tests?$' }
$testSize = 0
foreach ($dir in $testDirs) {
$size = (Get-ChildItem -Path $dir.FullName -Recurse -File | Measure-Object -Property Length -Sum).Sum
$testSize += $size
Remove-Item -Path $dir.FullName -Recurse -Force
}
if ($testDirs.Count -gt 0) {
Write-Host " 删除 $($testDirs.Count) 个目录,节省 $([math]::Round($testSize / 1MB, 2)) MB"
} else {
Write-Host " 未发现测试目录(已优化)" -ForegroundColor Gray
}
# 5. 删除文档和示例
Write-Host "`n[5/7] 删除 docs/examples 文档目录..." -ForegroundColor Green
$docDirs = Get-ChildItem -Path $DistPath -Recurse -Directory | Where-Object { $_.Name -match '^(docs|examples|samples|demo)$' }
$docSize = 0
foreach ($dir in $docDirs) {
$size = (Get-ChildItem -Path $dir.FullName -Recurse -File | Measure-Object -Property Length -Sum).Sum
$docSize += $size
Remove-Item -Path $dir.FullName -Recurse -Force
}
if ($docDirs.Count -gt 0) {
Write-Host " 删除 $($docDirs.Count) 个目录,节省 $([math]::Round($docSize / 1MB, 2)) MB"
} else {
Write-Host " 未发现文档目录(已优化)" -ForegroundColor Gray
}
# 6. 删除 .pyc 文件
Write-Host "`n[6/7] 删除 .pyc 字节码..." -ForegroundColor Green
$pycFiles = Get-ChildItem -Path $DistPath -Recurse -Include *.pyc -File
$pycSize = ($pycFiles | Measure-Object -Property Length -Sum).Sum / 1MB
if ($pycFiles.Count -gt 0) {
$pycFiles | Remove-Item -Force
Write-Host " 删除 $($pycFiles.Count) 个文件,节省 $([math]::Round($pycSize, 2)) MB"
} else {
Write-Host " 未发现 .pyc 文件(已优化)" -ForegroundColor Gray
}
# 7. 精简 .dist-info 元数据
Write-Host "`n[7/7] 精简 .dist-info 元数据..." -ForegroundColor Green
$distInfoDirs = Get-ChildItem -Path $DistPath -Recurse -Directory -Filter "*.dist-info"
$removedCount = 0
$removedSize = 0
foreach ($infoDir in $distInfoDirs) {
# 仅删安装期记账文件;保留 METADATA 与 entry_points.txt(运行期被 importlib.metadata 读取,删除会破坏插件发现)
$filesToRemove = @("RECORD", "INSTALLER", "direct_url.json")
foreach ($fileName in $filesToRemove) {
$file = Join-Path $infoDir.FullName $fileName
if (Test-Path $file) {
$size = (Get-Item $file).Length
$removedSize += $size
Remove-Item $file -Force
$removedCount++
}
}
}
if ($removedCount -gt 0) {
Write-Host " 删除 $removedCount 个元数据文件,节省 $([math]::Round($removedSize / 1MB, 2)) MB"
} else {
Write-Host " 未发现可清理的元数据(已优化)" -ForegroundColor Gray
}
# 统计最终体积
$FinalSize = (Get-ChildItem -Path $DistPath -Recurse -File | Measure-Object -Property Length -Sum).Sum / 1MB
$SavedSize = $InitialSize - $FinalSize
$SavedPercent = if ($InitialSize -gt 0) { ($SavedSize / $InitialSize) * 100 } else { 0 }
Write-Host "`n========================================" -ForegroundColor Cyan
Write-Host "清理完成!" -ForegroundColor Green
Write-Host "========================================" -ForegroundColor Cyan
Write-Host "初始体积: $([math]::Round($InitialSize, 2)) MB" -ForegroundColor Yellow
Write-Host "最终体积: $([math]::Round($FinalSize, 2)) MB" -ForegroundColor Green
Write-Host "节省空间: $([math]::Round($SavedSize, 2)) MB ($([math]::Round($SavedPercent, 1))%)" -ForegroundColor Cyan
Write-Host "========================================`n" -ForegroundColor Cyan
# 对比 参考项目
Write-Host "[对比参考]" -ForegroundColor Yellow
Write-Host "参考项目总体积: 323 MB (包含 PyQt, OpenCV, Playwright 等重量级库)" -ForegroundColor Gray
Write-Host "如果你的项目是纯 Tkinter + 标准库,目标应该在 80-150 MB" -ForegroundColor Gray
```
**预期效果**:节省 **15-30%** 体积
### 六、DLL 依赖分析工具
**保存为 `analyze_dlls.py`(用于找出体积大户)**
```python
"""
DLL 依赖分析工具
参考 参考项目的 DLL 管理策略,帮助识别体积大户和优化建议
"""
import sys
from pathlib import Path
def analyze_dlls(dist_path: str):
"""分析 dist 目录中的 DLL 依赖"""
dist_dir = Path(dist_path)
if not dist_dir.exists():
print(f"[错误] 目录不存在: {dist_path}")
return
print("=" * 70)
print("DLL 依赖分析(参考 参考项目策略)")
print("=" * 70)
# 收集所有 DLL
dll_files = list(dist_dir.rglob("*.dll"))
if not dll_files:
print("\n未发现 DLL 文件")
return
# 按大小排序
dll_data = [(dll, dll.stat().st_size) for dll in dll_files]
dll_data.sort(key=lambda x: x[1], reverse=True)
total_size = sum(size for _, size in dll_data)
print(f"\n总 DLL 数量: {len(dll_files)}")
print(f"总 DLL 体积: {total_size / 1024 / 1024:.2f} MB\n")
# 参考项目对比
print("[对比参考] 参考项目的 DLL 情况:")
print(" - 总数量: 71 个")
print(" - 总体积: 93.23 MB")
print(" - 最大的: libopenblas (26.85 MB), opengl32sw (15.25 MB)\n")
# 分析大于 3MB 的 DLL
large_dlls = [(dll, size) for dll, size in dll_data if size > 3 * 1024 * 1024]
if large_dlls:
print("=" * 70)
print("WARNING: 大于 3MB 的 DLL(需重点关注)")
print("=" * 70)
for dll, size in large_dlls:
size_mb = size / 1024 / 1024
relative_path = dll.relative_to(dist_dir)
name_lower = dll.name.lower()
print(f"\n{size_mb:8.2f} MB {dll.name}")
print(f" 位置: {relative_path.parent}")
# 优化建议
suggestions = get_optimization_suggestion(name_lower)
if suggestions:
for suggestion in suggestions:
print(f" {suggestion}")
# 检查冗余 DLL
print("\n" + "=" * 70)
print(" 冗余检查")
print("=" * 70)
# 检查调试版本
debug_dlls = [dll for dll, _ in dll_data if dll.stem.endswith('d')]
if debug_dlls:
print(f"\nWARNING: 发现 {len(debug_dlls)} 个调试版本 DLL(可以删除):")
for dll in debug_dlls:
print(f" - {dll.name}")
else:
print("\nPASS: 未发现调试版本 DLL(已优化)")
# VC++ Runtime
vc_runtimes = [dll for dll, _ in dll_data if 'vcruntime' in dll.name.lower() or 'msvcp' in dll.name.lower()]
if vc_runtimes:
print(f"\n[VC++ Runtime 库] 发现 {len(vc_runtimes)} 个:")
for dll in vc_runtimes:
size_mb = dll.stat().st_size / 1024 / 1024
print(f" - {dll.name} ({size_mb:.2f} MB)")
print(" 这些是必需的,参考项目也包含了这些文件")
# 全部 DLL 列表
print("\n" + "=" * 70)
print(" 完整 DLL 列表(按体积排序,前 20)")
print("=" * 70)
print(f"\n{'体积 (MB)':>10} {'文件名':<30} 位置")
print("-" * 70)
for dll, size in dll_data[:20]:
size_mb = size / 1024 / 1024
relative_path = dll.relative_to(dist_dir)
location = str(relative_path.parent) if relative_path.parent != Path('.') else "根目录"
print(f"{size_mb:10.2f} {dll.name:<30} {location}")
if len(dll_data) > 20:
remaining_size = sum(size for _, size in dll_data[20:]) / 1024 / 1024
print(f"... 还有 {len(dll_data) - 20} 个 DLL,共 {remaining_size:.2f} MB")
def get_optimization_suggestion(dll_name: str) -> list:
"""根据 DLL 名称给出优化建议"""
suggestions = []
if "openblas" in dll_name or "mkl" in dll_name:
suggestions.append("数学运算库,参考项目的 libopenblas 有 26.85 MB")
suggestions.append("如不需要高性能计算可考虑轻量版")
elif "opencv" in dll_name or "ffmpeg" in dll_name:
suggestions.append("OpenCV 相关,参考项目的 opencv_videoio_ffmpeg 有 18.48 MB")
suggestions.append("考虑用 opencv-python-headless")
elif "qt5" in dll_name or "qt6" in dll_name or "pyside" in dll_name:
suggestions.append("Qt 库,参考项目的 Qt5Core 有 5.13 MB")
suggestions.append("可排除不需要的模块(WebEngine, 3D, Charts")
elif "opengl" in dll_name and "sw" in dll_name:
suggestions.append("OpenGL 软件渲染器,参考项目保留了 15.25 MB")
suggestions.append("通常可以删除(使用硬件渲染)")
elif "d3dcompiler" in dll_name:
suggestions.append("DirectX 编译器,参考项目有 3.53 MB")
elif "mfc140" in dll_name:
suggestions.append("MFC 库,参考项目有 4.89 MB")
return suggestions
if __name__ == "__main__":
if len(sys.argv) < 2:
print("用法: python analyze_dlls.py <dist目录路径>")
print("示例: python analyze_dlls.py dist/main.dist")
sys.exit(1)
analyze_dlls(sys.argv[1])
```
**使用方法**
```bash
python analyze_dlls.py dist/你的软件名.dist
```
---
## 完整优化工作流
### 步骤 1:修改编译脚本配置
编辑 `build_optimized.bat`,修改这 3 行:
```batch
set APP_NAME=你的软件名 REM 改成实际名称
set MAIN_FILE=main.py REM 你的主程序文件
set ICON_FILE=icon.ico REM 你的图标文件
```
### 步骤 2:一键编译+瘦身
```bash
# 在项目根目录执行
build_optimized.bat
```
### 步骤 3:分析 DLL 依赖
```bash
python analyze_dlls.py dist/你的软件名.dist
```
### 步骤 4:根据分析结果优化
**如果用了 OpenCV** → 改用无头版
```bash
pip uninstall opencv-python
pip install opencv-python-headless
```
**如果用了 Qt** → 排除不需要的模块
```batch
# 在编译命令中添加
--nofollow-import-to=PyQt5.QtWebEngine,PyQt5.Qt3D,PyQt5.QtCharts
```
**删除软件渲染器**(如果不需要)
```powershell
# 在 dist 目录执行
Remove-Item "opengl32sw.dll" -Force
```
---
## VC++ 运行库处理方案
### 方案一:静态链接(推荐)
```bash
nuitka --static-libpython=yes ...
```
### 方案二:捆绑运行库安装(商业发布推荐)
在 Inno Setup 脚本中添加:
```iss
; WARNING: VC++ 运行库架构必须与 Python/Nuitka 构建架构一致。
; 本 skill 推荐 32 位 Python,故默认捆绑 vc_redist.x86.exe
; 若用 64 位 Python 编译,请把下面两处改为 vc_redist.x64.exe。
[Files]
Source: "{#MySourceDir}\..\vc_redist.x86.exe"; DestDir: "{tmp}"; Flags: deleteafterinstall
[Run]
Filename: "{tmp}\vc_redist.x86.exe"; Parameters: "/quiet /norestart"; StatusMsg: "正在安装运行库..."; Flags: waituntilterminated
```
> 下载地址:[Microsoft Visual C++ Redistributable](https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist)
---
## Inno Setup 脚本模板(商业终极版)
```iss
; =====================================================================
; WARNING: 商业级 Python 安装脚本 (Inno Setup 6.x)
; 特性:LZMA2 极限压缩 | 全中文 | 完整元数据 | 无残留卸载
; 参考:参考项目(323 MB, LZMA2 压缩)
; =====================================================================
; --- 1. 参数定义 ---
#define MyAppName "{{APP_NAME}}"
#define MyAppVersion "{{APP_VERSION}}"
#define MyAppPublisher "{{PUBLISHER}}"
#define MyAppURL "{{APP_URL}}"
#define MyAppExeName "{{EXE_NAME}}"
#define MySourceDir "{{SOURCE_DIR}}"
#define MyOutputDir "{{OUTPUT_DIR}}"
;#define MyIconPath "{{ICON_PATH}}"
[Setup]
; --- 身份识别 ---
AppId={{GENERATE_RANDOM_GUID}}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
; --- 安装路径与权限 ---
DefaultDirName={autopf}\{#MyAppName}
DefaultGroupName={#MyAppName}
DisableDirPage=no
DisableProgramGroupPage=no
PrivilegesRequired=admin
; --- 输出设置 ---
OutputDir={#MyOutputDir}
OutputBaseFilename=Setup_{#MyAppName}_v{#MyAppVersion}
; --- 视觉体验 ---
WizardStyle=modern
#ifdef MyIconPath
SetupIconFile={#MyIconPath}
UninstallDisplayIcon={app}\{#MyAppExeName}
#endif
; --- 核心压缩 (参考 参考项目) ---
Compression=lzma2/ultra64
SolidCompression=yes
LZMAUseSeparateProcess=yes
; --- 架构 ---
; 注意:仅 64 位 Python 构建才设此项。本 skill 推荐 32 位 Python——
; 32 位构建请保持注释,使应用按 32 位安装并与上面捆绑的 vc_redist.x86.exe 匹配。
; 仅当用 64 位 Python 编译时才取消注释。
;ArchitecturesInstallIn64BitMode=x64compatible
[Languages]
Name: "chinesesimplified"; MessagesFile: "compiler:Languages\ChineseSimplified.isl"
[Files]
Source: "{#MySourceDir}\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
[UninstallDelete]
Type: filesandordirs; Name: "{app}\*"
[Icons]
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
Name: "{group}\卸载 {#MyAppName}"; Filename: "{uninstallexe}"
[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
[Run]
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#MyAppName}}"; Flags: nowait postinstall skipifsilent
```
---
## 占位符说明
| 占位符 | 说明 | 示例值 |
|--------|------|--------|
| `{{APP_NAME}}` | 软件显示名称 | `红墨批注` |
| `{{APP_VERSION}}` | 版本号 | `1.0.0` |
| `{{PUBLISHER}}` | 发布者/公司名 | `MyCompany` |
| `{{APP_URL}}` | 官网地址 | `https://example.com` |
| `{{EXE_NAME}}` | 主程序文件名 | `RedInk.exe` |
| `{{SOURCE_DIR}}` | Nuitka dist 文件夹路径 | `D:\project\dist\RedInk.dist` |
| `{{OUTPUT_DIR}}` | 安装包输出路径 | `D:\project\output` |
| `{{ICON_PATH}}` | 图标文件路径 | `D:\project\icon.ico` |
| `{{GENERATE_RANDOM_GUID}}` | **需生成唯一GUID** | 使用 Inno Setup "Tools > Generate GUID" |
---
## 常见问题 (FAQ)
### Q1: 安装后双击程序无反应?
1. 打开 CMD,手动运行 exe 查看错误信息
2. 检查是否缺少 VC++ 运行库
3. 检查 Nuitka 编译是否成功
### Q2: 安装包体积过大?
**优化方法**
1. 使用 32 位 Python 编译(节省 20-30%
2. 应用 参考项目的模块排除清单
3. 启用 Anti-Bloat 插件
4. 执行 dist 文件夹瘦身脚本
5. 分析 DLL,移除不必要的大文件
### Q3: 杀毒软件误报?
**解决方案**
- 提交到主流杀毒厂商进行白名单申请
- 购买代码签名证书(推荐:Sectigo, DigiCert
- 避免使用 UPX 压缩
### Q4: 安装时提示 Windows 已保护你的电脑?
- 购买 EV 代码签名证书(可立即获得信任)
- 普通代码签名证书需要积累安装量后逐渐获得信任
---
## 实战问题处理记录(更新 2026-02-07
- **安装后提示缺少 python3xx.dll**:必须使用 Nuitka `--standalone`;确认 dist 内存在该 dll;不要打单文件版。
- **安装后点击无反应**:GUI 启动期可能被重依赖阻塞;将重依赖延迟到"开始导出"再 import;添加日志排查。
- **Nuitka + MinGW 在非 ASCII 路径报错**:把源码复制到 ASCII 目录再编译;设置 `PYTHONIOENCODING=utf-8`
- **Inno Setup 警告 `x64` 已弃用(仅 64 位构建需要 64 位安装模式时)**:改为 `ArchitecturesInstallIn64BitMode=x64compatible`32 位构建无需此项。
- **`--disable-console` 已废弃**:改用 `--windows-console-mode=disable`
- **dist 出现 `_nuitka_temp.exe`**:在 [Files] 中排除它。
---
## 优化效果预期
| 优化组合 | 体积减少 | 启动提升 | 风险等级 |
|----------|----------|----------|----------|
| 基础编译 | 基准 | 基准 | 无 |
| + `--lto=yes` | 5-10% | 10-20% | PASS: 无 |
| + anti-bloat | 15-25% | - | PASS: 无 |
| + 模块排除 | 20-35% | 5% | PASS: 无 |
| + dist 瘦身 | 25-40% | - | PASS: 无 |
| + 32 位编译 | 40-60% | - | PASS: 无 |
| **全部组合** | **45-65%** | **15-25%** | PASS: **无风险** |
> WARNING: **不建议使用 UPX 压缩**,虽然能进一步减小体积,但极易触发杀毒软件误报。
---
**基于 参考项目实战经验优化,助你打造商业级安装包!**
+107
View File
@@ -1586,6 +1586,49 @@ function runTests() {
'custom phrase inside command substitution should be gated');
})) passed++; else failed++;
// --- find -exec destructive detection ---
if (test('denies find -exec rm {} \\; as destructive', () => {
expectDestructiveDeny('find . -name "*.tmp" -exec rm {} \\;',
'find -exec rm');
})) passed++; else failed++;
if (test('denies find -exec rm -rf {} \\; as destructive', () => {
expectDestructiveDeny('find . -name "*.tmp" -exec rm -rf {} \\;',
'find -exec rm -rf');
})) passed++; else failed++;
if (test('denies find -exec rmdir {} \\; as destructive', () => {
expectDestructiveDeny('find . -name "*.tmp" -exec rmdir {} \\;',
'find -exec rmdir');
})) passed++; else failed++;
if (test('denies find -exec unlink {} \\; as destructive', () => {
expectDestructiveDeny('find . -name "*.tmp" -exec unlink {} \\;',
'find -exec unlink');
})) passed++; else failed++;
if (test('denies find -exec git reset --hard {} \\; as destructive', () => {
expectDestructiveDeny('find . -name "*.tmp" -exec git reset --hard {} \\;',
'find -exec git reset --hard');
})) passed++; else failed++;
if (test('allows find -exec echo {} \\; (non-destructive, routine gate)', () => {
clearState();
const input = { tool_name: 'Bash', tool_input: { command: 'find . -name "*.tmp" -exec echo {} \\;' } };
const result = runBashHook(input);
assert.strictEqual(result.code, 0, 'exit code should be 0');
const output = parseOutput(result.stdout);
assert.ok(output, 'should produce JSON output');
// Should be denied by routine gate (first bash), not destructive gate
assert.strictEqual(output.hookSpecificOutput.permissionDecision, 'deny',
'should be denied by routine gate');
assert.ok(!output.hookSpecificOutput.permissionDecisionReason.includes('Destructive'),
'should not be the destructive deny message');
assert.ok(!output.hookSpecificOutput.permissionDecisionReason.includes('rollback'),
'should not mention rollback');
})) passed++; else failed++;
// --- Issue #2078 review fix: warning emitted once per *distinct*
// invalid regex, not once per process. Verifies the same-process
// path that the reviewers (CodeRabbit + cubic) flagged.
@@ -1745,6 +1788,70 @@ function runTests() {
'destructive gate is exempt from dampening');
})) passed++; else failed++;
// --- Novos comandos Git read-only ---
console.log('\n Novos comandos Git read-only:');
clearState();
if (test('allows git diff --cached', () => {
expectAllow('git diff --cached', 'git diff --cached');
})) passed++; else failed++;
clearState();
if (test('allows git diff --staged', () => {
expectAllow('git diff --staged', 'git diff --staged');
})) passed++; else failed++;
clearState();
if (test('allows git diff --stat', () => {
expectAllow('git diff --stat', 'git diff --stat');
})) passed++; else failed++;
clearState();
if (test('allows git diff --name-only --cached', () => {
expectAllow('git diff --name-only --cached', 'git diff --name-only --cached');
})) passed++; else failed++;
clearState();
if (test('allows git show --stat', () => {
expectAllow('git show --stat', 'git show --stat');
})) passed++; else failed++;
clearState();
if (test('allows git show --name-only', () => {
expectAllow('git show --name-only', 'git show --name-only');
})) passed++; else failed++;
clearState();
if (test('allows git show HEAD --stat', () => {
expectAllow('git show HEAD --stat', 'git show HEAD --stat');
})) passed++; else failed++;
clearState();
if (test('allows git show HEAD --name-only', () => {
expectAllow('git show HEAD --name-only', 'git show HEAD --name-only');
})) passed++; else failed++;
// Garantir que comandos destrutivos continuam negados
clearState();
if (test('still denies git reset --hard', () => {
expectDestructiveDeny('git reset --hard', 'git reset --hard');
})) passed++; else failed++;
clearState();
if (test('still denies git checkout -f', () => {
expectDestructiveDeny('git checkout -f main', 'git checkout -f');
})) passed++; else failed++;
clearState();
if (test('still denies git clean -fd', () => {
expectDestructiveDeny('git clean -fd', 'git clean -fd');
})) passed++; else failed++;
clearState();
if (test('still denies git push --force', () => {
expectDestructiveDeny('git push --force origin main', 'git push --force');
})) passed++; else failed++;
// Cleanup only the temp directory created by this test file.
try {
if (fs.existsSync(stateDir)) {
+148 -133
View File
@@ -6,7 +6,7 @@ const assert = require('assert');
const fs = require('fs');
const os = require('os');
const path = require('path');
const { execFileSync, spawnSync } = require('child_process');
const { spawnSync } = require('child_process');
const { createStateStore } = require('../../scripts/lib/state-store');
@@ -23,7 +23,9 @@ function cleanup(dirPath) {
function writeGhShim(rootDir, responses) {
const shimPath = path.join(rootDir, 'gh-shim.js');
const logPath = path.join(rootDir, 'gh-calls.jsonl');
fs.writeFileSync(shimPath, `
fs.writeFileSync(
shimPath,
`
const fs = require('fs');
const responses = ${JSON.stringify(responses)};
const args = process.argv.slice(2);
@@ -42,7 +44,8 @@ if (args[0] === 'issue' && (args[1] === 'edit' || args[1] === 'comment')) {
}
console.error('Unexpected gh args: ' + key);
process.exit(3);
`);
`
);
return { shimPath, logPath };
}
@@ -51,11 +54,11 @@ function run(args = [], options = {}) {
cwd: options.cwd || path.join(__dirname, '..', '..'),
env: {
...process.env,
...(options.env || {}),
...(options.env || {})
},
encoding: 'utf8',
stdio: ['pipe', 'pipe', 'pipe'],
timeout: 10000,
timeout: 10000
});
}
@@ -90,143 +93,155 @@ async function runTests() {
let passed = 0;
let failed = 0;
if (await test('claims an epic issue, updates GitHub state, and caches a work item', async () => {
const rootDir = createTempDir('github-coordination-claim-');
const dbPath = path.join(rootDir, 'state.db');
if (
await test('claims an epic issue, updates GitHub state, and caches a work item', async () => {
const rootDir = createTempDir('github-coordination-claim-');
const dbPath = path.join(rootDir, 'state.db');
try {
const epicBody = [
'# Ship GitHub-native coordination',
'',
'We want deterministic epic state.',
'',
'## Tasks',
'- [ ] Claim the epic',
'- [ ] Validate the epic',
].join('\n');
const issueView = {
number: 12,
title: 'Ship GitHub-native coordination',
body: epicBody,
url: 'https://github.com/affaan-m/ECC/issues/12',
state: 'OPEN',
labels: [{ name: 'epic' }],
author: { login: 'maintainer' },
updatedAt: '2026-06-01T12:00:00Z',
};
const shim = writeGhShim(rootDir, {
'issue view 12 --repo affaan-m/ECC --json number,title,body,url,state,labels,author,updatedAt,assignees': issueView,
});
try {
const epicBody = ['# Ship GitHub-native coordination', '', 'We want deterministic epic state.', '', '## Tasks', '- [ ] Claim the epic', '- [ ] Validate the epic'].join('\n');
const issueView = {
number: 12,
title: 'Ship GitHub-native coordination',
body: epicBody,
url: 'https://github.com/affaan-m/ECC/issues/12',
state: 'OPEN',
labels: [{ name: 'epic' }],
author: { login: 'maintainer' },
updatedAt: '2026-06-01T12:00:00Z'
};
const shim = writeGhShim(rootDir, {
'issue view 12 --repo affaan-m/ECC --json number,title,body,url,state,labels,author,updatedAt,assignees': issueView
});
const result = run(['claim', '12', '--repo', 'affaan-m/ECC', '--actor', 'codex', '--db', dbPath, '--json'], {
cwd: rootDir,
env: {
ECC_GH_SHIM: shim.shimPath,
ECC_GH_SHIM_LOG: shim.logPath,
},
});
assert.strictEqual(result.status, 0, result.stderr);
const payload = parseJson(result.stdout);
assert.strictEqual(payload.status, 'claimed');
assert.strictEqual(payload.owner, 'codex');
assert.strictEqual(payload.project.state, 'in-progress');
const result = run(['claim', '12', '--repo', 'affaan-m/ECC', '--actor', 'codex', '--db', dbPath, '--json'], {
cwd: rootDir,
env: {
ECC_GH_SHIM: shim.shimPath,
ECC_GH_SHIM_LOG: shim.logPath
}
});
assert.strictEqual(result.status, 0, result.stderr);
const payload = parseJson(result.stdout);
assert.strictEqual(payload.status, 'claimed');
assert.strictEqual(payload.owner, 'codex');
assert.strictEqual(payload.project.state, 'in-progress');
const logEntries = fs.readFileSync(shim.logPath, 'utf8').trim().split(/\r?\n/).map(line => JSON.parse(line));
assert.ok(logEntries.some(entry => entry.args[0] === 'issue' && entry.args[1] === 'edit'));
assert.ok(logEntries.some(entry => entry.args[0] === 'issue' && entry.args[1] === 'comment'));
const logEntries = fs
.readFileSync(shim.logPath, 'utf8')
.trim()
.split(/\r?\n/)
.map(line => JSON.parse(line));
assert.ok(logEntries.some(entry => entry.args[0] === 'issue' && entry.args[1] === 'edit'));
assert.ok(logEntries.some(entry => entry.args[0] === 'issue' && entry.args[1] === 'comment'));
const stored = await readStore(dbPath);
const epicItem = stored.items.find(item => item.source === 'github-epic');
assert.ok(epicItem, 'expected github epic work item');
assert.strictEqual(epicItem.status, 'in-progress');
assert.strictEqual(epicItem.metadata.coordination.status, 'claimed');
assert.strictEqual(epicItem.metadata.coordination.owner, 'codex');
} finally {
cleanup(rootDir);
}
})) passed++; else failed++;
const stored = await readStore(dbPath);
const epicItem = stored.items.find(item => item.source === 'github-epic');
assert.ok(epicItem, 'expected github epic work item');
assert.strictEqual(epicItem.status, 'in-progress');
assert.strictEqual(epicItem.metadata.coordination.status, 'claimed');
assert.strictEqual(epicItem.metadata.coordination.owner, 'codex');
} finally {
cleanup(rootDir);
}
})
)
passed++;
else failed++;
if (await test('unblocks an epic when dependencies are closed', async () => {
const rootDir = createTempDir('github-coordination-unblock-');
const dbPath = path.join(rootDir, 'state.db');
if (
await test('unblocks an epic when dependencies are closed', async () => {
const rootDir = createTempDir('github-coordination-unblock-');
const dbPath = path.join(rootDir, 'state.db');
try {
const blockedBody = [
'# Release readiness',
'',
'Dependencies: #2',
'',
'<!-- ecc-coordination:start -->',
'```json',
JSON.stringify({
schemaVersion: 'ecc.github.coordination.v1',
kind: 'epic',
status: 'blocked',
owner: 'codex',
branch: 'feat/release-readiness',
validation: 'pending',
review: 'requested',
project: { state: 'blocked', fields: {} },
dependencies: [2],
tasks: [{ title: 'Check release checklist', done: false }],
labels: ['epic', 'coordination:blocked'],
lastAction: 'claim',
lastActionAt: '2026-06-01T13:00:00Z',
lastSyncAt: '2026-06-01T13:00:00Z',
notes: null,
}, null, 2),
'```',
'<!-- ecc-coordination:end -->',
].join('\n');
const openIssue = {
number: 1,
title: 'Release readiness',
body: blockedBody,
url: 'https://github.com/affaan-m/ECC/issues/1',
state: 'OPEN',
labels: [{ name: 'epic' }, { name: 'coordination:blocked' }],
author: { login: 'codex' },
updatedAt: '2026-06-01T13:00:00Z',
};
const closedDependency = {
number: 2,
title: 'Release prerequisite',
body: '# Release prerequisite',
url: 'https://github.com/affaan-m/ECC/issues/2',
state: 'CLOSED',
labels: [{ name: 'blocked-by-release' }],
author: { login: 'maintainer' },
updatedAt: '2026-06-01T10:00:00Z',
};
const shim = writeGhShim(rootDir, {
'issue list --repo affaan-m/ECC --state all --limit 100 --json number,title,body,url,state,labels,author,updatedAt,assignees': [openIssue, closedDependency],
'issue view 1 --repo affaan-m/ECC --json number,title,body,url,state,labels,author,updatedAt,assignees': openIssue,
});
try {
const blockedBody = [
'# Release readiness',
'',
'Dependencies: #2',
'',
'<!-- ecc-coordination:start -->',
'```json',
JSON.stringify(
{
schemaVersion: 'ecc.github.coordination.v1',
kind: 'epic',
status: 'blocked',
owner: 'codex',
branch: 'feat/release-readiness',
validation: 'pending',
review: 'requested',
project: { state: 'blocked', fields: {} },
dependencies: [2],
tasks: [{ title: 'Check release checklist', done: false }],
labels: ['epic', 'coordination:blocked'],
lastAction: 'claim',
lastActionAt: '2026-06-01T13:00:00Z',
lastSyncAt: '2026-06-01T13:00:00Z',
notes: null
},
null,
2
),
'```',
'<!-- ecc-coordination:end -->'
].join('\n');
const openIssue = {
number: 1,
title: 'Release readiness',
body: blockedBody,
url: 'https://github.com/affaan-m/ECC/issues/1',
state: 'OPEN',
labels: [{ name: 'epic' }, { name: 'coordination:blocked' }],
author: { login: 'codex' },
updatedAt: '2026-06-01T13:00:00Z'
};
const closedDependency = {
number: 2,
title: 'Release prerequisite',
body: '# Release prerequisite',
url: 'https://github.com/affaan-m/ECC/issues/2',
state: 'CLOSED',
labels: [{ name: 'blocked-by-release' }],
author: { login: 'maintainer' },
updatedAt: '2026-06-01T10:00:00Z'
};
const shim = writeGhShim(rootDir, {
'issue list --repo affaan-m/ECC --state all --limit 100 --json number,title,body,url,state,labels,author,updatedAt,assignees': [openIssue, closedDependency],
'issue view 1 --repo affaan-m/ECC --json number,title,body,url,state,labels,author,updatedAt,assignees': openIssue
});
const result = run(['unblock', '--repo', 'affaan-m/ECC', '--db', dbPath, '--json'], {
cwd: rootDir,
env: {
ECC_GH_SHIM: shim.shimPath,
ECC_GH_SHIM_LOG: shim.logPath,
},
});
assert.strictEqual(result.status, 0, result.stderr);
const payload = parseJson(result.stdout);
assert.strictEqual(payload.count, 1);
assert.strictEqual(payload.items[0].status, 'ready');
const result = run(['unblock', '--repo', 'affaan-m/ECC', '--db', dbPath, '--json'], {
cwd: rootDir,
env: {
ECC_GH_SHIM: shim.shimPath,
ECC_GH_SHIM_LOG: shim.logPath
}
});
assert.strictEqual(result.status, 0, result.stderr);
const payload = parseJson(result.stdout);
assert.strictEqual(payload.count, 1);
assert.strictEqual(payload.items[0].status, 'ready');
const logEntries = fs.readFileSync(shim.logPath, 'utf8').trim().split(/\r?\n/).map(line => JSON.parse(line));
assert.ok(logEntries.some(entry => entry.args[0] === 'issue' && entry.args[1] === 'edit'));
assert.ok(logEntries.some(entry => entry.args[0] === 'issue' && entry.args[1] === 'comment'));
const logEntries = fs
.readFileSync(shim.logPath, 'utf8')
.trim()
.split(/\r?\n/)
.map(line => JSON.parse(line));
assert.ok(logEntries.some(entry => entry.args[0] === 'issue' && entry.args[1] === 'edit'));
assert.ok(logEntries.some(entry => entry.args[0] === 'issue' && entry.args[1] === 'comment'));
const stored = await readStore(dbPath);
const epicItem = stored.items.find(item => item.source === 'github-epic');
assert.ok(epicItem, 'expected github epic work item');
assert.strictEqual(epicItem.metadata.coordination.status, 'ready');
} finally {
cleanup(rootDir);
}
})) passed++; else failed++;
const stored = await readStore(dbPath);
const epicItem = stored.items.find(item => item.source === 'github-epic');
assert.ok(epicItem, 'expected github epic work item');
assert.strictEqual(epicItem.metadata.coordination.status, 'ready');
} finally {
cleanup(rootDir);
}
})
)
passed++;
else failed++;
process.stdout.write(`\nResults: Passed: ${passed}, Failed: ${failed}\n`);
process.exit(failed > 0 ? 1 : 0);
+180 -85
View File
@@ -23,63 +23,80 @@ __metadata:
languageName: node
linkType: hard
"@eslint-community/regexpp@npm:^4.12.2":
"@eslint-community/regexpp@npm:^4.12.1":
version: 4.12.2
resolution: "@eslint-community/regexpp@npm:4.12.2"
checksum: 10c0/fddcbc66851b308478d04e302a4d771d6917a0b3740dc351513c0da9ca2eab8a1adf99f5e0aa7ab8b13fa0df005c81adeee7e63a92f3effd7d367a163b721c2d
languageName: node
linkType: hard
"@eslint/config-array@npm:^0.23.5":
version: 0.23.5
resolution: "@eslint/config-array@npm:0.23.5"
"@eslint/config-array@npm:^0.21.1":
version: 0.21.1
resolution: "@eslint/config-array@npm:0.21.1"
dependencies:
"@eslint/object-schema": "npm:^3.0.5"
"@eslint/object-schema": "npm:^2.1.7"
debug: "npm:^4.3.1"
minimatch: "npm:^10.2.4"
checksum: 10c0/b24833c4c76e78ee075d306cd3f095db46b2db0f90cc13a6ee6e4275f9889731c05bf5403ab5fefb79c756e07ac9184ed0e04570341382f9eccbccc80e6d1a0c
minimatch: "npm:^3.1.2"
checksum: 10c0/2f657d4edd6ddcb920579b72e7a5b127865d4c3fb4dda24f11d5c4f445a93ca481aebdbd6bf3291c536f5d034458dbcbb298ee3b698bc6c9dd02900fe87eec3c
languageName: node
linkType: hard
"@eslint/config-helpers@npm:^0.6.0":
version: 0.6.0
resolution: "@eslint/config-helpers@npm:0.6.0"
"@eslint/config-helpers@npm:^0.4.2":
version: 0.4.2
resolution: "@eslint/config-helpers@npm:0.4.2"
dependencies:
"@eslint/core": "npm:^1.2.1"
checksum: 10c0/f9af20e8b60b0ba27edb74b8eb40c0c5d51a9bf9baf9e053bb57833a87cb0a1c49b4dfaad88fc24d49c907ad1324c8a0b668684fa9c321351dac4bc9155ec10a
"@eslint/core": "npm:^0.17.0"
checksum: 10c0/92efd7a527b2d17eb1a148409d71d80f9ac160b565ac73ee092252e8bf08ecd08670699f46b306b94f13d22e88ac88a612120e7847570dd7cdc72f234d50dcb4
languageName: node
linkType: hard
"@eslint/core@npm:^1.2.1":
version: 1.2.1
resolution: "@eslint/core@npm:1.2.1"
"@eslint/core@npm:^0.17.0":
version: 0.17.0
resolution: "@eslint/core@npm:0.17.0"
dependencies:
"@types/json-schema": "npm:^7.0.15"
checksum: 10c0/10979b40588ecfef771fcb5013a542a35fb30692cc95a65f3481b0b36fbd89f5679efeb30d57f4eed35203d859aabace2a620177d6c536f71b299a1af2f3398f
checksum: 10c0/9a580f2246633bc752298e7440dd942ec421860d1946d0801f0423830e67887e4aeba10ab9a23d281727a978eb93d053d1922a587d502942a713607f40ed704e
languageName: node
linkType: hard
"@eslint/js@npm:^9.39.2":
"@eslint/eslintrc@npm:^3.3.1":
version: 3.3.3
resolution: "@eslint/eslintrc@npm:3.3.3"
dependencies:
ajv: "npm:^6.12.4"
debug: "npm:^4.3.2"
espree: "npm:^10.0.1"
globals: "npm:^14.0.0"
ignore: "npm:^5.2.0"
import-fresh: "npm:^3.2.1"
js-yaml: "npm:^4.1.1"
minimatch: "npm:^3.1.2"
strip-json-comments: "npm:^3.1.1"
checksum: 10c0/532c7acc7ddd042724c28b1f020bd7bf148fcd4653bb44c8314168b5f772508c842ce4ee070299cac51c5c5757d2124bdcfcef5551c8c58ff9986e3e17f2260d
languageName: node
linkType: hard
"@eslint/js@npm:9.39.2, @eslint/js@npm:^9.39.2":
version: 9.39.2
resolution: "@eslint/js@npm:9.39.2"
checksum: 10c0/00f51c52b04ac79faebfaa65a9652b2093b9c924e945479f1f3945473f78aee83cbc76c8d70bbffbf06f7024626575b16d97b66eab16182e1d0d39daff2f26f5
languageName: node
linkType: hard
"@eslint/object-schema@npm:^3.0.5":
version: 3.0.5
resolution: "@eslint/object-schema@npm:3.0.5"
checksum: 10c0/1db337431f520b99e9edda64ef5fafd7ec6a029843eeb608753025125b6649d861d843cffafafd3c4e37926d7d5f9ec0c6a8e3665c13c3da2144e8132892e92e
"@eslint/object-schema@npm:^2.1.7":
version: 2.1.7
resolution: "@eslint/object-schema@npm:2.1.7"
checksum: 10c0/936b6e499853d1335803f556d526c86f5fe2259ed241bc665000e1d6353828edd913feed43120d150adb75570cae162cf000b5b0dfc9596726761c36b82f4e87
languageName: node
linkType: hard
"@eslint/plugin-kit@npm:^0.7.2":
version: 0.7.2
resolution: "@eslint/plugin-kit@npm:0.7.2"
"@eslint/plugin-kit@npm:^0.4.1":
version: 0.4.1
resolution: "@eslint/plugin-kit@npm:0.4.1"
dependencies:
"@eslint/core": "npm:^1.2.1"
"@eslint/core": "npm:^0.17.0"
levn: "npm:^0.4.1"
checksum: 10c0/aafba08077bcd6d7dde6c2e21db18086046a88f914f29971a84cac9ad2d48952ded1b293e665e523805297eff756522dafa16f0062195e2c7143dcd1d47d11ed
checksum: 10c0/51600f78b798f172a9915dffb295e2ffb44840d583427bc732baf12ecb963eb841b253300e657da91d890f4b323d10a1bd12934bf293e3018d8bb66fdce5217b
languageName: node
linkType: hard
@@ -250,13 +267,6 @@ __metadata:
languageName: node
linkType: hard
"@types/esrecurse@npm:^4.3.1":
version: 4.3.1
resolution: "@types/esrecurse@npm:4.3.1"
checksum: 10c0/90dad74d5da3ad27606d8e8e757322f33171cfeaa15ad558b615cf71bb2a516492d18f55f4816384685a3eb2412142e732bbae9a4a7cd2cf3deb7572aa4ebe03
languageName: node
linkType: hard
"@types/estree@npm:^1.0.6":
version: 1.0.8
resolution: "@types/estree@npm:1.0.8"
@@ -264,13 +274,6 @@ __metadata:
languageName: node
linkType: hard
"@types/estree@npm:^1.0.8":
version: 1.0.9
resolution: "@types/estree@npm:1.0.9"
checksum: 10c0/3ad3286ca2988cd550dafb8f2ad599c8474868e954fa601a36655bdfefd8039f7c714b8c1c7f2ae219ffbd58bd4660e66fa7479a0120fc02d4777057d4865387
languageName: node
linkType: hard
"@types/istanbul-lib-coverage@npm:^2.0.1":
version: 2.0.6
resolution: "@types/istanbul-lib-coverage@npm:2.0.6"
@@ -331,16 +334,16 @@ __metadata:
languageName: node
linkType: hard
"acorn@npm:^8.16.0":
version: 8.17.0
resolution: "acorn@npm:8.17.0"
"acorn@npm:^8.15.0":
version: 8.15.0
resolution: "acorn@npm:8.15.0"
bin:
acorn: bin/acorn
checksum: 10c0/5dcefea5f8f023b6cc24cbe71fb5a8112b601d36c4fa07d14e4e6ffc2ee47383332c46b36c766d9437725aa6660156eae50efa0c838719823b50d7c327c4ed42
checksum: 10c0/dec73ff59b7d6628a01eebaece7f2bdb8bb62b9b5926dcad0f8931f2b8b79c2be21f6c68ac095592adb5adb15831a3635d9343e6a91d028bbe85d564875ec3ec
languageName: node
linkType: hard
"ajv@npm:^6.14.0":
"ajv@npm:^6.12.4":
version: 6.15.0
resolution: "ajv@npm:6.15.0"
dependencies:
@@ -378,7 +381,7 @@ __metadata:
languageName: node
linkType: hard
"ansi-styles@npm:^4.0.0":
"ansi-styles@npm:^4.0.0, ansi-styles@npm:^4.1.0":
version: 4.3.0
resolution: "ansi-styles@npm:4.3.0"
dependencies:
@@ -394,6 +397,13 @@ __metadata:
languageName: node
linkType: hard
"balanced-match@npm:^1.0.0":
version: 1.0.2
resolution: "balanced-match@npm:1.0.2"
checksum: 10c0/9308baf0a7e4838a82bbfd11e01b1cb0f0cf2893bc1676c27c2a8c0e70cbae1c59120c3268517a8ae7fb6376b4639ef81ca22582611dbee4ed28df945134aaee
languageName: node
linkType: hard
"balanced-match@npm:^4.0.2":
version: 4.0.4
resolution: "balanced-match@npm:4.0.4"
@@ -401,6 +411,16 @@ __metadata:
languageName: node
linkType: hard
"brace-expansion@npm:^1.1.7":
version: 1.1.14
resolution: "brace-expansion@npm:1.1.14"
dependencies:
balanced-match: "npm:^1.0.0"
concat-map: "npm:0.0.1"
checksum: 10c0/b6fdac832bc4e36a753658c9ed052c2e1a2be221763b002df25d1efbf7d21724334e726a6cd5eadc72a4b19ec3efb632d629cc003bc9c62f7af7a7915ffa4385
languageName: node
linkType: hard
"brace-expansion@npm:^5.0.5":
version: 5.0.6
resolution: "brace-expansion@npm:5.0.6"
@@ -436,6 +456,23 @@ __metadata:
languageName: node
linkType: hard
"callsites@npm:^3.0.0":
version: 3.1.0
resolution: "callsites@npm:3.1.0"
checksum: 10c0/fff92277400eb06c3079f9e74f3af120db9f8ea03bad0e84d9aede54bbe2d44a56cccb5f6cf12211f93f52306df87077ecec5b712794c5a9b5dac6d615a3f301
languageName: node
linkType: hard
"chalk@npm:^4.0.0":
version: 4.1.2
resolution: "chalk@npm:4.1.2"
dependencies:
ansi-styles: "npm:^4.1.0"
supports-color: "npm:^7.1.0"
checksum: 10c0/4a3fef5cc34975c898ffe77141450f679721df9dde00f6c304353fa9c8b571929123b26a0e4617bde5018977eb655b31970c297b91b63ee83bb82aeb04666880
languageName: node
linkType: hard
"character-entities-legacy@npm:^3.0.0":
version: 3.0.0
resolution: "character-entities-legacy@npm:3.0.0"
@@ -505,6 +542,13 @@ __metadata:
languageName: node
linkType: hard
"concat-map@npm:0.0.1":
version: 0.0.1
resolution: "concat-map@npm:0.0.1"
checksum: 10c0/c996b1cfdf95b6c90fee4dae37e332c8b6eb7d106430c17d538034c0ad9a1630cb194d2ab37293b1bdd4d779494beee7786d586a50bd9376fd6f7bcc2bd4c98f
languageName: node
linkType: hard
"convert-source-map@npm:^2.0.0":
version: 2.0.0
resolution: "convert-source-map@npm:2.0.0"
@@ -591,7 +635,7 @@ __metadata:
"@types/node": "npm:25.9.2"
ajv: "npm:^8.18.0"
c8: "npm:^11.0.0"
eslint: "npm:^10.5.0"
eslint: "npm:^9.39.2"
globals: "npm:^17.4.0"
markdownlint-cli: "npm:^0.48.0"
sql.js: "npm:^1.14.1"
@@ -656,15 +700,13 @@ __metadata:
languageName: node
linkType: hard
"eslint-scope@npm:^9.1.2":
version: 9.1.2
resolution: "eslint-scope@npm:9.1.2"
"eslint-scope@npm:^8.4.0":
version: 8.4.0
resolution: "eslint-scope@npm:8.4.0"
dependencies:
"@types/esrecurse": "npm:^4.3.1"
"@types/estree": "npm:^1.0.8"
esrecurse: "npm:^4.3.0"
estraverse: "npm:^5.2.0"
checksum: 10c0/9fb8bca5a73e5741efb6cec84467027b6cb6f4203ff9b43a938e272c5cd30800bde46a5c20dfd1609f840225f0b62b7673be391b20acadf8658ca9fa4729b3dd
checksum: 10c0/407f6c600204d0f3705bd557f81bd0189e69cd7996f408f8971ab5779c0af733d1af2f1412066b40ee1588b085874fc37a2333986c6521669cdbdd36ca5058e0
languageName: node
linkType: hard
@@ -675,35 +717,38 @@ __metadata:
languageName: node
linkType: hard
"eslint-visitor-keys@npm:^5.0.1":
version: 5.0.1
resolution: "eslint-visitor-keys@npm:5.0.1"
checksum: 10c0/16190bdf2cbae40a1109384c94450c526a79b0b9c3cb21e544256ed85ac48a4b84db66b74a6561d20fe6ab77447f150d711c2ad5ad74df4fcc133736bce99678
"eslint-visitor-keys@npm:^4.2.1":
version: 4.2.1
resolution: "eslint-visitor-keys@npm:4.2.1"
checksum: 10c0/fcd43999199d6740db26c58dbe0c2594623e31ca307e616ac05153c9272f12f1364f5a0b1917a8e962268fdecc6f3622c1c2908b4fcc2e047a106fe6de69dc43
languageName: node
linkType: hard
"eslint@npm:^10.5.0":
version: 10.5.0
resolution: "eslint@npm:10.5.0"
"eslint@npm:^9.39.2":
version: 9.39.2
resolution: "eslint@npm:9.39.2"
dependencies:
"@eslint-community/eslint-utils": "npm:^4.8.0"
"@eslint-community/regexpp": "npm:^4.12.2"
"@eslint/config-array": "npm:^0.23.5"
"@eslint/config-helpers": "npm:^0.6.0"
"@eslint/core": "npm:^1.2.1"
"@eslint/plugin-kit": "npm:^0.7.2"
"@eslint-community/regexpp": "npm:^4.12.1"
"@eslint/config-array": "npm:^0.21.1"
"@eslint/config-helpers": "npm:^0.4.2"
"@eslint/core": "npm:^0.17.0"
"@eslint/eslintrc": "npm:^3.3.1"
"@eslint/js": "npm:9.39.2"
"@eslint/plugin-kit": "npm:^0.4.1"
"@humanfs/node": "npm:^0.16.6"
"@humanwhocodes/module-importer": "npm:^1.0.1"
"@humanwhocodes/retry": "npm:^0.4.2"
"@types/estree": "npm:^1.0.6"
ajv: "npm:^6.14.0"
ajv: "npm:^6.12.4"
chalk: "npm:^4.0.0"
cross-spawn: "npm:^7.0.6"
debug: "npm:^4.3.2"
escape-string-regexp: "npm:^4.0.0"
eslint-scope: "npm:^9.1.2"
eslint-visitor-keys: "npm:^5.0.1"
espree: "npm:^11.2.0"
esquery: "npm:^1.7.0"
eslint-scope: "npm:^8.4.0"
eslint-visitor-keys: "npm:^4.2.1"
espree: "npm:^10.4.0"
esquery: "npm:^1.5.0"
esutils: "npm:^2.0.2"
fast-deep-equal: "npm:^3.1.3"
file-entry-cache: "npm:^8.0.0"
@@ -713,7 +758,8 @@ __metadata:
imurmurhash: "npm:^0.1.4"
is-glob: "npm:^4.0.0"
json-stable-stringify-without-jsonify: "npm:^1.0.1"
minimatch: "npm:^10.2.4"
lodash.merge: "npm:^4.6.2"
minimatch: "npm:^3.1.2"
natural-compare: "npm:^1.4.0"
optionator: "npm:^0.9.3"
peerDependencies:
@@ -723,22 +769,22 @@ __metadata:
optional: true
bin:
eslint: bin/eslint.js
checksum: 10c0/e1f7a1d442027e5478945cb07b6a382adc3f149fbfd3dbc14951a7a9fa312546b27fb35b0556897ca1d04539ad16ee85bda75ad9f724873e1153d1fbca0d6145
checksum: 10c0/bb88ca8fd16bb7e1ac3e13804c54d41c583214460c0faa7b3e7c574e69c5600c7122295500fb4b0c06067831111db740931e98da1340329527658e1cf80073d3
languageName: node
linkType: hard
"espree@npm:^11.2.0":
version: 11.2.0
resolution: "espree@npm:11.2.0"
"espree@npm:^10.0.1, espree@npm:^10.4.0":
version: 10.4.0
resolution: "espree@npm:10.4.0"
dependencies:
acorn: "npm:^8.16.0"
acorn: "npm:^8.15.0"
acorn-jsx: "npm:^5.3.2"
eslint-visitor-keys: "npm:^5.0.1"
checksum: 10c0/cf87e18ffd9dc113eb8d16588e7757701bc10c9934a71cce8b89c2611d51672681a918307bd6b19ac3ccd0e7ba1cbccc2f815b36b52fa7e73097b251014c3d81
eslint-visitor-keys: "npm:^4.2.1"
checksum: 10c0/c63fe06131c26c8157b4083313cb02a9a54720a08e21543300e55288c40e06c3fc284bdecf108d3a1372c5934a0a88644c98714f38b6ae8ed272b40d9ea08d6b
languageName: node
linkType: hard
"esquery@npm:^1.7.0":
"esquery@npm:^1.5.0":
version: 1.7.0
resolution: "esquery@npm:1.7.0"
dependencies:
@@ -913,6 +959,13 @@ __metadata:
languageName: node
linkType: hard
"globals@npm:^14.0.0":
version: 14.0.0
resolution: "globals@npm:14.0.0"
checksum: 10c0/b96ff42620c9231ad468d4c58ff42afee7777ee1c963013ff8aabe095a451d0ceeb8dcd8ef4cbd64d2538cef45f787a78ba3a9574f4a634438963e334471302d
languageName: node
linkType: hard
"globals@npm:^17.4.0":
version: 17.6.0
resolution: "globals@npm:17.6.0"
@@ -955,6 +1008,16 @@ __metadata:
languageName: node
linkType: hard
"import-fresh@npm:^3.2.1":
version: 3.3.1
resolution: "import-fresh@npm:3.3.1"
dependencies:
parent-module: "npm:^1.0.0"
resolve-from: "npm:^4.0.0"
checksum: 10c0/bf8cc494872fef783249709385ae883b447e3eb09db0ebd15dcead7d9afe7224dad7bd7591c6b73b0b19b3c0f9640eb8ee884f01cfaf2887ab995b0b36a0cbec
languageName: node
linkType: hard
"imurmurhash@npm:^0.1.4":
version: 0.1.4
resolution: "imurmurhash@npm:0.1.4"
@@ -1072,7 +1135,7 @@ __metadata:
languageName: node
linkType: hard
"js-yaml@npm:~4.1.1":
"js-yaml@npm:^4.1.1, js-yaml@npm:~4.1.1":
version: 4.1.1
resolution: "js-yaml@npm:4.1.1"
dependencies:
@@ -1180,6 +1243,13 @@ __metadata:
languageName: node
linkType: hard
"lodash.merge@npm:^4.6.2":
version: 4.6.2
resolution: "lodash.merge@npm:4.6.2"
checksum: 10c0/402fa16a1edd7538de5b5903a90228aa48eb5533986ba7fa26606a49db2572bf414ff73a2c9f5d5fd36b31c46a5d5c7e1527749c07cbcf965ccff5fbdf32c506
languageName: node
linkType: hard
"lru-cache@npm:^11.0.0":
version: 11.2.7
resolution: "lru-cache@npm:11.2.7"
@@ -1553,7 +1623,7 @@ __metadata:
languageName: node
linkType: hard
"minimatch@npm:^10.2.2, minimatch@npm:^10.2.4, minimatch@npm:~10.2.4":
"minimatch@npm:^10.2.2, minimatch@npm:~10.2.4":
version: 10.2.5
resolution: "minimatch@npm:10.2.5"
dependencies:
@@ -1562,6 +1632,15 @@ __metadata:
languageName: node
linkType: hard
"minimatch@npm:^3.1.2":
version: 3.1.5
resolution: "minimatch@npm:3.1.5"
dependencies:
brace-expansion: "npm:^1.1.7"
checksum: 10c0/2ecbdc0d33f07bddb0315a8b5afbcb761307a8778b48f0b312418ccbced99f104a2d17d8aca7573433c70e8ccd1c56823a441897a45e384ea76ef401a26ace70
languageName: node
linkType: hard
"minimist@npm:^1.2.8":
version: 1.2.8
resolution: "minimist@npm:1.2.8"
@@ -1725,6 +1804,15 @@ __metadata:
languageName: node
linkType: hard
"parent-module@npm:^1.0.0":
version: 1.0.1
resolution: "parent-module@npm:1.0.1"
dependencies:
callsites: "npm:^3.0.0"
checksum: 10c0/c63d6e80000d4babd11978e0d3fee386ca7752a02b035fd2435960ffaa7219dc42146f07069fb65e6e8bf1caef89daf9af7535a39bddf354d78bf50d8294f556
languageName: node
linkType: hard
"parse-entities@npm:^4.0.0":
version: 4.0.2
resolution: "parse-entities@npm:4.0.2"
@@ -1820,6 +1908,13 @@ __metadata:
languageName: node
linkType: hard
"resolve-from@npm:^4.0.0":
version: 4.0.0
resolution: "resolve-from@npm:4.0.0"
checksum: 10c0/8408eec31a3112ef96e3746c37be7d64020cda07c03a920f5024e77290a218ea758b26ca9529fd7b1ad283947f34b2291c1c0f6aa0ed34acfdda9c6014c8d190
languageName: node
linkType: hard
"run-con@npm:~1.3.2":
version: 1.3.2
resolution: "run-con@npm:1.3.2"
@@ -1919,7 +2014,7 @@ __metadata:
languageName: node
linkType: hard
"strip-json-comments@npm:~3.1.1":
"strip-json-comments@npm:^3.1.1, strip-json-comments@npm:~3.1.1":
version: 3.1.1
resolution: "strip-json-comments@npm:3.1.1"
checksum: 10c0/9681a6257b925a7fa0f285851c0e613cc934a50661fa7bb41ca9cbbff89686bb4a0ee366e6ecedc4daafd01e83eee0720111ab294366fe7c185e935475ebcecd
@@ -1936,15 +2031,15 @@ __metadata:
linkType: hard
"tar@npm:^7.5.4":
version: 7.5.13
resolution: "tar@npm:7.5.13"
version: 7.5.16
resolution: "tar@npm:7.5.16"
dependencies:
"@isaacs/fs-minipass": "npm:^4.0.0"
chownr: "npm:^3.0.0"
minipass: "npm:^7.1.2"
minizlib: "npm:^3.1.0"
yallist: "npm:^5.0.0"
checksum: 10c0/5c65b8084799bde7a791593a1c1a45d3d6ee98182e3700b24c247b7b8f8654df4191642abbdb07ff25043d45dcff35620827c3997b88ae6c12040f64bed5076b
checksum: 10c0/4f37f3c4bd2ca2755fd736a5df1d573c1a868ec1b1e893346aeafa95ac510f9e2fd1469420bd866cc7904799e5bd4ac62b5d4f03fe27747d6e1e373b44505c5c
languageName: node
linkType: hard