@mmerterden/multi-agent-pipeline 17.6.0 → 19.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 +310 -0
- package/README.md +76 -18
- package/README.tr.md +55 -16
- package/docs/adr/0002-instruction-driven-flag.md +1 -0
- package/docs/adr/0005-lazy-phase-docs.md +11 -1
- package/docs/adr/0008-installer-modularization-and-secret-leak-defense.md +1 -0
- package/docs/adr/0010-own-code-graph.md +1 -0
- package/docs/adr/0011-dormant-ci.md +25 -1
- package/docs/adr/0014-six-phase-consolidation.md +134 -0
- package/docs/adr/README.md +2 -1
- package/docs/architecture.md +37 -38
- package/docs/best-practices.md +1 -1
- package/docs/ecosystem.md +37 -26
- package/docs/engineering.md +1 -1
- package/docs/facts.json +45 -0
- package/docs/features.md +54 -53
- package/docs/performance.md +5 -5
- package/docs/recovery-guide.md +9 -9
- package/docs/server-readiness.md +188 -0
- package/docs/token-budget-history.md +3 -1
- package/index.js +18 -3
- package/install/_codex-agents.mjs +1 -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 +1 -1
- package/install/templates/codex-instructions.md +1 -1
- package/install/templates/copilot-instructions.md +28 -28
- package/manifest.json +1065 -0
- package/package.json +6 -3
- package/pipeline/agents/dev-critic.md +3 -3
- package/pipeline/commands/figma-to-swiftui.md +1 -1
- package/pipeline/commands/multi-agent/SKILL.md +8 -8
- package/pipeline/commands/multi-agent/analysis/SKILL.md +9 -9
- package/pipeline/commands/multi-agent/autopilot/SKILL.md +7 -7
- package/pipeline/commands/multi-agent/channels/SKILL.md +15 -15
- package/pipeline/commands/multi-agent/diff-explain/SKILL.md +6 -6
- package/pipeline/commands/multi-agent/garbage-collect/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/graph/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/help/SKILL.md +62 -62
- package/pipeline/commands/multi-agent/language/SKILL.md +2 -2
- package/pipeline/commands/multi-agent/local/SKILL.md +11 -11
- package/pipeline/commands/multi-agent/local-autopilot/SKILL.md +13 -13
- package/pipeline/commands/multi-agent/log/SKILL.md +2 -2
- package/pipeline/commands/multi-agent/manual-test/SKILL.md +9 -9
- package/pipeline/commands/multi-agent/model/SKILL.md +69 -0
- package/pipeline/commands/multi-agent/refactor/SKILL.md +3 -3
- package/pipeline/commands/multi-agent/resume/SKILL.md +4 -4
- package/pipeline/commands/multi-agent/resume-local/SKILL.md +19 -17
- package/pipeline/commands/multi-agent/review/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/route-off/SKILL.md +36 -0
- package/pipeline/commands/multi-agent/route-on/SKILL.md +74 -0
- package/pipeline/commands/multi-agent/route-status/SKILL.md +56 -0
- package/pipeline/commands/multi-agent/setup/SKILL.md +2 -2
- package/pipeline/commands/multi-agent/status/SKILL.md +54 -23
- package/pipeline/commands/multi-agent/steer/SKILL.md +2 -2
- package/pipeline/commands/multi-agent/sync/SKILL.md +12 -13
- package/pipeline/commands/multi-agent/test/SKILL.md +1 -1
- 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/credential-inventory.sh +1 -1
- package/pipeline/lib/fatal.mjs +129 -0
- package/pipeline/lib/fetch-fortify.sh +1 -1
- 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/model-rung.sh +142 -0
- package/pipeline/lib/outbound-gate.mjs +175 -0
- package/pipeline/lib/phase-schema.mjs +88 -0
- package/pipeline/lib/plan-todos.sh +32 -11
- 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/route-state.sh +161 -0
- package/pipeline/lib/run-paths.sh +335 -0
- package/pipeline/multi-agent-refs/_account-picker.md +1 -1
- package/pipeline/multi-agent-refs/_dev-context.md +1 -1
- package/pipeline/multi-agent-refs/_input-parser.md +1 -1
- package/pipeline/multi-agent-refs/analysis/evidence.md +0 -9
- package/pipeline/multi-agent-refs/analysis/intake.md +1 -1
- package/pipeline/multi-agent-refs/analysis/locked.md +21 -22
- package/pipeline/multi-agent-refs/analysis/render.md +1 -1
- package/pipeline/multi-agent-refs/analysis/synthesis.md +12 -6
- package/pipeline/multi-agent-refs/android-guide.md +1 -1
- package/pipeline/multi-agent-refs/audit-guide.md +13 -13
- package/pipeline/multi-agent-refs/channels/issue-comment.md +2 -2
- package/pipeline/multi-agent-refs/channels/jira.md +3 -3
- package/pipeline/multi-agent-refs/channels/pr.md +4 -4
- package/pipeline/multi-agent-refs/channels/wiki.md +1 -1
- package/pipeline/multi-agent-refs/component-dispatch.md +3 -3
- package/pipeline/multi-agent-refs/cross-cli-contract.md +31 -6
- package/pipeline/multi-agent-refs/features/autopilot-circuit-breaker.md +74 -4
- package/pipeline/multi-agent-refs/features/code-graph.md +5 -5
- package/pipeline/multi-agent-refs/features/cost-analysis.md +93 -0
- package/pipeline/multi-agent-refs/features/design-conformance.md +1 -1
- package/pipeline/multi-agent-refs/features/dev-critic.md +3 -3
- package/pipeline/multi-agent-refs/features/doctor.md +47 -2
- package/pipeline/multi-agent-refs/features/external-context-injection.md +3 -3
- package/pipeline/multi-agent-refs/features/maturity-followup.md +3 -3
- package/pipeline/multi-agent-refs/features/model-fallback.md +5 -5
- package/pipeline/multi-agent-refs/features/plan-todos.md +1 -1
- package/pipeline/multi-agent-refs/features/repo-map.md +1 -1
- package/pipeline/multi-agent-refs/features/review-delta.md +3 -3
- package/pipeline/multi-agent-refs/features/review-multi-repo.md +1 -1
- package/pipeline/multi-agent-refs/features/scope-check.md +4 -4
- package/pipeline/multi-agent-refs/features/skill-conformance.md +2 -2
- package/pipeline/multi-agent-refs/features/stack-skill-routing.md +1 -1
- package/pipeline/multi-agent-refs/features/verify-by-test.md +4 -4
- package/pipeline/multi-agent-refs/features/verify.md +83 -0
- package/pipeline/multi-agent-refs/features/visual-evidence.md +19 -19
- package/pipeline/multi-agent-refs/features/worktree-finalize.md +6 -6
- package/pipeline/multi-agent-refs/issue-jira-triad.md +10 -10
- package/pipeline/multi-agent-refs/knowledge.md +11 -11
- package/pipeline/multi-agent-refs/multi-repo-integration-build.md +13 -13
- package/pipeline/multi-agent-refs/payload-contracts.md +8 -8
- package/pipeline/multi-agent-refs/phases/log-format.md +10 -10
- package/pipeline/multi-agent-refs/phases/modes.md +30 -30
- package/pipeline/multi-agent-refs/phases/operations.md +21 -10
- package/pipeline/multi-agent-refs/phases/phase-0-init.md +25 -25
- package/pipeline/multi-agent-refs/phases/phase-1-plan.md +599 -0
- package/pipeline/multi-agent-refs/phases/{phase-3-dev.md → phase-2-dev.md} +129 -49
- package/pipeline/multi-agent-refs/phases/{phase-4-review.md → phase-3-review.md} +225 -107
- package/pipeline/multi-agent-refs/phases/{phase-6-commit.md → phase-4-commit.md} +23 -23
- package/pipeline/multi-agent-refs/phases/{phase-7-report.md → phase-5-report.md} +29 -29
- package/pipeline/multi-agent-refs/phases.md +44 -48
- package/pipeline/multi-agent-refs/picker-contract.md +1 -1
- package/pipeline/multi-agent-refs/progress-contract.md +6 -6
- package/pipeline/multi-agent-refs/readiness-review.md +1 -1
- package/pipeline/multi-agent-refs/rules.md +7 -7
- package/pipeline/multi-agent-refs/swiftui-guide.md +2 -2
- package/pipeline/multi-agent-refs/tracker-contract.md +31 -32
- package/pipeline/multi-agent-refs/unattended-contract.md +129 -0
- package/pipeline/multi-agent-refs/wiki-capture.md +14 -14
- package/pipeline/preferences-template.json +9 -1
- package/pipeline/rules/outside-the-pipeline.md +1 -1
- package/pipeline/schemas/agent-state.schema.json +50 -50
- package/pipeline/schemas/analysis-output.schema.json +2 -2
- package/pipeline/schemas/autopilot-config.schema.json +1 -1
- package/pipeline/schemas/code-graph.schema.json +1 -1
- package/pipeline/schemas/criteria-manifest.schema.json +1 -1
- package/pipeline/schemas/dev-critic-output.schema.json +1 -1
- package/pipeline/schemas/diff-risk.schema.json +1 -1
- package/pipeline/schemas/migrations/prefs-2.4.0-to-2.5.0.mjs +2 -2
- package/pipeline/schemas/migrations/prefs-2.6.0-to-2.7.0.mjs +31 -0
- package/pipeline/schemas/migrations/state-2.1.0-to-2.2.0.mjs +129 -0
- package/pipeline/schemas/phases.json +105 -0
- package/pipeline/schemas/plan-todos.schema.json +5 -5
- package/pipeline/schemas/planning-output.schema.json +1 -1
- package/pipeline/schemas/prefs.schema.json +100 -56
- package/pipeline/schemas/reviewer-output.schema.json +3 -3
- package/pipeline/schemas/route-config.schema.json +74 -0
- package/pipeline/schemas/scope-check.schema.json +1 -1
- package/pipeline/schemas/test-gap.schema.json +1 -1
- package/pipeline/schemas/token-budget.json +12 -18
- package/pipeline/schemas/triage-output.schema.json +6 -6
- package/pipeline/scripts/README.md +3 -3
- package/pipeline/scripts/_code-graph.mjs +2 -2
- package/pipeline/scripts/_run-paths.mjs +372 -0
- package/pipeline/scripts/_smoke-root.sh +1 -1
- package/pipeline/scripts/aggregate-metrics.mjs +65 -65
- 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/capture-flush.sh +8 -8
- package/pipeline/scripts/capture-resume.sh +3 -3
- package/pipeline/scripts/classify-plan-safety.mjs +3 -2
- 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 +7 -10
- package/pipeline/scripts/diff-risk-score.mjs +2 -1
- package/pipeline/scripts/doctor.mjs +140 -6
- package/pipeline/scripts/evidence-gate.mjs +9 -3
- package/pipeline/scripts/feedback-send.mjs +12 -2
- package/pipeline/scripts/gc-abandoned.sh +32 -16
- package/pipeline/scripts/gc-tmp.sh +1 -1
- package/pipeline/scripts/gc-worktrees.sh +12 -5
- package/pipeline/scripts/gen-facts.mjs +175 -0
- package/pipeline/scripts/gen-mode-dispatch.mjs +32 -37
- package/pipeline/scripts/gen-ref-toc.mjs +1 -1
- package/pipeline/scripts/github-ssh-setup.sh +64 -7
- package/pipeline/scripts/graph-mermaid.mjs +4 -2
- package/pipeline/scripts/graph-report.mjs +1 -1
- package/pipeline/scripts/jira-attach.sh +1 -1
- package/pipeline/scripts/keychain-save.sh +101 -30
- package/pipeline/scripts/learn-from-transcripts.mjs +3 -2
- package/pipeline/scripts/learning-curve.mjs +36 -31
- package/pipeline/scripts/log-metric.sh +17 -4
- package/pipeline/scripts/make-manifest.mjs +199 -0
- package/pipeline/scripts/memory-save.sh +1 -1
- package/pipeline/scripts/migrate-prefs.mjs +24 -6
- package/pipeline/scripts/migrate-state.mjs +94 -4
- package/pipeline/scripts/phase-banner.sh +26 -22
- package/pipeline/scripts/phase-tracker.sh +48 -10
- package/pipeline/scripts/plan-coverage-gate.mjs +8 -4
- 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 +10 -3
- package/pipeline/scripts/render-cost-summary.sh +9 -2
- package/pipeline/scripts/render-work-summary.sh +14 -7
- package/pipeline/scripts/review-file-filter.mjs +5 -3
- package/pipeline/scripts/review-scope.mjs +2 -1
- package/pipeline/scripts/routine-registry.mjs +2 -1
- package/pipeline/scripts/run-aggregator.mjs +26 -20
- package/pipeline/scripts/run-metrics.mjs +4 -2
- package/pipeline/scripts/runs-index.mjs +353 -0
- package/pipeline/scripts/scorecard-snapshot.mjs +178 -0
- package/pipeline/scripts/search-logs.sh +18 -0
- package/pipeline/scripts/smoke-cross-cli-behavior.sh +6 -6
- package/pipeline/scripts/smoke-schema-validation.sh +26 -7
- package/pipeline/scripts/test-gap-scan.mjs +2 -1
- package/pipeline/scripts/test-integrity-gate.mjs +2 -1
- package/pipeline/scripts/token-budget-report.mjs +13 -2
- package/pipeline/scripts/triage-memory.mjs +2 -2
- package/pipeline/scripts/update-issue-progress.sh +56 -7
- package/pipeline/scripts/usage-report.mjs +12 -1
- package/pipeline/scripts/validate-analysis-doc.mjs +75 -18
- 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-planning.mjs +1 -1
- package/pipeline/scripts/validate-reviewer.mjs +1 -1
- package/pipeline/scripts/validate-state.mjs +45 -5
- package/pipeline/scripts/validate-test-gap.mjs +6 -3
- package/pipeline/scripts/validate-triage.mjs +6 -4
- package/pipeline/scripts/verify-citations.mjs +4 -2
- package/pipeline/scripts/verify.mjs +327 -0
- package/pipeline/scripts/worktree-finalize.sh +18 -9
- package/pipeline/scripts/write-state.mjs +154 -15
- package/pipeline/skills/.skill-manifest.json +37 -21
- package/pipeline/skills/.skills-index.json +104 -5
- package/pipeline/skills/shared/README.md +15 -6
- package/pipeline/skills/shared/core/apple-archive-compliance/SKILL.md +2 -2
- package/pipeline/skills/shared/core/google-play-compliance/SKILL.md +2 -2
- package/pipeline/skills/shared/core/multi-agent/SKILL.md +69 -71
- package/pipeline/skills/shared/core/multi-agent-autopilot/SKILL.md +3 -3
- package/pipeline/skills/shared/core/multi-agent-channels/SKILL.md +14 -14
- package/pipeline/skills/shared/core/multi-agent-diff-explain/SKILL.md +5 -5
- package/pipeline/skills/shared/core/multi-agent-graph/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-help/SKILL.md +25 -23
- package/pipeline/skills/shared/core/multi-agent-language/SKILL.md +2 -2
- package/pipeline/skills/shared/core/multi-agent-local/SKILL.md +2 -2
- package/pipeline/skills/shared/core/multi-agent-local-autopilot/SKILL.md +8 -8
- package/pipeline/skills/shared/core/multi-agent-manual-test/SKILL.md +6 -6
- package/pipeline/skills/shared/core/multi-agent-model/SKILL.md +71 -0
- package/pipeline/skills/shared/core/multi-agent-refactor/SKILL.md +3 -3
- package/pipeline/skills/shared/core/multi-agent-resume/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-resume-local/SKILL.md +7 -7
- package/pipeline/skills/shared/core/multi-agent-route-off/SKILL.md +39 -0
- package/pipeline/skills/shared/core/multi-agent-route-on/SKILL.md +76 -0
- package/pipeline/skills/shared/core/multi-agent-route-status/SKILL.md +59 -0
- package/pipeline/skills/shared/core/multi-agent-setup/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-status/SKILL.md +35 -11
- package/pipeline/skills/shared/core/multi-agent-steer/SKILL.md +2 -2
- package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +6 -5
- 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 +13 -4
- package/pipeline/multi-agent-refs/phases/phase-1-analysis.md +0 -263
- package/pipeline/multi-agent-refs/phases/phase-2-planning.md +0 -344
- package/pipeline/multi-agent-refs/phases/phase-5-test.md +0 -182
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
//
|
|
20
20
|
// Exit: 0 always (advisory; empty/missing metrics -> friendly note).
|
|
21
21
|
|
|
22
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
22
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
23
23
|
import { homedir } from "node:os";
|
|
24
24
|
import { join } from "node:path";
|
|
25
25
|
|
|
@@ -39,8 +39,11 @@ for (const a of args) {
|
|
|
39
39
|
const METRICS_FILE =
|
|
40
40
|
process.env.METRICS_FILE || join(homedir(), ".claude", "logs", "multi-agent", "metrics.jsonl");
|
|
41
41
|
|
|
42
|
+
// Called from module scope and has to stop the script, so the write goes to
|
|
43
|
+
// fd 1 synchronously: console.log would still be in the pipe when process.exit
|
|
44
|
+
// tore the process down.
|
|
42
45
|
function emitEmpty(reason) {
|
|
43
|
-
if (opts.json)
|
|
46
|
+
if (opts.json) writeFileSync(1, `${JSON.stringify({ ok: false, reason, buckets: [] })}\n`);
|
|
44
47
|
else console.error(`learning-curve: ${reason}`);
|
|
45
48
|
process.exit(0);
|
|
46
49
|
}
|
|
@@ -84,9 +87,9 @@ for (const e of events) {
|
|
|
84
87
|
}
|
|
85
88
|
if (e.ts && (!t.firstTs || e.ts < t.firstTs)) t.firstTs = e.ts;
|
|
86
89
|
const d = e.details || {};
|
|
87
|
-
// phase-
|
|
90
|
+
// phase-3-review.md's actual review.completed emission (raw_count=/
|
|
88
91
|
// accepted=/deferred=/rejected=/approved=/duration_ms=) carries no
|
|
89
|
-
// review_cycles field - that name only appears on Phase
|
|
92
|
+
// review_cycles field - that name only appears on Phase 5's task.completed
|
|
90
93
|
// summary event, which this never matched. Phase 4 re-runs (and logs its
|
|
91
94
|
// own review.completed) once per rework cycle, so the cycle count for a
|
|
92
95
|
// task is how many review.completed events share its task_id - the same
|
|
@@ -183,35 +186,37 @@ if (buckets.length >= 2) {
|
|
|
183
186
|
|
|
184
187
|
const out = { ok: true, bucketDays: opts.bucketDays, buckets, trend };
|
|
185
188
|
|
|
189
|
+
// else, not process.exit(0) after the write: stdout to a pipe is asynchronous
|
|
190
|
+
// and exiting drops whatever has not drained, so the JSON arrived truncated to
|
|
191
|
+
// anything reading it through a pipe.
|
|
186
192
|
if (opts.json) {
|
|
187
193
|
console.log(JSON.stringify(out, null, 2));
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
};
|
|
198
|
-
console.log(`# Learning curve (${opts.bucketDays}-day buckets)\n`);
|
|
199
|
-
console.log(
|
|
200
|
-
"| Bucket | Tasks | First-pass clean | Avg review cycles | Rework/task | Tokens/task | Cache ratio | Memory precision | Mined lessons/task | Offload bytes saved/task |",
|
|
201
|
-
);
|
|
202
|
-
console.log("|---|---|---|---|---|---|---|---|---|---|");
|
|
203
|
-
for (const b of buckets) {
|
|
194
|
+
} else {
|
|
195
|
+
// Markdown / text (default)
|
|
196
|
+
const arrow = (v, goodIsUp) => {
|
|
197
|
+
if (v == null || v === 0) return "-";
|
|
198
|
+
const up = v > 0;
|
|
199
|
+
const good = up === goodIsUp;
|
|
200
|
+
return `${up ? "+" : ""}${v} ${good ? "(better)" : "(worse)"}`;
|
|
201
|
+
};
|
|
202
|
+
console.log(`# Learning curve (${opts.bucketDays}-day buckets)\n`);
|
|
204
203
|
console.log(
|
|
205
|
-
|
|
204
|
+
"| Bucket | Tasks | First-pass clean | Avg review cycles | Rework/task | Tokens/task | Cache ratio | Memory precision | Mined lessons/task | Offload bytes saved/task |",
|
|
206
205
|
);
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
206
|
+
console.log("|---|---|---|---|---|---|---|---|---|---|");
|
|
207
|
+
for (const b of buckets) {
|
|
208
|
+
console.log(
|
|
209
|
+
`| ${b.bucketStart} | ${b.tasks} | ${b.firstPassCleanRate ?? "-"} | ${b.avgReviewCycles ?? "-"} | ${b.reworkPerTask} | ${b.tokensPerTask} | ${b.cacheRatio ?? "-"} | ${b.memoryPrecision ?? "-"} | ${b.minedLessonsPerTask ?? "-"} | ${b.offloadBytesSavedPerTask ?? "-"} |`,
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
if (trend) {
|
|
213
|
+
console.log(`\n**Trend ${trend.from} -> ${trend.to}:**`);
|
|
214
|
+
console.log(`- First-pass clean: ${arrow(trend.firstPassCleanRate, true)}`);
|
|
215
|
+
console.log(`- Avg review cycles: ${arrow(trend.avgReviewCycles, false)}`);
|
|
216
|
+
console.log(`- Tokens/task: ${arrow(trend.tokensPerTask, false)}`);
|
|
217
|
+
console.log(`- Cache ratio: ${arrow(trend.cacheRatio, true)}`);
|
|
218
|
+
console.log(`- Memory precision: ${arrow(trend.memoryPrecision, true)}`);
|
|
219
|
+
} else {
|
|
220
|
+
console.log("\n(need >= 2 buckets for a trend; keep running the pipeline)");
|
|
221
|
+
}
|
|
217
222
|
}
|
|
@@ -6,11 +6,19 @@
|
|
|
6
6
|
# {
|
|
7
7
|
# "ts": "<ISO 8601 timestamp>",
|
|
8
8
|
# "task_id": "<jira id, github issue, or run id>",
|
|
9
|
-
# "phase": "<0..
|
|
9
|
+
# "phase": "<0..5>",
|
|
10
10
|
# "event": "<event name>",
|
|
11
|
-
# "details": { ...arbitrary key/value pairs... }
|
|
11
|
+
# "details": { ...arbitrary key/value pairs... },
|
|
12
|
+
# "phaseSchema": 2
|
|
12
13
|
# }
|
|
13
14
|
#
|
|
15
|
+
# phaseSchema names the phase vocabulary the line was written under, because
|
|
16
|
+
# this file is append-only and v19.0.0 renumbered the phases. A line with
|
|
17
|
+
# phase 3 means Dev under schema 1 and Review under schema 2. Lines with no
|
|
18
|
+
# phaseSchema field predate v19.0.0 and are schema 1. Aggregators pick their
|
|
19
|
+
# name table from this field; without it the two vocabularies are
|
|
20
|
+
# indistinguishable and every historical roll-up is quietly wrong.
|
|
21
|
+
#
|
|
14
22
|
# Usage:
|
|
15
23
|
# log-metric.sh <task_id> <phase> <event> [key=value ...]
|
|
16
24
|
#
|
|
@@ -18,7 +26,7 @@
|
|
|
18
26
|
# log-metric.sh PROJ-1234 4 review.completed raw_count=8 accepted=3 deferred=2 rejected=3 approved=true duration_ms=42000
|
|
19
27
|
# log-metric.sh PROJ-1234 4 triage.edge_case case=high-rejection-rate
|
|
20
28
|
# log-metric.sh PROJ-1234 3 rework.started iteration=2 accepted_blocking=2
|
|
21
|
-
# log-metric.sh #316
|
|
29
|
+
# log-metric.sh #316 5 task.completed phases=6 review_cycles=2 lang=tr
|
|
22
30
|
#
|
|
23
31
|
# Atomic append: each call uses `>> "$file"` which the kernel guarantees
|
|
24
32
|
# atomic for writes ≤ PIPE_BUF (4 KB on macOS/Linux). One JSONL line is well
|
|
@@ -64,8 +72,13 @@ for kv in "$@"; do
|
|
|
64
72
|
done
|
|
65
73
|
DETAILS="${DETAILS}}"
|
|
66
74
|
|
|
75
|
+
# Read from the phase contract rather than hardcoding: the next renumbering
|
|
76
|
+
# bumps one JSON field instead of hunting for a literal in a shell script.
|
|
77
|
+
PHASE_SCHEMA="$(node -e 'const c=require("'"$(cd "$(dirname "$0")" && pwd)"'/../schemas/phases.json"); process.stdout.write(String(c.phaseSchema))' 2>/dev/null)"
|
|
78
|
+
[ -n "$PHASE_SCHEMA" ] || PHASE_SCHEMA=2
|
|
79
|
+
|
|
67
80
|
TS="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
|
68
|
-
LINE="{\"ts\":\"${TS}\",\"task_id\":\"${TASK_ID}\",\"phase\":\"${PHASE}\",\"event\":\"${EVENT}\",\"details\":${DETAILS}}"
|
|
81
|
+
LINE="{\"ts\":\"${TS}\",\"task_id\":\"${TASK_ID}\",\"phase\":\"${PHASE}\",\"event\":\"${EVENT}\",\"details\":${DETAILS},\"phaseSchema\":${PHASE_SCHEMA}}"
|
|
69
82
|
|
|
70
83
|
# Atomic append (one line, well under PIPE_BUF).
|
|
71
84
|
echo "$LINE" >> "$METRICS_FILE" 2>/dev/null || {
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* make-manifest.mjs - a digest of everything the package ships.
|
|
4
|
+
*
|
|
5
|
+
* "Is my install the one that was published, or has something edited it" is a
|
|
6
|
+
* question this tree could not answer. The install is a COPY: `install.js`
|
|
7
|
+
* writes into ~/.claude, ~/.copilot and ~/.codex, and from that moment the two
|
|
8
|
+
* halves drift independently. An edit made in the installed copy looks like a
|
|
9
|
+
* feature nobody can find the source of; a file the installer failed to write
|
|
10
|
+
* looks like a bug in a script that was never there.
|
|
11
|
+
*
|
|
12
|
+
* The manifest turns both into one command. It records a SHA-256 for every
|
|
13
|
+
* file the package ships, the version, and the commit it was built from, and
|
|
14
|
+
* `multi-agent-pipeline verify` compares an installed tree against it, file by
|
|
15
|
+
* file.
|
|
16
|
+
*
|
|
17
|
+
* The file set is not guessed: it comes from `npm pack --dry-run --json`, so
|
|
18
|
+
* it is by construction the same set npm will publish, `files` globs and all.
|
|
19
|
+
* `--ignore-scripts` is on the inner call because this script runs FROM
|
|
20
|
+
* prepack, and without it npm would run prepack again.
|
|
21
|
+
*
|
|
22
|
+
* Two things the manifest cannot cover, said here rather than discovered later:
|
|
23
|
+
* it cannot hash itself (it does not exist when the list is taken), and a
|
|
24
|
+
* signature over it does not authenticate the tarball - the verifier and the
|
|
25
|
+
* public key travel inside the same tarball, so anyone able to rewrite one can
|
|
26
|
+
* rewrite the others. What a signature DOES catch is a manifest damaged in
|
|
27
|
+
* transit or edited after install, which is the failure this file exists for.
|
|
28
|
+
* Provenance belongs to npm's own integrity field, not to us.
|
|
29
|
+
*
|
|
30
|
+
* Usage:
|
|
31
|
+
* make-manifest.mjs write manifest.json at the repo root
|
|
32
|
+
* make-manifest.mjs --sign also write manifest.sig (needs a signing key)
|
|
33
|
+
* make-manifest.mjs --check recompute and report drift, write nothing
|
|
34
|
+
* make-manifest.mjs --json
|
|
35
|
+
*
|
|
36
|
+
* Exit codes:
|
|
37
|
+
* 0 written, or --check found no drift
|
|
38
|
+
* 1 --check found drift
|
|
39
|
+
* 2 usage or environment error
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
import { execFileSync } from "node:child_process";
|
|
43
|
+
import { createHash, createPrivateKey, sign as cryptoSign } from "node:crypto";
|
|
44
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
45
|
+
import { dirname, join } from "node:path";
|
|
46
|
+
import { fileURLToPath } from "node:url";
|
|
47
|
+
import { runMain } from "../lib/fatal.mjs";
|
|
48
|
+
|
|
49
|
+
const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
50
|
+
const MANIFEST = join(ROOT, "manifest.json");
|
|
51
|
+
|
|
52
|
+
const argv = process.argv.slice(2);
|
|
53
|
+
const has = (f) => argv.includes(f);
|
|
54
|
+
|
|
55
|
+
/** The exact set npm would publish, in npm's own words. */
|
|
56
|
+
function shippedFiles() {
|
|
57
|
+
const out = execFileSync("npm", ["pack", "--dry-run", "--json", "--ignore-scripts"], {
|
|
58
|
+
cwd: ROOT,
|
|
59
|
+
encoding: "utf-8",
|
|
60
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
61
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
62
|
+
});
|
|
63
|
+
const parsed = JSON.parse(out);
|
|
64
|
+
const files = parsed?.[0]?.files;
|
|
65
|
+
if (!Array.isArray(files) || !files.length) {
|
|
66
|
+
throw new Error("npm pack --dry-run returned no file list");
|
|
67
|
+
}
|
|
68
|
+
return files
|
|
69
|
+
.map((f) => f.path)
|
|
70
|
+
.filter((p) => p !== "manifest.json" && p !== "manifest.sig")
|
|
71
|
+
.sort();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function sha256(path) {
|
|
75
|
+
return createHash("sha256").update(readFileSync(path)).digest("hex");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function gitCommit() {
|
|
79
|
+
try {
|
|
80
|
+
return execFileSync("git", ["rev-parse", "HEAD"], {
|
|
81
|
+
cwd: ROOT,
|
|
82
|
+
encoding: "utf-8",
|
|
83
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
84
|
+
}).trim();
|
|
85
|
+
} catch {
|
|
86
|
+
// A tarball unpacked outside a checkout has no git. The manifest is still
|
|
87
|
+
// valid; only the provenance line is unknown, and saying so beats writing
|
|
88
|
+
// a placeholder that reads like a real commit.
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function build() {
|
|
94
|
+
const pkg = JSON.parse(readFileSync(join(ROOT, "package.json"), "utf-8"));
|
|
95
|
+
const files = {};
|
|
96
|
+
for (const rel of shippedFiles()) {
|
|
97
|
+
const abs = join(ROOT, rel);
|
|
98
|
+
if (!existsSync(abs)) continue;
|
|
99
|
+
files[rel] = sha256(abs);
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
name: pkg.name,
|
|
103
|
+
version: pkg.version,
|
|
104
|
+
commit: gitCommit(),
|
|
105
|
+
generatedAt: new Date().toISOString(),
|
|
106
|
+
algorithm: "sha256",
|
|
107
|
+
fileCount: Object.keys(files).length,
|
|
108
|
+
files,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Sign the manifest with an ed25519 key. Optional, and silent about its absence
|
|
114
|
+
* on the common path: a package with no signature still verifies on hashes
|
|
115
|
+
* alone, which is the property that actually gets used.
|
|
116
|
+
*
|
|
117
|
+
* The key is read through the credential store, never taken from argv and never
|
|
118
|
+
* printed. MULTI_AGENT_SIGNING_KEY is the fallback for a build host that has no
|
|
119
|
+
* store (CI), and it holds the PEM itself.
|
|
120
|
+
*
|
|
121
|
+
* @param {string} manifestJson exact bytes that were written to disk
|
|
122
|
+
* @returns {string|null} base64 signature, or null when no key is configured
|
|
123
|
+
*/
|
|
124
|
+
function sign(manifestJson) {
|
|
125
|
+
let pem = process.env.MULTI_AGENT_SIGNING_KEY || "";
|
|
126
|
+
if (!pem) {
|
|
127
|
+
try {
|
|
128
|
+
pem = execFileSync(
|
|
129
|
+
join(ROOT, "pipeline", "lib", "credential-store.sh"),
|
|
130
|
+
["get", "pipeline_signing"],
|
|
131
|
+
{
|
|
132
|
+
encoding: "utf-8",
|
|
133
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
134
|
+
},
|
|
135
|
+
).trim();
|
|
136
|
+
} catch {
|
|
137
|
+
pem = "";
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (!pem) {
|
|
141
|
+
process.stderr.write(
|
|
142
|
+
"make-manifest: no signing key configured - manifest written unsigned (hashes still verify)\n",
|
|
143
|
+
);
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
const privateKey = createPrivateKey(pem);
|
|
147
|
+
return cryptoSign(null, Buffer.from(manifestJson), privateKey).toString("base64");
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function main() {
|
|
151
|
+
if (has("--check")) {
|
|
152
|
+
if (!existsSync(MANIFEST)) {
|
|
153
|
+
process.stderr.write("make-manifest: no manifest.json to check\n");
|
|
154
|
+
process.exit(2);
|
|
155
|
+
}
|
|
156
|
+
const stored = JSON.parse(readFileSync(MANIFEST, "utf-8"));
|
|
157
|
+
const fresh = build();
|
|
158
|
+
const changed = [];
|
|
159
|
+
for (const [rel, hash] of Object.entries(fresh.files)) {
|
|
160
|
+
if (stored.files[rel] !== hash) changed.push(rel);
|
|
161
|
+
}
|
|
162
|
+
const gone = Object.keys(stored.files).filter((r) => !(r in fresh.files));
|
|
163
|
+
const report = { changed, gone, stale: changed.length + gone.length > 0 };
|
|
164
|
+
if (has("--json")) process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
165
|
+
else if (!report.stale)
|
|
166
|
+
process.stdout.write(`manifest.json is current (${fresh.fileCount} files)\n`);
|
|
167
|
+
else {
|
|
168
|
+
process.stdout.write(
|
|
169
|
+
`manifest.json is stale: ${changed.length} changed, ${gone.length} gone\n`,
|
|
170
|
+
);
|
|
171
|
+
for (const r of [...changed, ...gone].slice(0, 20)) process.stdout.write(` ${r}\n`);
|
|
172
|
+
}
|
|
173
|
+
process.exitCode = report.stale ? 1 : 0;
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const manifest = build();
|
|
178
|
+
const json = `${JSON.stringify(manifest, null, 2)}\n`;
|
|
179
|
+
writeFileSync(MANIFEST, json);
|
|
180
|
+
if (has("--sign")) {
|
|
181
|
+
const sig = sign(json);
|
|
182
|
+
if (sig) writeFileSync(join(ROOT, "manifest.sig"), `${sig}\n`);
|
|
183
|
+
}
|
|
184
|
+
if (has("--json"))
|
|
185
|
+
process.stdout.write(
|
|
186
|
+
`${JSON.stringify({ written: MANIFEST, ...manifest, files: undefined }, null, 2)}\n`,
|
|
187
|
+
);
|
|
188
|
+
else
|
|
189
|
+
// STDERR, not stdout. This runs as `prepack`, and npm passes a prepack
|
|
190
|
+
// script's stdout straight through - so one friendly line here lands INSIDE
|
|
191
|
+
// `npm pack --dry-run --json` and every consumer that parses that JSON
|
|
192
|
+
// fails with "Unexpected token 'm'". Two gates caught it within minutes;
|
|
193
|
+
// a publish would not have.
|
|
194
|
+
process.stderr.write(
|
|
195
|
+
`manifest.json: ${manifest.fileCount} files, version ${manifest.version}, commit ${manifest.commit?.slice(0, 12) ?? "unknown"}\n`,
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
runMain("make-manifest", main);
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# memory-save.sh - v6.2.E
|
|
3
3
|
#
|
|
4
4
|
# Writes a single per-repo memory file and updates the MEMORY.md index.
|
|
5
|
-
# Called by Phase
|
|
5
|
+
# Called by Phase 5's synthesis step (or any user skill that wants to
|
|
6
6
|
# persist a finding). Opt-in via prefs.global.perRepoMemory.
|
|
7
7
|
#
|
|
8
8
|
# Usage:
|
|
@@ -14,12 +14,30 @@ import fs from "node:fs";
|
|
|
14
14
|
import path from "node:path";
|
|
15
15
|
import os from "node:os";
|
|
16
16
|
import { fileURLToPath } from "node:url";
|
|
17
|
+
import { runMain } from "../lib/fatal.mjs";
|
|
17
18
|
|
|
18
19
|
const DEFAULT_FILE = path.join(os.homedir(), ".claude", "multi-agent-preferences.json");
|
|
19
20
|
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
|
|
21
|
+
// The migration target, read from the schema rather than written here. It was
|
|
22
|
+
// a literal, and a literal is how it came to say 2.6.0 while the schema had
|
|
23
|
+
// moved to 2.7.0 - the runner then reported every current file as needing a
|
|
24
|
+
// migration it had already had. Referenced by the migrate() bump chain AND
|
|
25
|
+
// every user-facing message, so they cannot drift from each other either.
|
|
26
|
+
const TARGET_VERSION = (() => {
|
|
27
|
+
const schemaPath = path.join(
|
|
28
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
29
|
+
"..",
|
|
30
|
+
"schemas",
|
|
31
|
+
"prefs.schema.json",
|
|
32
|
+
);
|
|
33
|
+
const sv = JSON.parse(fs.readFileSync(schemaPath, "utf-8"))?.properties?.schemaVersion;
|
|
34
|
+
const v = sv?.const ?? sv?.enum?.at(-1);
|
|
35
|
+
if (!v) {
|
|
36
|
+
console.error("migrate-prefs: prefs.schema.json declares no schemaVersion");
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
return v;
|
|
40
|
+
})();
|
|
23
41
|
|
|
24
42
|
const USAGE = "Usage: migrate-prefs.mjs [--dry-run] [--file <path>]";
|
|
25
43
|
|
|
@@ -326,7 +344,7 @@ function migrate(prefs) {
|
|
|
326
344
|
out.global.routines = [];
|
|
327
345
|
changes.push("added empty routines (v2.4.0 user-defined /multi-agent:save routines)");
|
|
328
346
|
}
|
|
329
|
-
// v2.5.0 / v14.0.0: Phase
|
|
347
|
+
// v2.5.0 / v14.0.0: Phase 3 Review entered the --dev phase sets and Step 1.78
|
|
330
348
|
// (criteria resolution) landed. Default false because only Swift plus the two
|
|
331
349
|
// store-compliance catalogs ship a scoped registry today, so blocking on a
|
|
332
350
|
// coverage gap would stop every Kotlin/Python/Node/ObjC run from day one. The
|
|
@@ -487,7 +505,7 @@ function migrateReportChannels(out, changes) {
|
|
|
487
505
|
if (typeof g.autopilotReportTimeoutSeconds !== "number") {
|
|
488
506
|
g.autopilotReportTimeoutSeconds = 1800;
|
|
489
507
|
changes.push(
|
|
490
|
-
"added autopilotReportTimeoutSeconds: 1800 (30 min) - Phase
|
|
508
|
+
"added autopilotReportTimeoutSeconds: 1800 (30 min) - Phase 5 always-pause timeout",
|
|
491
509
|
);
|
|
492
510
|
}
|
|
493
511
|
|
|
@@ -544,4 +562,4 @@ function main() {
|
|
|
544
562
|
console.log(`✓ wrote migrated prefs to ${args.file}`);
|
|
545
563
|
}
|
|
546
564
|
|
|
547
|
-
main
|
|
565
|
+
runMain("migrate-prefs", main);
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
* node migrate-state.mjs --type=state <path> # force state type
|
|
14
14
|
* node migrate-state.mjs --type=prefs <path> # force prefs type
|
|
15
15
|
* node migrate-state.mjs --dry-run <path> # preview, no write
|
|
16
|
+
* node migrate-state.mjs --all # sweep every run on disk
|
|
17
|
+
* node migrate-state.mjs --all --dry-run # sweep, report only
|
|
16
18
|
*
|
|
17
19
|
* Exit codes:
|
|
18
20
|
* 0 - migration applied (or file already current)
|
|
@@ -56,20 +58,101 @@ function parseArgs() {
|
|
|
56
58
|
const args = process.argv.slice(2);
|
|
57
59
|
let type = null;
|
|
58
60
|
let dryRun = false;
|
|
61
|
+
let all = false;
|
|
59
62
|
let path = null;
|
|
60
63
|
for (const a of args) {
|
|
61
64
|
if (a.startsWith("--type=")) type = a.slice(7);
|
|
62
65
|
else if (a === "--dry-run") dryRun = true;
|
|
66
|
+
else if (a === "--all") all = true;
|
|
63
67
|
else if (a.startsWith("--")) {
|
|
64
68
|
console.error(`unknown flag: ${a}`);
|
|
65
69
|
process.exit(1);
|
|
66
70
|
} else path = a;
|
|
67
71
|
}
|
|
68
|
-
if (!path) {
|
|
69
|
-
console.error("usage: migrate-state.mjs [--type=state|prefs] [--dry-run] <path>");
|
|
72
|
+
if (!path && !all) {
|
|
73
|
+
console.error("usage: migrate-state.mjs [--type=state|prefs] [--dry-run] <path> | --all");
|
|
70
74
|
process.exit(1);
|
|
71
75
|
}
|
|
72
|
-
return { type, dryRun, path };
|
|
76
|
+
return { type, dryRun, all, path };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Sweep every run on disk and report what the migration chain can and cannot
|
|
81
|
+
* do with it.
|
|
82
|
+
*
|
|
83
|
+
* This exists because a per-file runner answers "is THIS file current" and
|
|
84
|
+
* nobody was asking it. Measured on a real install: 43 of 43 agent-state.json
|
|
85
|
+
* files carried no schemaVersion, so the whole schemas/migrations/ chain had
|
|
86
|
+
* never run on real data and nothing said so.
|
|
87
|
+
*
|
|
88
|
+
* A file with no schemaVersion is REPORTED, never stamped. Stamping it would
|
|
89
|
+
* assert that it conforms to a version nothing checked; validate-state.mjs
|
|
90
|
+
* already treats such files as resume-safe, which is the property that matters.
|
|
91
|
+
*
|
|
92
|
+
* @param {boolean} dryRun
|
|
93
|
+
* @returns {Promise<number>} process exit code
|
|
94
|
+
*/
|
|
95
|
+
async function sweepAll(dryRun) {
|
|
96
|
+
const { listRuns } = await import("./_run-paths.mjs");
|
|
97
|
+
const runs = listRuns();
|
|
98
|
+
let current = 0;
|
|
99
|
+
let migrated = 0;
|
|
100
|
+
let legacy = 0;
|
|
101
|
+
let unreadable = 0;
|
|
102
|
+
const legacyIds = [];
|
|
103
|
+
|
|
104
|
+
for (const run of runs) {
|
|
105
|
+
const file = join(run.dir, "agent-state.json");
|
|
106
|
+
if (!existsSync(file)) continue;
|
|
107
|
+
let doc;
|
|
108
|
+
try {
|
|
109
|
+
doc = JSON.parse(readFileSync(file, "utf-8"));
|
|
110
|
+
} catch {
|
|
111
|
+
unreadable++;
|
|
112
|
+
console.log(` ✗ ${run.taskId} unreadable JSON`);
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
const from = doc?.schemaVersion;
|
|
116
|
+
if (typeof from !== "string") {
|
|
117
|
+
legacy++;
|
|
118
|
+
legacyIds.push(run.taskId);
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (from === TARGET_VERSIONS.state) {
|
|
122
|
+
current++;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
const chain = resolveChain("state", from, TARGET_VERSIONS.state);
|
|
126
|
+
if (!chain) {
|
|
127
|
+
console.log(` ✗ ${run.taskId} no path ${from} → ${TARGET_VERSIONS.state}`);
|
|
128
|
+
unreadable++;
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
let next = doc;
|
|
132
|
+
for (const step of chain) {
|
|
133
|
+
const mod = await import(pathToFileURL(step.path).href);
|
|
134
|
+
next = mod.default(next);
|
|
135
|
+
}
|
|
136
|
+
if (!dryRun) writeFileSync(file, JSON.stringify(next, null, 2) + "\n");
|
|
137
|
+
migrated++;
|
|
138
|
+
console.log(
|
|
139
|
+
` ✓ ${run.taskId} ${from} → ${TARGET_VERSIONS.state}${dryRun ? " (dry run)" : ""}`,
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
console.log(
|
|
144
|
+
`\nmigrate-state --all: ${runs.length} runs, ${current} current, ` +
|
|
145
|
+
`${migrated} migrated${dryRun ? " (dry run)" : ""}, ${legacy} legacy-unstamped, ${unreadable} unreadable`,
|
|
146
|
+
);
|
|
147
|
+
if (legacy) {
|
|
148
|
+
console.log(
|
|
149
|
+
`\n${legacy} state file(s) predate schemaVersion stamping and are reported, not stamped -\n` +
|
|
150
|
+
`stamping would claim a conformance nothing verified. They stay resume-safe\n` +
|
|
151
|
+
`(validate-state.mjs tolerates them by design). New runs are stamped by write-state.mjs.\n` +
|
|
152
|
+
` ${legacyIds.slice(0, 8).join(", ")}${legacyIds.length > 8 ? ", ..." : ""}`,
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
return unreadable ? 1 : 0;
|
|
73
156
|
}
|
|
74
157
|
|
|
75
158
|
/**
|
|
@@ -139,6 +222,11 @@ function resolveChain(type, from, to) {
|
|
|
139
222
|
}
|
|
140
223
|
|
|
141
224
|
async function main() {
|
|
225
|
+
const parsed = parseArgs();
|
|
226
|
+
if (parsed.all) {
|
|
227
|
+
process.exit(await sweepAll(parsed.dryRun));
|
|
228
|
+
}
|
|
229
|
+
|
|
142
230
|
const { type: forced, dryRun, path } = parseArgs();
|
|
143
231
|
|
|
144
232
|
if (!existsSync(path)) {
|
|
@@ -199,9 +287,11 @@ async function main() {
|
|
|
199
287
|
}
|
|
200
288
|
|
|
201
289
|
if (dryRun) {
|
|
290
|
+
// return, not process.exit(0): a state file is the payload here, and exiting
|
|
291
|
+
// truncates it when stdout is a pipe - which is how a dry run gets reviewed.
|
|
202
292
|
console.log("--- dry run, not writing ---");
|
|
203
293
|
console.log(JSON.stringify(current, null, 2));
|
|
204
|
-
|
|
294
|
+
return;
|
|
205
295
|
}
|
|
206
296
|
|
|
207
297
|
writeFileSync(path, JSON.stringify(current, null, 2) + "\n");
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
# phase-banner.sh label <phase> (print canonical name)
|
|
13
13
|
#
|
|
14
14
|
# Examples:
|
|
15
|
-
# phase-banner.sh start
|
|
16
|
-
# phase-banner.sh start
|
|
17
|
-
# PHASE_LANG=tr phase-banner.sh start
|
|
15
|
+
# phase-banner.sh start 3 "Review" "parallel + triage + user test"
|
|
16
|
+
# phase-banner.sh start 3 auto # resolves to "Review" (en) or "İnceleme" (tr)
|
|
17
|
+
# PHASE_LANG=tr phase-banner.sh start 3 auto # "Faz 3: İnceleme"
|
|
18
18
|
#
|
|
19
19
|
# Status values for `end`: done | failed | skipped
|
|
20
20
|
#
|
|
@@ -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
|
|
|
@@ -31,28 +32,28 @@ set -uo pipefail
|
|
|
31
32
|
|
|
32
33
|
PHASE_LANG="${PHASE_LANG:-en}"
|
|
33
34
|
|
|
34
|
-
# Canonical phase label table.
|
|
35
|
-
# pipeline/
|
|
36
|
-
#
|
|
35
|
+
# Canonical phase label table. The English half is checked against
|
|
36
|
+
# pipeline/schemas/phases.json by smoke-phase-contract.sh, so it cannot drift
|
|
37
|
+
# from the contract in silence the way it did through the eight-phase era. It
|
|
38
|
+
# stays a case statement rather than a jq read because this runs on every
|
|
39
|
+
# banner and the pipeline's zero-dependency rule (ADR-0004) applies to the hot
|
|
40
|
+
# path. The Turkish half has no contract entry - labels are translations, not
|
|
41
|
+
# identifiers - and is kept beside it.
|
|
37
42
|
phase_label() {
|
|
38
43
|
local phase="$1" lang="${2:-en}"
|
|
39
44
|
case "$lang:$phase" in
|
|
40
45
|
en:0) echo "Init" ;;
|
|
41
|
-
en:1) echo "
|
|
42
|
-
en:2) echo "
|
|
43
|
-
en:3) echo "
|
|
44
|
-
en:4) echo "
|
|
45
|
-
en:5) echo "
|
|
46
|
-
en:6) echo "Commit & PR" ;;
|
|
47
|
-
en:7) echo "Report" ;;
|
|
46
|
+
en:1) echo "Plan" ;;
|
|
47
|
+
en:2) echo "Dev" ;;
|
|
48
|
+
en:3) echo "Review" ;;
|
|
49
|
+
en:4) echo "Commit & PR" ;;
|
|
50
|
+
en:5) echo "Report" ;;
|
|
48
51
|
tr:0) echo "Başlangıç" ;;
|
|
49
|
-
tr:1) echo "
|
|
50
|
-
tr:2) echo "
|
|
51
|
-
tr:3) echo "
|
|
52
|
-
tr:4) echo "
|
|
53
|
-
tr:5) echo "
|
|
54
|
-
tr:6) echo "Commit & PR" ;;
|
|
55
|
-
tr:7) echo "Rapor" ;;
|
|
52
|
+
tr:1) echo "Plan" ;;
|
|
53
|
+
tr:2) echo "Geliştirme" ;;
|
|
54
|
+
tr:3) echo "İnceleme" ;;
|
|
55
|
+
tr:4) echo "Commit & PR" ;;
|
|
56
|
+
tr:5) echo "Rapor" ;;
|
|
56
57
|
*) echo "" ;;
|
|
57
58
|
esac
|
|
58
59
|
}
|
|
@@ -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'
|