@mmerterden/multi-agent-pipeline 17.5.1 → 18.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +276 -0
- package/README.md +59 -1
- package/README.tr.md +57 -0
- package/docs/adr/0011-dormant-ci.md +25 -1
- package/docs/features.md +24 -0
- package/docs/server-readiness.md +188 -0
- package/docs/token-budget-history.md +1 -1
- package/index.js +16 -1
- package/install/_common.mjs +42 -17
- package/install/_dev-only-files.mjs +8 -0
- package/install/_unattended-profile.mjs +113 -0
- package/install/index.mjs +48 -0
- package/install/templates/claude-hooks.json +13 -1
- package/manifest.json +1049 -0
- package/package.json +5 -2
- package/pipeline/commands/multi-agent/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/feedback/SKILL.md +7 -1
- package/pipeline/commands/multi-agent/graph/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/issue/SKILL.md +13 -1
- package/pipeline/commands/multi-agent/jira/SKILL.md +13 -1
- package/pipeline/commands/multi-agent/resume/SKILL.md +16 -1
- package/pipeline/commands/multi-agent/setup/SKILL.md +14 -16
- package/pipeline/commands/multi-agent/status/SKILL.md +52 -21
- package/pipeline/commands/multi-agent/update/SKILL.md +13 -56
- package/pipeline/lib/_jira-auth.sh +8 -0
- package/pipeline/lib/analysis-jira-write.sh +32 -0
- package/pipeline/lib/ask-choice.sh +13 -2
- package/pipeline/lib/autopilot-state.sh +8 -0
- package/pipeline/lib/fatal.mjs +129 -0
- package/pipeline/lib/figma-mcp-refresh.sh +18 -0
- package/pipeline/lib/figma-screenshot.sh +18 -0
- package/pipeline/lib/invoked-directly.mjs +43 -0
- package/pipeline/lib/jira-publish.sh +42 -0
- package/pipeline/lib/md2confluence-v3.py +47 -0
- package/pipeline/lib/outbound-gate.mjs +175 -0
- package/pipeline/lib/plan-todos.sh +27 -6
- package/pipeline/lib/post-pr-review.sh +77 -8
- package/pipeline/lib/repo-hygiene.sh +8 -3
- package/pipeline/lib/require-jq.sh +40 -0
- package/pipeline/lib/run-paths.sh +335 -0
- package/pipeline/multi-agent-refs/features/autopilot-circuit-breaker.md +70 -0
- package/pipeline/multi-agent-refs/features/code-graph.md +20 -0
- package/pipeline/multi-agent-refs/features/cost-analysis.md +93 -0
- package/pipeline/multi-agent-refs/features/doctor.md +68 -0
- package/pipeline/multi-agent-refs/features/maturity-followup.md +166 -0
- package/pipeline/multi-agent-refs/features/package-manager.md +80 -0
- package/pipeline/multi-agent-refs/features/usage-reporting.md +79 -0
- package/pipeline/multi-agent-refs/features/verify-by-test.md +1 -1
- package/pipeline/multi-agent-refs/features/verify.md +83 -0
- package/pipeline/multi-agent-refs/phases/operations.md +13 -2
- package/pipeline/multi-agent-refs/phases/phase-0-init.md +6 -3
- package/pipeline/multi-agent-refs/phases/phase-3-dev.md +8 -2
- package/pipeline/multi-agent-refs/phases/phase-4-review.md +1 -1
- package/pipeline/multi-agent-refs/picker-contract.md +1 -1
- package/pipeline/multi-agent-refs/unattended-contract.md +129 -0
- package/pipeline/preferences-template.json +1 -1
- package/pipeline/schemas/agent-state.schema.json +122 -11
- package/pipeline/schemas/prefs.schema.json +35 -0
- package/pipeline/schemas/token-budget.json +2 -2
- package/pipeline/scripts/_run-paths.mjs +372 -0
- package/pipeline/scripts/aggregate-metrics.mjs +64 -64
- package/pipeline/scripts/autopilot-arming.mjs +2 -1
- package/pipeline/scripts/autopilot-intake.mjs +2 -1
- package/pipeline/scripts/autopilot-runner.mjs +206 -2
- package/pipeline/scripts/build-references.mjs +2 -1
- package/pipeline/scripts/build-stack-plugins.mjs +10 -2
- package/pipeline/scripts/capture-evidence.sh +7 -2
- package/pipeline/scripts/classify-plan-safety.mjs +2 -1
- package/pipeline/scripts/cost-analyze.mjs +600 -0
- package/pipeline/scripts/cost-budget-check.mjs +4 -12
- package/pipeline/scripts/council-view.mjs +2 -1
- package/pipeline/scripts/crush-json.mjs +2 -1
- package/pipeline/scripts/diff-explain.mjs +6 -9
- package/pipeline/scripts/diff-risk-score.mjs +2 -1
- package/pipeline/scripts/doctor.mjs +203 -4
- package/pipeline/scripts/evidence-gate.mjs +9 -3
- package/pipeline/scripts/feedback-send.mjs +13 -3
- package/pipeline/scripts/gc-abandoned.sh +29 -13
- package/pipeline/scripts/gc-worktrees.sh +11 -4
- package/pipeline/scripts/github-ssh-setup.sh +64 -7
- package/pipeline/scripts/graph-mermaid.mjs +4 -2
- package/pipeline/scripts/graph-report.mjs +155 -1
- package/pipeline/scripts/keychain-save.sh +101 -30
- package/pipeline/scripts/learn-from-transcripts.mjs +2 -1
- package/pipeline/scripts/learning-curve.mjs +34 -29
- package/pipeline/scripts/make-manifest.mjs +199 -0
- package/pipeline/scripts/maturity-followup.mjs +294 -0
- package/pipeline/scripts/migrate-prefs.mjs +2 -1
- package/pipeline/scripts/migrate-state.mjs +94 -4
- package/pipeline/scripts/package-manager.mjs +310 -0
- package/pipeline/scripts/phase-banner.sh +6 -2
- package/pipeline/scripts/phase-tracker.sh +41 -3
- package/pipeline/scripts/plan-coverage-gate.mjs +6 -2
- package/pipeline/scripts/pre-commit-check.sh +7 -0
- package/pipeline/scripts/pre-push-check.sh +7 -0
- package/pipeline/scripts/purge.sh +23 -6
- package/pipeline/scripts/render-agent-log-cost.sh +9 -2
- package/pipeline/scripts/render-cost-summary.sh +9 -2
- package/pipeline/scripts/render-work-summary.sh +11 -4
- package/pipeline/scripts/review-file-filter.mjs +4 -2
- package/pipeline/scripts/review-scope.mjs +2 -1
- package/pipeline/scripts/routine-registry.mjs +2 -1
- package/pipeline/scripts/run-aggregator.mjs +13 -14
- package/pipeline/scripts/run-metrics.mjs +3 -1
- package/pipeline/scripts/runs-index.mjs +343 -0
- package/pipeline/scripts/scorecard-snapshot.mjs +178 -0
- package/pipeline/scripts/search-logs.sh +18 -0
- package/pipeline/scripts/test-gap-scan.mjs +2 -1
- package/pipeline/scripts/test-integrity-gate.mjs +2 -1
- package/pipeline/scripts/update-issue-progress.sh +56 -7
- package/pipeline/scripts/usage-register.mjs +271 -0
- package/pipeline/scripts/usage-report.mjs +14 -3
- package/pipeline/scripts/validate-analysis-doc.mjs +2 -1
- package/pipeline/scripts/validate-code-graph.mjs +6 -3
- package/pipeline/scripts/validate-complaint-doc.mjs +2 -1
- package/pipeline/scripts/validate-diff-risk.mjs +6 -3
- package/pipeline/scripts/validate-test-gap.mjs +6 -3
- package/pipeline/scripts/validate-triage.mjs +3 -1
- package/pipeline/scripts/verify-citations.mjs +4 -2
- package/pipeline/scripts/verify.mjs +327 -0
- package/pipeline/scripts/worktree-finalize.sh +13 -4
- package/pipeline/scripts/write-state.mjs +154 -15
- package/pipeline/skills/.skill-manifest.json +6 -6
- package/pipeline/skills/.skills-index.json +56 -1
- package/pipeline/skills/shared/README.md +8 -3
- package/pipeline/skills/shared/core/multi-agent-issue/SKILL.md +14 -0
- package/pipeline/skills/shared/core/multi-agent-jira/SKILL.md +14 -0
- package/pipeline/skills/shared/core/multi-agent-setup/SKILL.md +13 -0
- package/pipeline/skills/shared/core/multi-agent-status/SKILL.md +33 -9
- package/pipeline/skills/shared/core/multi-agent-update/SKILL.md +6 -0
- package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/package_app.sh +4 -1
- package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/setup_dev_signing.sh +4 -1
- package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/sign-and-notarize.sh +2 -1
- package/pipeline/skills/skills-index.md +6 -1
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* package-manager.mjs - which package manager does THIS repo use.
|
|
4
|
+
*
|
|
5
|
+
* WHY THIS EXISTS
|
|
6
|
+
*
|
|
7
|
+
* Phase 3 and Phase 5 shelled `npm test` and `npm run build` for the node-shaped
|
|
8
|
+
* stacks, hardcoded. A repo on pnpm, yarn or bun then gets one of two outcomes,
|
|
9
|
+
* both bad: the command fails outright, or npm quietly installs against a lock
|
|
10
|
+
* file it does not own and the run continues on a tree the repo's own tooling
|
|
11
|
+
* would not have produced. Either way it happens in Phase 3, after a worktree
|
|
12
|
+
* and a branch already exist - the same failure shape ADR-0012 rejected for
|
|
13
|
+
* platforms ("it fails partway through, having already created a worktree and a
|
|
14
|
+
* branch").
|
|
15
|
+
*
|
|
16
|
+
* The iOS and Android paths are untouched; this is only about the stacks where
|
|
17
|
+
* a node package manager is the build tool.
|
|
18
|
+
*
|
|
19
|
+
* NODE CORE ONLY (ADR-0004). No corepack call, no spawn, no network: the answer
|
|
20
|
+
* is derivable from files that are already on disk, and a resolver that shells
|
|
21
|
+
* out would need a working install of the very tool it is trying to identify.
|
|
22
|
+
*
|
|
23
|
+
* RESOLUTION ORDER, strongest evidence first:
|
|
24
|
+
* 1. $MA_PACKAGE_MANAGER an explicit override always wins
|
|
25
|
+
* 2. package.json "packageManager" the repo's own declaration (corepack's field)
|
|
26
|
+
* 3. a lock file what the repo actually committed
|
|
27
|
+
* 4. npm the default, reported AS a default
|
|
28
|
+
*
|
|
29
|
+
* The order is "what the repo said" before "what the repo left behind", because
|
|
30
|
+
* a stale lock file outlives a migration and the declaration does not.
|
|
31
|
+
*
|
|
32
|
+
* Usage:
|
|
33
|
+
* node package-manager.mjs detect --dir <d> -> JSON
|
|
34
|
+
* node package-manager.mjs test --dir <d> [--pattern <p>] -> the command line
|
|
35
|
+
* node package-manager.mjs run --dir <d> --script build -> the command line
|
|
36
|
+
* node package-manager.mjs install --dir <d> -> the command line
|
|
37
|
+
*
|
|
38
|
+
* Exit: 0 ok · 2 usage · 3 the requested script is not declared (run/test)
|
|
39
|
+
*
|
|
40
|
+
* @module pipeline/scripts/package-manager
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
import { existsSync, readFileSync, realpathSync, statSync } from "node:fs";
|
|
44
|
+
import { dirname, join, resolve as resolvePath } from "node:path";
|
|
45
|
+
import { pathToFileURL } from "node:url";
|
|
46
|
+
|
|
47
|
+
/** Lock file -> package manager. Ordered only for a stable tie-break message. */
|
|
48
|
+
export const LOCKFILES = [
|
|
49
|
+
["bun.lockb", "bun"],
|
|
50
|
+
["bun.lock", "bun"],
|
|
51
|
+
["pnpm-lock.yaml", "pnpm"],
|
|
52
|
+
["yarn.lock", "yarn"],
|
|
53
|
+
["package-lock.json", "npm"],
|
|
54
|
+
["npm-shrinkwrap.json", "npm"],
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
export const KNOWN = ["npm", "pnpm", "yarn", "bun"];
|
|
58
|
+
|
|
59
|
+
function readJson(file) {
|
|
60
|
+
try {
|
|
61
|
+
return JSON.parse(readFileSync(file, "utf8"));
|
|
62
|
+
} catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* `"pnpm@9.1.0"` -> `pnpm`. A field naming something we do not know about is
|
|
69
|
+
* returned as-is by name so the caller can say WHICH unknown manager it saw;
|
|
70
|
+
* garbage (empty, a version with no name) resolves to null and falls through.
|
|
71
|
+
*/
|
|
72
|
+
export function parseManagerField(value) {
|
|
73
|
+
if (typeof value !== "string") return null;
|
|
74
|
+
const name = value.trim().split("@")[0].trim().toLowerCase();
|
|
75
|
+
return /^[a-z][a-z0-9-]*$/.test(name) ? name : null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Walk up from `dir` looking for the evidence. A monorepo keeps its lock file at
|
|
80
|
+
* the root while the task edits a package three levels down, so stopping at the
|
|
81
|
+
* starting directory would resolve to the default for most real repos.
|
|
82
|
+
*
|
|
83
|
+
* The walk stops AFTER the directory that holds `.git` - that is the repo, and
|
|
84
|
+
* anything above it belongs to somebody else (a home directory with a stray
|
|
85
|
+
* yarn.lock has misrouted builds before).
|
|
86
|
+
*/
|
|
87
|
+
export function* ancestors(dir) {
|
|
88
|
+
let cur = resolvePath(dir);
|
|
89
|
+
for (;;) {
|
|
90
|
+
yield cur;
|
|
91
|
+
if (existsSync(join(cur, ".git"))) return;
|
|
92
|
+
const up = dirname(cur);
|
|
93
|
+
if (up === cur) return;
|
|
94
|
+
cur = up;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function locksIn(dir) {
|
|
99
|
+
const found = [];
|
|
100
|
+
for (const [file, pm] of LOCKFILES) {
|
|
101
|
+
const full = join(dir, file);
|
|
102
|
+
if (existsSync(full)) {
|
|
103
|
+
let mtime = 0;
|
|
104
|
+
try {
|
|
105
|
+
mtime = statSync(full).mtimeMs;
|
|
106
|
+
} catch {
|
|
107
|
+
/* unreadable is the same as absent for a tie-break */
|
|
108
|
+
}
|
|
109
|
+
found.push({ file, pm, mtime });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return found;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Resolve the package manager for a directory.
|
|
117
|
+
*
|
|
118
|
+
* @param {string} dir
|
|
119
|
+
* @param {object} [opts]
|
|
120
|
+
* @param {Record<string,string|undefined>} [opts.env]
|
|
121
|
+
* @returns {{pm: string, source: string, evidence: string, root: string|null,
|
|
122
|
+
* known: boolean, ambiguous?: string[]}}
|
|
123
|
+
*/
|
|
124
|
+
export function detect(dir = process.cwd(), { env = process.env } = {}) {
|
|
125
|
+
const raw = (env.MA_PACKAGE_MANAGER || "").trim().toLowerCase();
|
|
126
|
+
// The output of this module is pasted into a shell line, so the name is held to
|
|
127
|
+
// the same shape a manager's binary actually has. Anything else is dropped and
|
|
128
|
+
// the resolution continues from the repo - an override is not worth executing
|
|
129
|
+
// whatever a stray environment variable happens to contain.
|
|
130
|
+
const override = /^[a-z][a-z0-9-]*$/.test(raw) ? raw : "";
|
|
131
|
+
if (override) {
|
|
132
|
+
return {
|
|
133
|
+
pm: override,
|
|
134
|
+
source: "env",
|
|
135
|
+
evidence: "MA_PACKAGE_MANAGER",
|
|
136
|
+
root: null,
|
|
137
|
+
known: KNOWN.includes(override),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
if (raw) {
|
|
141
|
+
process.emitWarning(
|
|
142
|
+
`MA_PACKAGE_MANAGER=${JSON.stringify(raw)} is not a package manager name - ignored`,
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
for (const cur of ancestors(dir)) {
|
|
147
|
+
const pkgPath = join(cur, "package.json");
|
|
148
|
+
if (existsSync(pkgPath)) {
|
|
149
|
+
const declared = parseManagerField(readJson(pkgPath)?.packageManager);
|
|
150
|
+
if (declared) {
|
|
151
|
+
return {
|
|
152
|
+
pm: declared,
|
|
153
|
+
source: "packageManager-field",
|
|
154
|
+
evidence: `${pkgPath}#packageManager`,
|
|
155
|
+
root: cur,
|
|
156
|
+
known: KNOWN.includes(declared),
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const locks = locksIn(cur);
|
|
162
|
+
if (locks.length === 1) {
|
|
163
|
+
return {
|
|
164
|
+
pm: locks[0].pm,
|
|
165
|
+
source: "lockfile",
|
|
166
|
+
evidence: join(cur, locks[0].file),
|
|
167
|
+
root: cur,
|
|
168
|
+
known: true,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
if (locks.length > 1) {
|
|
172
|
+
// Two lock files almost always means a migration that left one behind.
|
|
173
|
+
// Newest wins, and every candidate is reported: a resolver that silently
|
|
174
|
+
// picks one of two committed lock files is how a repo ends up building
|
|
175
|
+
// with the manager it migrated AWAY from.
|
|
176
|
+
const sorted = [...locks].sort((a, b) => b.mtime - a.mtime);
|
|
177
|
+
return {
|
|
178
|
+
pm: sorted[0].pm,
|
|
179
|
+
source: "lockfile-newest",
|
|
180
|
+
evidence: join(cur, sorted[0].file),
|
|
181
|
+
root: cur,
|
|
182
|
+
known: true,
|
|
183
|
+
ambiguous: sorted.map((l) => l.file),
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
pm: "npm",
|
|
190
|
+
source: "default",
|
|
191
|
+
evidence: "no lock file, no declaration",
|
|
192
|
+
root: null,
|
|
193
|
+
known: true,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** Does the resolved root declare this script? */
|
|
198
|
+
export function hasScript(dir, script) {
|
|
199
|
+
for (const cur of ancestors(dir)) {
|
|
200
|
+
const pkgPath = join(cur, "package.json");
|
|
201
|
+
if (!existsSync(pkgPath)) continue;
|
|
202
|
+
const scripts = readJson(pkgPath)?.scripts;
|
|
203
|
+
if (scripts && typeof scripts === "object") return Object.hasOwn(scripts, script);
|
|
204
|
+
}
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* `npm run build` for every manager, deliberately.
|
|
210
|
+
*
|
|
211
|
+
* `pnpm build` and `yarn build` also work, but only until a script shares a name
|
|
212
|
+
* with a builtin (`pnpm test`, `yarn add`, `bun install`): then the builtin wins
|
|
213
|
+
* and the repo's own script never runs. The explicit `run` form has no such
|
|
214
|
+
* collision on any of the four.
|
|
215
|
+
*/
|
|
216
|
+
export function runCommand(pm, script, args = []) {
|
|
217
|
+
const extra = args.length ? ` ${args.join(" ")}` : "";
|
|
218
|
+
return `${pm} run ${script}${extra}`;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* The test command, with pass-through arguments.
|
|
223
|
+
*
|
|
224
|
+
* npm is the odd one: it needs `--` to stop eating the arguments itself. The
|
|
225
|
+
* other three forward them to the script as written, and an extra `--` becomes a
|
|
226
|
+
* literal argument the test runner then has to ignore.
|
|
227
|
+
*/
|
|
228
|
+
export function testCommand(pm, args = []) {
|
|
229
|
+
if (!args.length) return pm === "bun" ? "bun run test" : `${pm} run test`;
|
|
230
|
+
const sep = pm === "npm" ? " -- " : " ";
|
|
231
|
+
return `${pm === "bun" ? "bun run test" : `${pm} run test`}${sep}${args.join(" ")}`;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** The install command. `--frozen-lockfile` is NOT added: that is a CI decision. */
|
|
235
|
+
export function installCommand(pm) {
|
|
236
|
+
return pm === "yarn" ? "yarn install" : `${pm} install`;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// ---------------------------------------------------------------------------
|
|
240
|
+
|
|
241
|
+
function usage(msg) {
|
|
242
|
+
if (msg) process.stderr.write(`package-manager: ${msg}\n`);
|
|
243
|
+
process.stderr.write(
|
|
244
|
+
"usage: package-manager.mjs detect|test|run|install --dir <d> [--script <s>] [--pattern <p>]\n",
|
|
245
|
+
);
|
|
246
|
+
process.exit(2);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function main(argv) {
|
|
250
|
+
const mode = argv[0];
|
|
251
|
+
if (!mode || mode.startsWith("-")) usage("a mode is required");
|
|
252
|
+
const opts = {};
|
|
253
|
+
for (let i = 1; i < argv.length; i += 1) {
|
|
254
|
+
const a = argv[i];
|
|
255
|
+
if (a === "--dir") opts.dir = argv[++i];
|
|
256
|
+
else if (a === "--script") opts.script = argv[++i];
|
|
257
|
+
else if (a === "--pattern") opts.pattern = argv[++i];
|
|
258
|
+
else if (a === "-h" || a === "--help") usage();
|
|
259
|
+
else usage(`unexpected argument ${a}`);
|
|
260
|
+
}
|
|
261
|
+
const dir = opts.dir || process.cwd();
|
|
262
|
+
const d = detect(dir);
|
|
263
|
+
|
|
264
|
+
if (mode === "detect") {
|
|
265
|
+
process.stdout.write(`${JSON.stringify(d)}\n`);
|
|
266
|
+
return 0;
|
|
267
|
+
}
|
|
268
|
+
if (mode === "install") {
|
|
269
|
+
process.stdout.write(`${installCommand(d.pm)}\n`);
|
|
270
|
+
return 0;
|
|
271
|
+
}
|
|
272
|
+
if (mode === "test") {
|
|
273
|
+
if (!hasScript(dir, "test")) {
|
|
274
|
+
process.stderr.write("package-manager: no test script declared in package.json\n");
|
|
275
|
+
return 3;
|
|
276
|
+
}
|
|
277
|
+
const args = opts.pattern ? [opts.pattern] : [];
|
|
278
|
+
process.stdout.write(`${testCommand(d.pm, args)}\n`);
|
|
279
|
+
return 0;
|
|
280
|
+
}
|
|
281
|
+
if (mode === "run") {
|
|
282
|
+
if (!opts.script) usage("run needs --script");
|
|
283
|
+
if (!hasScript(dir, opts.script)) {
|
|
284
|
+
process.stderr.write(`package-manager: no ${opts.script} script declared in package.json\n`);
|
|
285
|
+
return 3;
|
|
286
|
+
}
|
|
287
|
+
process.stdout.write(`${runCommand(d.pm, opts.script)}\n`);
|
|
288
|
+
return 0;
|
|
289
|
+
}
|
|
290
|
+
usage(`unknown mode ${mode}`);
|
|
291
|
+
return 2;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Not `file://${process.argv[1]}`: a path with a space or a non-ASCII character
|
|
295
|
+
// percent-encodes in import.meta.url, and on macOS /tmp is a symlink to
|
|
296
|
+
// /private/tmp so argv[1] and the resolved module URL disagree. Either one
|
|
297
|
+
// silently turns this file into a library that prints nothing and exits 0.
|
|
298
|
+
function isEntrypoint() {
|
|
299
|
+
const arg = process.argv[1];
|
|
300
|
+
if (!arg) return false;
|
|
301
|
+
try {
|
|
302
|
+
return import.meta.url === pathToFileURL(realpathSync(arg)).href;
|
|
303
|
+
} catch {
|
|
304
|
+
return import.meta.url === pathToFileURL(arg).href;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (isEntrypoint()) {
|
|
309
|
+
process.exit(main(process.argv.slice(2)));
|
|
310
|
+
}
|
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
# Turkish. The `auto` sentinel in place of [name] triggers a table lookup.
|
|
24
24
|
# Canonical label set mirrors pipeline/multi-agent-refs/phases.md.
|
|
25
25
|
#
|
|
26
|
-
# Output is colored when stdout is a TTY (TERM != dumb)
|
|
26
|
+
# Output is colored when stdout is a TTY (TERM != dumb) and the run is not
|
|
27
|
+
# marked unattended. Plain otherwise so
|
|
27
28
|
# logs and CI artifacts stay readable. Always written to stdout - log
|
|
28
29
|
# capture is the caller's responsibility.
|
|
29
30
|
|
|
@@ -88,7 +89,10 @@ fi
|
|
|
88
89
|
PHASE_WORD="$(phase_word "$PHASE_LANG")"
|
|
89
90
|
|
|
90
91
|
# Color helpers (skip if not a TTY or TERM=dumb).
|
|
91
|
-
|
|
92
|
+
# Sunucuda terminal olabilir ve renk kimseye gitmez; log dosyasına ANSI
|
|
93
|
+
# kaçışları yazmak onu greplenemez yapar. MULTI_AGENT_UNATTENDED=1 bunu da
|
|
94
|
+
# kapatır; tanımsızken karar bugünküyle aynı.
|
|
95
|
+
if [ -t 1 ] && [ "${TERM:-}" != "dumb" ] && [ "${MULTI_AGENT_UNATTENDED:-}" != "1" ]; then
|
|
92
96
|
C_RESET=$'\033[0m'
|
|
93
97
|
C_DIM=$'\033[2m'
|
|
94
98
|
C_BOLD=$'\033[1m'
|
|
@@ -12,6 +12,10 @@
|
|
|
12
12
|
# $HOME/.claude/logs/multi-agent/<task_id>/tracker-state.json
|
|
13
13
|
# (override with $TRACKER_FILE env var)
|
|
14
14
|
#
|
|
15
|
+
# `init` writes that flat path. READS go through pipeline/lib/run-paths.sh,
|
|
16
|
+
# which also finds a tracker nested under <project>/<task_id>/ - the multi-repo
|
|
17
|
+
# path writes there, and a hard-coded flat lookup used to miss those runs.
|
|
18
|
+
#
|
|
15
19
|
# Commands:
|
|
16
20
|
# init <task_id> Reset tracker for this task
|
|
17
21
|
# add <phase_id> "<name>" Register a phase (status=pending); idempotent - existing id is a no-op
|
|
@@ -52,6 +56,12 @@
|
|
|
52
56
|
|
|
53
57
|
set -uo pipefail
|
|
54
58
|
|
|
59
|
+
# Run-state path resolution: pipeline/lib/run-paths.sh owns both layouts.
|
|
60
|
+
_MA_RP_HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
61
|
+
# shellcheck source=/dev/null
|
|
62
|
+
. "$_MA_RP_HERE/../lib/run-paths.sh" 2>/dev/null || . "$HOME/.claude/lib/run-paths.sh"
|
|
63
|
+
|
|
64
|
+
|
|
55
65
|
if [ "$#" -lt 1 ]; then
|
|
56
66
|
cat >&2 <<USAGE
|
|
57
67
|
usage:
|
|
@@ -110,7 +120,13 @@ if [ -z "$TRACKER_FILE" ]; then
|
|
|
110
120
|
exit 64
|
|
111
121
|
fi
|
|
112
122
|
else
|
|
113
|
-
|
|
123
|
+
# Resolve through run-paths.sh: this script writes flat, but 13 of the
|
|
124
|
+
# trackers on a real machine sit nested under a project directory (written
|
|
125
|
+
# by the multi-repo path), and the hard-coded flat path could not see them.
|
|
126
|
+
# A run that does not exist yet resolves to the flat path, which is exactly
|
|
127
|
+
# where `init` will create it - so new-run behaviour is unchanged.
|
|
128
|
+
TRACKER_FILE="$(ma_resolve_run_file "$TASK_ID_FROM_ENV" tracker-state.json 2>/dev/null \
|
|
129
|
+
|| echo "$HOME/.claude/logs/multi-agent/${TASK_ID_FROM_ENV}/tracker-state.json")"
|
|
114
130
|
fi
|
|
115
131
|
fi
|
|
116
132
|
TRACKER_DIR="$(dirname "$TRACKER_FILE")"
|
|
@@ -430,7 +446,16 @@ save_state() {
|
|
|
430
446
|
# corrupt document (render exits 65). $$ keeps each writer's temp private; the
|
|
431
447
|
# rename is still atomic, so the last full write wins instead of a torn one.
|
|
432
448
|
local tmp="${TRACKER_FILE}.tmp.$$"
|
|
433
|
-
|
|
449
|
+
local doc="$1"
|
|
450
|
+
# Stamp the unlocked-write count, if there were any. jq is guarded because a
|
|
451
|
+
# machine without it must still get its state written - losing the annotation
|
|
452
|
+
# is acceptable, losing the write is not.
|
|
453
|
+
if [ "${TRACKER_LOCK_LOST:-0}" -gt 0 ] && command -v jq >/dev/null 2>&1; then
|
|
454
|
+
doc=$(printf '%s' "$doc" | jq --argjson n "$TRACKER_LOCK_LOST" \
|
|
455
|
+
'.unlockedWrites = ((.unlockedWrites // 0) + $n)' 2>/dev/null || printf '%s' "$doc")
|
|
456
|
+
TRACKER_LOCK_LOST=0
|
|
457
|
+
fi
|
|
458
|
+
printf '%s\n' "$doc" > "$tmp"
|
|
434
459
|
mv "$tmp" "$TRACKER_FILE"
|
|
435
460
|
}
|
|
436
461
|
|
|
@@ -444,6 +469,9 @@ save_state() {
|
|
|
444
469
|
# reclaimed.
|
|
445
470
|
TRACKER_LOCK_DIR=""
|
|
446
471
|
TRACKER_LOCK_HELD=0
|
|
472
|
+
# How many writes went through WITHOUT the lock in this process. Stamped into
|
|
473
|
+
# the state so a run's own record says its counts may be low.
|
|
474
|
+
TRACKER_LOCK_LOST=0
|
|
447
475
|
|
|
448
476
|
state_lock_stale() {
|
|
449
477
|
local pid mtime now age
|
|
@@ -482,7 +510,17 @@ acquire_state_lock() {
|
|
|
482
510
|
fi
|
|
483
511
|
tries=$((tries + 1))
|
|
484
512
|
if [ "$tries" -ge 50 ]; then
|
|
485
|
-
|
|
513
|
+
# STILL fails open, and that is the right call: a lost tracker write is
|
|
514
|
+
# worse than a lost lock, and a hung pipeline is worse than both.
|
|
515
|
+
#
|
|
516
|
+
# What changes is that it stops being invisible. Two writers in the
|
|
517
|
+
# critical section means one of their token deltas is dropped, and the
|
|
518
|
+
# only trace was a warning on stderr inside a background run nobody
|
|
519
|
+
# reads. The count now lands in the state file, so the run CARRIES the
|
|
520
|
+
# evidence that its numbers are low - which is the difference between an
|
|
521
|
+
# undercount and an undercount you can see.
|
|
522
|
+
TRACKER_LOCK_LOST=$((TRACKER_LOCK_LOST + 1))
|
|
523
|
+
echo "phase-tracker: WARN - state lock busy ($TRACKER_LOCK_DIR); proceeding without it. Token counts for this write may be low; recorded as unlockedWrites in the state." >&2
|
|
486
524
|
return 0
|
|
487
525
|
fi
|
|
488
526
|
sleep 0.1
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
//
|
|
30
30
|
// Exit codes: 0 clean, 1 coverage gap, 2 usage / parse error.
|
|
31
31
|
|
|
32
|
-
import { readFileSync, existsSync } from "node:fs";
|
|
32
|
+
import { readFileSync, existsSync, writeFileSync } from "node:fs";
|
|
33
33
|
import { join, isAbsolute } from "node:path";
|
|
34
34
|
import { pathToFileURL } from "node:url";
|
|
35
35
|
|
|
@@ -157,8 +157,12 @@ if (isMain) {
|
|
|
157
157
|
const msg = Array.isArray(todos)
|
|
158
158
|
? "the plan has zero steps - that is an unusable plan, not a clean one; skip this gate explicitly for modes with no Phase 2"
|
|
159
159
|
: "no plan todos found - if this mode has no Phase 2, skip this gate explicitly";
|
|
160
|
+
// fd 1 synchronously, then exit. This sits in `if (isMain)` at module scope,
|
|
161
|
+
// not in a function, so there is nothing to return from - and a
|
|
162
|
+
// process.stdout.write followed by process.exit() loses the verdict when
|
|
163
|
+
// stdout is a pipe.
|
|
160
164
|
if (asJson)
|
|
161
|
-
|
|
165
|
+
writeFileSync(1, `${JSON.stringify({ verdict: "unusable", reason: msg }, null, 2)}\n`);
|
|
162
166
|
else process.stderr.write(`plan-coverage-gate: ${msg}\n`);
|
|
163
167
|
process.exit(2);
|
|
164
168
|
}
|
|
@@ -4,6 +4,13 @@
|
|
|
4
4
|
# Exit 0 = clean, Exit 2 = secrets found. The PreToolUse hook contract
|
|
5
5
|
# (install/templates/claude-hooks.json, agent-guard.sh) blocks the tool call
|
|
6
6
|
# only on exit 2, with the reason on stderr.
|
|
7
|
+
#
|
|
8
|
+
# No `-e` here either, and for a sharper reason than in pre-push-check.sh: this
|
|
9
|
+
# file is a PreToolUse hook that fires on every Bash call, and it is built out
|
|
10
|
+
# of greps that are SUPPOSED to find nothing. Under `-e` the first clean
|
|
11
|
+
# detector would end the scan, and a hook that exits early reports "no secrets"
|
|
12
|
+
# for a file it never finished reading - a scanner failing open, on the path
|
|
13
|
+
# that exists to keep secrets out of commits.
|
|
7
14
|
|
|
8
15
|
set -uo pipefail
|
|
9
16
|
|
|
@@ -43,6 +43,13 @@
|
|
|
43
43
|
# 0 - all gates passed, safe to push
|
|
44
44
|
# 1 - a gate failed (test, lint, schema, or personal-data leak)
|
|
45
45
|
# 2 - environment problem (node missing, npm missing, wrong cwd)
|
|
46
|
+
#
|
|
47
|
+
# No `-e`, and that is a decision rather than an omission. This file RUNS the
|
|
48
|
+
# gates and counts how many failed; `-e` would abort at the first one, so a
|
|
49
|
+
# tree with three problems would report one, get fixed, report the next, and
|
|
50
|
+
# take three full runs to learn what one run already knew. The four destructive
|
|
51
|
+
# scripts in this directory DO use `-e`, because there the next command deletes
|
|
52
|
+
# something. Here the next command is another question.
|
|
46
53
|
|
|
47
54
|
set -uo pipefail
|
|
48
55
|
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
# Exit: 0 on success (including "nothing to do"), 2 on usage error or refused
|
|
34
34
|
# repo.
|
|
35
35
|
|
|
36
|
-
set -
|
|
36
|
+
set -euo pipefail
|
|
37
37
|
|
|
38
38
|
REPO="$PWD"
|
|
39
39
|
DELETE=0
|
|
@@ -66,7 +66,11 @@ fi
|
|
|
66
66
|
# Anchor everything on the MAIN worktree (first porcelain entry), so running
|
|
67
67
|
# purge from inside a linked worktree still targets <repo>/.worktrees/.
|
|
68
68
|
MAIN_WT="$(git -C "$REPO" worktree list --porcelain 2>/dev/null | sed -n 's/^worktree //p' | head -1)"
|
|
69
|
-
|
|
69
|
+
# `|| true` on the resolve, not on the cd: an assignment takes the exit status
|
|
70
|
+
# of its command substitution, so under `set -e` a MAIN_WT that no longer exists
|
|
71
|
+
# would kill the script HERE - three lines before the guard whose whole job is
|
|
72
|
+
# to report exactly that, with a message, as exit 2.
|
|
73
|
+
MAIN_WT="$(cd "$MAIN_WT" 2>/dev/null && pwd -P || true)"
|
|
70
74
|
HOME_REAL="$(cd "$HOME" 2>/dev/null && pwd -P || printf '%s' "$HOME")"
|
|
71
75
|
if [ -z "$MAIN_WT" ] || [ "$MAIN_WT" = "/" ] || [ "$MAIN_WT" = "$HOME_REAL" ]; then
|
|
72
76
|
echo "purge: refusing to operate on repo root '$MAIN_WT'" >&2
|
|
@@ -96,7 +100,11 @@ cur_branch=""
|
|
|
96
100
|
flush_block() {
|
|
97
101
|
[ -z "$cur_path" ] && { cur_branch=""; return 0; }
|
|
98
102
|
local resolved
|
|
99
|
-
|
|
103
|
+
# A registered worktree whose directory is GONE is the ordinary case this
|
|
104
|
+
# function exists to handle - `git worktree list` still lists it, and purge is
|
|
105
|
+
# what cleans it up. resolve_dir fails there, and an unforgiven assignment
|
|
106
|
+
# would end the teardown with exit 1 and no output at all.
|
|
107
|
+
resolved="$(resolve_dir "$cur_path" || true)"
|
|
100
108
|
case "$resolved" in
|
|
101
109
|
"$WT_ROOT"/*)
|
|
102
110
|
wt_paths+=("$resolved")
|
|
@@ -184,7 +192,12 @@ while [ "$i" -lt "${#wt_paths[@]}" ]; do
|
|
|
184
192
|
case "$p" in
|
|
185
193
|
"$WT_ROOT"/*)
|
|
186
194
|
rm -rf "$p" 2>/dev/null && wt_gone=1
|
|
187
|
-
|
|
195
|
+
# `|| true` because this branch only runs when `worktree remove --force`
|
|
196
|
+
# ALREADY failed - a locked or damaged worktree - which is exactly when
|
|
197
|
+
# the follow-up prune is likeliest to fail too. Under `set -e` that would
|
|
198
|
+
# abort the teardown mid-loop, leaving the remaining worktrees, the
|
|
199
|
+
# branches, the counter reset and the exclude-block removal undone.
|
|
200
|
+
git -C "$MAIN_WT" worktree prune >/dev/null 2>&1 || true
|
|
188
201
|
;;
|
|
189
202
|
*) echo "purge: skipping worktree outside $WT_ROOT: $p" >&2 ;;
|
|
190
203
|
esac
|
|
@@ -228,8 +241,12 @@ if [ -n "$COUNTER" ]; then
|
|
|
228
241
|
rm -f "$COUNTER" 2>/dev/null && echo "→ reset task counter $COUNTER"
|
|
229
242
|
fi
|
|
230
243
|
|
|
231
|
-
# Drop the now-empty .worktrees/ shell (only when truly empty).
|
|
232
|
-
|
|
244
|
+
# Drop the now-empty .worktrees/ shell (only when truly empty). The `|| true`
|
|
245
|
+
# is the point of the line, not noise on it: a symlink pointing out of the tree
|
|
246
|
+
# is deliberately NOT followed and NOT deleted, so the directory is routinely
|
|
247
|
+
# non-empty here, rmdir routinely fails, and under `set -e` that ordinary
|
|
248
|
+
# outcome would abort the teardown before the exclude block came out.
|
|
249
|
+
[ -d "$WT_ROOT" ] && { rmdir "$WT_ROOT" 2>/dev/null || true; }
|
|
233
250
|
|
|
234
251
|
# purge is the whole-repo teardown, so it is the one place the managed exclude
|
|
235
252
|
# block should come back out - a per-task finalize must not, because other tasks
|
|
@@ -19,6 +19,13 @@
|
|
|
19
19
|
|
|
20
20
|
set -euo pipefail
|
|
21
21
|
|
|
22
|
+
# Run-state path resolution: pipeline/lib/run-paths.sh owns the two layouts
|
|
23
|
+
# (nested <root>/<project>/<id>/ and flat <root>/<id>/) and every id spelling.
|
|
24
|
+
_MA_RP_HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
25
|
+
# shellcheck source=/dev/null
|
|
26
|
+
. "$_MA_RP_HERE/../lib/run-paths.sh" 2>/dev/null || . "$HOME/.claude/lib/run-paths.sh"
|
|
27
|
+
|
|
28
|
+
|
|
22
29
|
TASK_ID="${1:?usage: render-agent-log-cost.sh <task-id> [--otel-spans <path>]}"
|
|
23
30
|
shift || true
|
|
24
31
|
|
|
@@ -50,8 +57,8 @@ for candidate in \
|
|
|
50
57
|
"$PWD/.worktrees/$TASK_ID/phase-tracker.json" \
|
|
51
58
|
"$PWD/.worktrees/$task_id_bare/phase-tracker.json" \
|
|
52
59
|
"$PWD/.worktrees/task-$task_id_bare/phase-tracker.json" \
|
|
53
|
-
"$
|
|
54
|
-
"$
|
|
60
|
+
"$(ma_resolve_run_file "$TASK_ID" tracker-state.json 2>/dev/null || echo /nonexistent)" \
|
|
61
|
+
"$(ma_resolve_run_file "$task_id_bare" tracker-state.json 2>/dev/null || echo /nonexistent)"
|
|
55
62
|
do
|
|
56
63
|
[ -f "$candidate" ] && { tracker_file="$candidate"; break; }
|
|
57
64
|
done
|
|
@@ -13,6 +13,13 @@
|
|
|
13
13
|
|
|
14
14
|
set -euo pipefail
|
|
15
15
|
|
|
16
|
+
# Run-state path resolution: pipeline/lib/run-paths.sh owns the two layouts
|
|
17
|
+
# (nested <root>/<project>/<id>/ and flat <root>/<id>/) and every id spelling.
|
|
18
|
+
_MA_RP_HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
19
|
+
# shellcheck source=/dev/null
|
|
20
|
+
. "$_MA_RP_HERE/../lib/run-paths.sh" 2>/dev/null || . "$HOME/.claude/lib/run-paths.sh"
|
|
21
|
+
|
|
22
|
+
|
|
16
23
|
TASK_ID="${1:?usage: render-cost-summary.sh <task-id> [--otel-spans <path>]}"
|
|
17
24
|
shift || true
|
|
18
25
|
|
|
@@ -50,8 +57,8 @@ for candidate in \
|
|
|
50
57
|
"$PWD/.worktrees/$TASK_ID/phase-tracker.json" \
|
|
51
58
|
"$PWD/.worktrees/$task_id_bare/phase-tracker.json" \
|
|
52
59
|
"$PWD/.worktrees/task-$task_id_bare/phase-tracker.json" \
|
|
53
|
-
"$
|
|
54
|
-
"$
|
|
60
|
+
"$(ma_resolve_run_file "$TASK_ID" tracker-state.json 2>/dev/null || echo /nonexistent)" \
|
|
61
|
+
"$(ma_resolve_run_file "$task_id_bare" tracker-state.json 2>/dev/null || echo /nonexistent)"
|
|
55
62
|
do
|
|
56
63
|
[ -f "$candidate" ] && { tracker_file="$candidate"; break; }
|
|
57
64
|
done
|
|
@@ -40,6 +40,13 @@
|
|
|
40
40
|
|
|
41
41
|
set -euo pipefail
|
|
42
42
|
|
|
43
|
+
# Run-state path resolution: pipeline/lib/run-paths.sh owns the two layouts
|
|
44
|
+
# (nested <root>/<project>/<id>/ and flat <root>/<id>/) and every id spelling.
|
|
45
|
+
_MA_RP_HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
46
|
+
# shellcheck source=/dev/null
|
|
47
|
+
. "$_MA_RP_HERE/../lib/run-paths.sh" 2>/dev/null || . "$HOME/.claude/lib/run-paths.sh"
|
|
48
|
+
|
|
49
|
+
|
|
43
50
|
TASK_ID="${1:?usage: render-work-summary.sh <task-id>}"
|
|
44
51
|
shift || true
|
|
45
52
|
|
|
@@ -85,11 +92,11 @@ fi
|
|
|
85
92
|
# had a log-dir fallback all along; this is the same list.
|
|
86
93
|
if [ -z "$STATE_FILE" ] || [ -z "$TRACKER_FILE" ]; then
|
|
87
94
|
task_bare="${TASK_ID##*-}"
|
|
95
|
+
# The `*/` glob here saw only the nested layout, and an unmatched glob is a
|
|
96
|
+
# literal path in bash - so a flat-layout run fell through silently.
|
|
88
97
|
for base in \
|
|
89
|
-
"$
|
|
90
|
-
"$
|
|
91
|
-
"$HOME"/.claude/logs/multi-agent/*/"$TASK_ID"/artifacts \
|
|
92
|
-
"$HOME"/.claude/logs/multi-agent/*/"$task_bare"/artifacts
|
|
98
|
+
"$(ma_resolve_run_dir_any "$TASK_ID" 2>/dev/null || echo /nonexistent)/artifacts" \
|
|
99
|
+
"$(ma_resolve_run_dir_any "$task_bare" 2>/dev/null || echo /nonexistent)/artifacts"
|
|
93
100
|
do
|
|
94
101
|
[ -d "$base" ] || continue
|
|
95
102
|
[ -z "$STATE_FILE" ] && [ -f "$base/agent-state.json" ] && STATE_FILE="$base/agent-state.json"
|
|
@@ -44,6 +44,8 @@ import { readFileSync } from "node:fs";
|
|
|
44
44
|
import { join } from "node:path";
|
|
45
45
|
import { globToRegExp } from "./glob-match.mjs";
|
|
46
46
|
import { unquotePathIfNeeded } from "./git-path.mjs";
|
|
47
|
+
import { runMain } from "../lib/fatal.mjs";
|
|
48
|
+
import { invokedDirectly } from "../lib/invoked-directly.mjs";
|
|
47
49
|
|
|
48
50
|
const DEFAULT_PATTERNS = join(import.meta.dirname, "..", "schemas", "review-file-exclusions.json");
|
|
49
51
|
|
|
@@ -175,6 +177,6 @@ function main() {
|
|
|
175
177
|
}
|
|
176
178
|
}
|
|
177
179
|
|
|
178
|
-
if (import.meta.url
|
|
179
|
-
main
|
|
180
|
+
if (invokedDirectly(import.meta.url)) {
|
|
181
|
+
runMain("review-file-filter", main);
|
|
180
182
|
}
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
* Exit 0 always. Invalid/empty input → fail SAFE to "full" (never under-review).
|
|
27
27
|
*/
|
|
28
28
|
import { readFileSync } from "node:fs";
|
|
29
|
+
import { runMain } from "../lib/fatal.mjs";
|
|
29
30
|
|
|
30
31
|
const args = process.argv.slice(2);
|
|
31
32
|
const num = (flag, dflt) => {
|
|
@@ -97,4 +98,4 @@ function main() {
|
|
|
97
98
|
emit({ scope: trivial ? "single" : "full", reason, churn, maxScore, blockers });
|
|
98
99
|
}
|
|
99
100
|
|
|
100
|
-
main
|
|
101
|
+
runMain("review-scope", main);
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
|
|
28
28
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, rmSync, renameSync } from "node:fs";
|
|
29
29
|
import { join, dirname } from "node:path";
|
|
30
|
+
import { runMain } from "../lib/fatal.mjs";
|
|
30
31
|
|
|
31
32
|
const NAME_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
32
33
|
const MAX_NAME = 40;
|
|
@@ -218,4 +219,4 @@ function main() {
|
|
|
218
219
|
}
|
|
219
220
|
}
|
|
220
221
|
|
|
221
|
-
main
|
|
222
|
+
runMain("routine-registry", main);
|