@mmerterden/multi-agent-pipeline 12.7.0 → 12.9.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 +207 -0
- package/install/_common.mjs +48 -0
- package/install/_dev-only-files.mjs +125 -5
- package/install/claude.mjs +14 -8
- package/install/copilot.mjs +5 -8
- package/package.json +17 -2
- package/pipeline/commands/multi-agent/analysis/SKILL.md +1 -1
- package/pipeline/lib/credential-store.sh +20 -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/_repo-picker.md +1 -1
- package/pipeline/multi-agent-refs/features/review-multi-repo.md +65 -0
- package/pipeline/multi-agent-refs/features/url-enrichment.md +93 -0
- package/pipeline/multi-agent-refs/phases/operations.md +28 -0
- package/pipeline/multi-agent-refs/phases/phase-0-init.md +3 -83
- package/pipeline/multi-agent-refs/phases/phase-2-planning.md +1 -1
- package/pipeline/multi-agent-refs/phases/phase-3-dev.md +1 -2
- package/pipeline/multi-agent-refs/phases/phase-4-review.md +50 -62
- package/pipeline/schemas/prefs.schema.json +6 -0
- package/pipeline/scripts/_smoke-root.sh +61 -0
- package/pipeline/scripts/agent-guard.py +57 -3
- package/pipeline/scripts/audit-log.sh +25 -0
- package/pipeline/scripts/smoke-cross-cli-behavior.sh +21 -7
- package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/expected/phase-1-analysis.json +0 -25
- package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/expected/phase-2-plan.json +0 -30
- package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/expected/phase-4-review.json +0 -20
- package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/expected/phase-4-triage.json +0 -15
- package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/metadata.json +0 -14
- package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/task.json +0 -12
- package/pipeline/eval/golden-tasks/02-android-feature-compose/expected/phase-1-analysis.json +0 -29
- package/pipeline/eval/golden-tasks/02-android-feature-compose/expected/phase-2-plan.json +0 -43
- package/pipeline/eval/golden-tasks/02-android-feature-compose/expected/phase-4-review.json +0 -35
- package/pipeline/eval/golden-tasks/02-android-feature-compose/expected/phase-4-triage.json +0 -35
- package/pipeline/eval/golden-tasks/02-android-feature-compose/metadata.json +0 -14
- package/pipeline/eval/golden-tasks/02-android-feature-compose/task.json +0 -12
- package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/expected/phase-1-analysis.json +0 -29
- package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/expected/phase-2-plan.json +0 -42
- package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/expected/phase-4-review.json +0 -20
- package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/expected/phase-4-triage.json +0 -15
- package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/metadata.json +0 -14
- package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/task.json +0 -12
- package/pipeline/eval/golden-tasks/04-frontend-next-hydration/expected/phase-1-analysis.json +0 -29
- package/pipeline/eval/golden-tasks/04-frontend-next-hydration/expected/phase-2-plan.json +0 -40
- package/pipeline/eval/golden-tasks/04-frontend-next-hydration/expected/phase-4-review.json +0 -20
- package/pipeline/eval/golden-tasks/04-frontend-next-hydration/expected/phase-4-triage.json +0 -15
- package/pipeline/eval/golden-tasks/04-frontend-next-hydration/metadata.json +0 -14
- package/pipeline/eval/golden-tasks/04-frontend-next-hydration/task.json +0 -12
- package/pipeline/eval/golden-tasks/05-ios-security-keychain/expected/phase-1-analysis.json +0 -29
- package/pipeline/eval/golden-tasks/05-ios-security-keychain/expected/phase-2-plan.json +0 -42
- package/pipeline/eval/golden-tasks/05-ios-security-keychain/expected/phase-4-review.json +0 -28
- package/pipeline/eval/golden-tasks/05-ios-security-keychain/expected/phase-4-triage.json +0 -27
- package/pipeline/eval/golden-tasks/05-ios-security-keychain/metadata.json +0 -14
- package/pipeline/eval/golden-tasks/05-ios-security-keychain/task.json +0 -12
- package/pipeline/eval/golden-tasks/06-android-refactor-usecase/expected/phase-1-analysis.json +0 -29
- package/pipeline/eval/golden-tasks/06-android-refactor-usecase/expected/phase-2-plan.json +0 -41
- package/pipeline/eval/golden-tasks/06-android-refactor-usecase/expected/phase-4-review.json +0 -12
- package/pipeline/eval/golden-tasks/06-android-refactor-usecase/expected/phase-4-triage.json +0 -6
- package/pipeline/eval/golden-tasks/06-android-refactor-usecase/metadata.json +0 -14
- package/pipeline/eval/golden-tasks/06-android-refactor-usecase/task.json +0 -12
- package/pipeline/eval/golden-tasks/07-backend-node-idempotency/expected/phase-1-analysis.json +0 -29
- package/pipeline/eval/golden-tasks/07-backend-node-idempotency/expected/phase-2-plan.json +0 -42
- package/pipeline/eval/golden-tasks/07-backend-node-idempotency/expected/phase-4-review.json +0 -28
- package/pipeline/eval/golden-tasks/07-backend-node-idempotency/expected/phase-4-triage.json +0 -27
- package/pipeline/eval/golden-tasks/07-backend-node-idempotency/metadata.json +0 -14
- package/pipeline/eval/golden-tasks/07-backend-node-idempotency/task.json +0 -12
- package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/expected/phase-1-analysis.json +0 -25
- package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/expected/phase-2-plan.json +0 -31
- package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/expected/phase-4-review.json +0 -12
- package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/expected/phase-4-triage.json +0 -18
- package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/metadata.json +0 -14
- package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/task.json +0 -12
- package/pipeline/eval/golden-tasks/README.md +0 -65
- package/pipeline/eval/intent-cases.json +0 -40
- package/pipeline/eval/run-metrics-fixture.json +0 -93
- package/pipeline/eval/triage/01-empty-findings/expected.json +0 -6
- package/pipeline/eval/triage/01-empty-findings/input.json +0 -5
- package/pipeline/eval/triage/01-empty-findings/notes.md +0 -7
- package/pipeline/eval/triage/02-real-blocker/expected.json +0 -15
- package/pipeline/eval/triage/02-real-blocker/input.json +0 -14
- package/pipeline/eval/triage/02-real-blocker/notes.md +0 -7
- package/pipeline/eval/triage/03-out-of-scope-defer/expected.json +0 -18
- package/pipeline/eval/triage/03-out-of-scope-defer/input.json +0 -14
- package/pipeline/eval/triage/03-out-of-scope-defer/notes.md +0 -10
- package/pipeline/eval/triage/04-false-positive-reject/expected.json +0 -18
- package/pipeline/eval/triage/04-false-positive-reject/input.json +0 -14
- package/pipeline/eval/triage/04-false-positive-reject/notes.md +0 -10
- package/pipeline/eval/triage/05-mixed-classification/expected.json +0 -43
- package/pipeline/eval/triage/05-mixed-classification/input.json +0 -38
- package/pipeline/eval/triage/05-mixed-classification/notes.md +0 -17
- package/pipeline/eval/triage/06-severity-mismatch/expected.json +0 -15
- package/pipeline/eval/triage/06-severity-mismatch/input.json +0 -14
- package/pipeline/eval/triage/06-severity-mismatch/notes.md +0 -9
- package/pipeline/eval/triage/07-duplicate-reviewers/expected.json +0 -27
- package/pipeline/eval/triage/07-duplicate-reviewers/input.json +0 -22
- package/pipeline/eval/triage/07-duplicate-reviewers/notes.md +0 -9
- package/pipeline/eval/triage/08-style-misclassified/expected.json +0 -18
- package/pipeline/eval/triage/08-style-misclassified/input.json +0 -14
- package/pipeline/eval/triage/08-style-misclassified/notes.md +0 -9
- package/pipeline/eval/triage/09-cascading-finding/expected.json +0 -23
- package/pipeline/eval/triage/09-cascading-finding/input.json +0 -22
- package/pipeline/eval/triage/09-cascading-finding/notes.md +0 -9
- package/pipeline/eval/triage/10-deferred-crossref/expected.json +0 -18
- package/pipeline/eval/triage/10-deferred-crossref/input.json +0 -14
- package/pipeline/eval/triage/10-deferred-crossref/notes.md +0 -9
- package/pipeline/eval/triage/11-vercel-token-leak-blocker/expected.json +0 -27
- package/pipeline/eval/triage/11-vercel-token-leak-blocker/input.json +0 -22
- package/pipeline/eval/triage/11-vercel-token-leak-blocker/notes.md +0 -14
- package/pipeline/eval/triage/README.md +0 -54
- package/pipeline/scripts/benchmark-phase-0.sh +0 -128
- package/pipeline/scripts/check-md-links.mjs +0 -88
- package/pipeline/scripts/eval-golden-tasks-live.mjs +0 -302
- package/pipeline/scripts/eval-golden-tasks.mjs +0 -224
- package/pipeline/scripts/eval-intent.mjs +0 -107
- package/pipeline/scripts/eval-mine-corpus.mjs +0 -211
- package/pipeline/scripts/eval-triage.mjs +0 -171
- package/pipeline/scripts/fixtures/diff-risk-android.diff +0 -40
- package/pipeline/scripts/fixtures/diff-risk-ios.diff +0 -48
- package/pipeline/scripts/fixtures/diff-risk-test-removal.diff +0 -40
- package/pipeline/scripts/fixtures/install-layout.tsv +0 -19
- package/pipeline/scripts/fixtures/pack-expected-count.txt +0 -1
- package/pipeline/scripts/fixtures/test-gap-node.diff +0 -30
- package/pipeline/scripts/fixtures/test-gap-python.diff +0 -32
- package/pipeline/scripts/lint-mcp-refs.mjs +0 -218
- package/pipeline/scripts/lint-skills.mjs +0 -154
- package/pipeline/scripts/run-smokes.mjs +0 -130
- package/pipeline/scripts/scorecard.mjs +0 -258
- package/pipeline/scripts/smoke-add-detail.sh +0 -137
- package/pipeline/scripts/smoke-agent-guard.sh +0 -74
- package/pipeline/scripts/smoke-agent-log-cost.sh +0 -262
- package/pipeline/scripts/smoke-agent-model-routing.sh +0 -87
- package/pipeline/scripts/smoke-ask-choice.sh +0 -42
- package/pipeline/scripts/smoke-autopilot-circuit-breaker.sh +0 -36
- package/pipeline/scripts/smoke-bitbucket-contract.sh +0 -255
- package/pipeline/scripts/smoke-changelog-version.sh +0 -47
- package/pipeline/scripts/smoke-channels-approval-gate.sh +0 -60
- package/pipeline/scripts/smoke-channels-flow.sh +0 -130
- package/pipeline/scripts/smoke-ci-workflows.sh +0 -88
- package/pipeline/scripts/smoke-clarify.sh +0 -148
- package/pipeline/scripts/smoke-command-inventory.sh +0 -81
- package/pipeline/scripts/smoke-commands-skills-parity.sh +0 -87
- package/pipeline/scripts/smoke-community-gates.sh +0 -75
- package/pipeline/scripts/smoke-compliance-skills.sh +0 -119
- package/pipeline/scripts/smoke-config-hygiene.sh +0 -58
- package/pipeline/scripts/smoke-context-budget.sh +0 -72
- package/pipeline/scripts/smoke-cost-budget.sh +0 -70
- package/pipeline/scripts/smoke-cost-summary.sh +0 -139
- package/pipeline/scripts/smoke-cross-phase-cohesion.sh +0 -128
- package/pipeline/scripts/smoke-description-tr.sh +0 -82
- package/pipeline/scripts/smoke-dev-critic.sh +0 -144
- package/pipeline/scripts/smoke-diff-explain.sh +0 -147
- package/pipeline/scripts/smoke-diff-risk.sh +0 -190
- package/pipeline/scripts/smoke-dynamic-skill-loading.sh +0 -160
- package/pipeline/scripts/smoke-eval-live.sh +0 -136
- package/pipeline/scripts/smoke-evidence-gate.sh +0 -93
- package/pipeline/scripts/smoke-extract-conventions.sh +0 -163
- package/pipeline/scripts/smoke-fetchers-offline.sh +0 -448
- package/pipeline/scripts/smoke-figma-dispatch.sh +0 -112
- package/pipeline/scripts/smoke-gate-hooks.sh +0 -74
- package/pipeline/scripts/smoke-gc-tmp.sh +0 -130
- package/pipeline/scripts/smoke-gc-worktrees.sh +0 -125
- package/pipeline/scripts/smoke-generate-issue.sh +0 -120
- package/pipeline/scripts/smoke-handoff-contract.sh +0 -92
- package/pipeline/scripts/smoke-identity-isolation.sh +0 -70
- package/pipeline/scripts/smoke-install-layout.sh +0 -248
- package/pipeline/scripts/smoke-intent-guard.sh +0 -86
- package/pipeline/scripts/smoke-issue-comment-template.sh +0 -86
- package/pipeline/scripts/smoke-issue-jira-triad.sh +0 -120
- package/pipeline/scripts/smoke-keychain.sh +0 -158
- package/pipeline/scripts/smoke-language-axis.sh +0 -109
- package/pipeline/scripts/smoke-learning-curve.sh +0 -61
- package/pipeline/scripts/smoke-learnings-ledger.sh +0 -86
- package/pipeline/scripts/smoke-lib-scripts.sh +0 -448
- package/pipeline/scripts/smoke-mcp-gate.sh +0 -68
- package/pipeline/scripts/smoke-md-links.sh +0 -8
- package/pipeline/scripts/smoke-md2confluence.sh +0 -126
- package/pipeline/scripts/smoke-metrics-cache-ratio.sh +0 -72
- package/pipeline/scripts/smoke-migrate-state.sh +0 -102
- package/pipeline/scripts/smoke-mode-dispatch-drift.sh +0 -161
- package/pipeline/scripts/smoke-model-fallback.sh +0 -89
- package/pipeline/scripts/smoke-multi-repo-integration.sh +0 -116
- package/pipeline/scripts/smoke-multi-repo-worktree.sh +0 -61
- package/pipeline/scripts/smoke-no-mcp-in-dev-phases.sh +0 -194
- package/pipeline/scripts/smoke-no-token-prompt.sh +0 -85
- package/pipeline/scripts/smoke-own-punctuation.sh +0 -103
- package/pipeline/scripts/smoke-pack-contents.sh +0 -140
- package/pipeline/scripts/smoke-pat-audit.sh +0 -128
- package/pipeline/scripts/smoke-per-repo-memory.sh +0 -156
- package/pipeline/scripts/smoke-phase-0-multi-repo.sh +0 -170
- package/pipeline/scripts/smoke-phase-6-multi.sh +0 -79
- package/pipeline/scripts/smoke-phase-banner.sh +0 -101
- package/pipeline/scripts/smoke-phase-tracker.sh +0 -324
- package/pipeline/scripts/smoke-phase0-bridge-contract.sh +0 -241
- package/pipeline/scripts/smoke-phase4-gates.sh +0 -45
- package/pipeline/scripts/smoke-phase4-triage.sh +0 -229
- package/pipeline/scripts/smoke-plan-approval-gate.sh +0 -71
- package/pipeline/scripts/smoke-plan-safety.sh +0 -139
- package/pipeline/scripts/smoke-plan-todos.sh +0 -196
- package/pipeline/scripts/smoke-pr-review-actions.sh +0 -152
- package/pipeline/scripts/smoke-pre-commit.sh +0 -170
- package/pipeline/scripts/smoke-pref-migration.sh +0 -226
- package/pipeline/scripts/smoke-prefs-language.sh +0 -134
- package/pipeline/scripts/smoke-progress-contract.sh +0 -127
- package/pipeline/scripts/smoke-prune-logs.sh +0 -137
- package/pipeline/scripts/smoke-purge.sh +0 -138
- package/pipeline/scripts/smoke-push-retry.sh +0 -75
- package/pipeline/scripts/smoke-repo-map.sh +0 -300
- package/pipeline/scripts/smoke-review-readiness.sh +0 -92
- package/pipeline/scripts/smoke-review-watch.sh +0 -146
- package/pipeline/scripts/smoke-routines.sh +0 -84
- package/pipeline/scripts/smoke-run-aggregator.sh +0 -216
- package/pipeline/scripts/smoke-run-metrics.sh +0 -50
- package/pipeline/scripts/smoke-search.sh +0 -187
- package/pipeline/scripts/smoke-shadow-git.sh +0 -224
- package/pipeline/scripts/smoke-skill-authoring.sh +0 -137
- package/pipeline/scripts/smoke-skill-language.sh +0 -83
- package/pipeline/scripts/smoke-skill-manifest.sh +0 -138
- package/pipeline/scripts/smoke-skill-scan.sh +0 -198
- package/pipeline/scripts/smoke-source-parity.sh +0 -85
- package/pipeline/scripts/smoke-subagent-validators.sh +0 -108
- package/pipeline/scripts/smoke-sync-parity.sh +0 -92
- package/pipeline/scripts/smoke-tasklist-ordering.sh +0 -112
- package/pipeline/scripts/smoke-telemetry.sh +0 -147
- package/pipeline/scripts/smoke-test-gap.sh +0 -183
- package/pipeline/scripts/smoke-token-budget.sh +0 -67
- package/pipeline/scripts/smoke-token-preflight.sh +0 -82
- package/pipeline/scripts/smoke-tracker-contract.sh +0 -191
- package/pipeline/scripts/smoke-tracker-tokens-invocation.sh +0 -73
- package/pipeline/scripts/smoke-triage-memory.sh +0 -174
- package/pipeline/scripts/smoke-update-check.sh +0 -135
- package/pipeline/scripts/smoke-url-enrichment.sh +0 -70
- package/pipeline/scripts/smoke-validate-analysis-doc.sh +0 -161
- package/pipeline/scripts/smoke-validator-contradiction.sh +0 -67
- package/pipeline/scripts/smoke-validator-gates.sh +0 -164
- package/pipeline/scripts/smoke-vercel-deploy-redact.sh +0 -129
- package/pipeline/scripts/smoke-verify-by-test.sh +0 -148
- package/pipeline/scripts/smoke-wiki-integration.sh +0 -122
- package/pipeline/scripts/smoke-work-summary.sh +0 -163
- package/pipeline/scripts/smoke-workflow-audit.sh +0 -101
- package/pipeline/scripts/smoke-worktree-path-convention.sh +0 -86
- package/pipeline/scripts/smoke-wrapper-preservation.sh +0 -68
- package/pipeline/scripts/smoke-write-state.sh +0 -159
- package/pipeline/scripts/sync-parity-check.sh +0 -135
- package/pipeline/scripts/test-gap-rules/android.json +0 -25
- package/pipeline/scripts/test-gap-rules/ios.json +0 -34
- package/pipeline/scripts/test-gap-rules/node.json +0 -29
- package/pipeline/scripts/test-gap-rules/python.json +0 -25
- package/pipeline/scripts/validate-schemas.mjs +0 -88
package/CHANGELOG.md
CHANGED
|
@@ -16,6 +16,213 @@ Internal file-layout changes that don't affect the slash-command surface are sti
|
|
|
16
16
|
|
|
17
17
|
## [Unreleased]
|
|
18
18
|
|
|
19
|
+
## [12.9.0] - 2026-07-26
|
|
20
|
+
|
|
21
|
+
Context engineering follow-through, and a guard that was blocking safe commands.
|
|
22
|
+
|
|
23
|
+
### Phase docs load their conditional sections on demand
|
|
24
|
+
|
|
25
|
+
The two biggest phase docs carried the UNION of every branch, so a run paid for
|
|
26
|
+
paths it never took. Both were over their own warn thresholds.
|
|
27
|
+
|
|
28
|
+
- `phase-0-init.md` Step 1b (URL Enrichment, 9.6 kB) applies only when the task
|
|
29
|
+
input carries URLs. A bare Jira ID, a GitHub issue number or a free-text task
|
|
30
|
+
never needs it. Moved to `multi-agent-refs/features/url-enrichment.md` behind a
|
|
31
|
+
761-byte stub that states the run condition. Phase 0: 11701 -> 9469 tokens.
|
|
32
|
+
- `phase-4-review.md` Multi-Repo Mode (3.0 kB) applies only when
|
|
33
|
+
`state.projects[].length > 1`. Moved to `features/review-multi-repo.md`.
|
|
34
|
+
Phase 4: 12070 -> 11048 tokens.
|
|
35
|
+
|
|
36
|
+
Total phase-doc budget 50955 -> 48047 tokens, and both docs are now under warn
|
|
37
|
+
rather than over. `smoke-url-enrichment.sh` follows the content to the ref and
|
|
38
|
+
additionally asserts the phase doc still POINTS at it: a JIT reference nothing
|
|
39
|
+
loads is worse than the inline copy it replaced.
|
|
40
|
+
|
|
41
|
+
### The always-on description surface is budgeted
|
|
42
|
+
|
|
43
|
+
Progressive disclosure defers reference BODIES, but a skill or command
|
|
44
|
+
`description` sits in the host's listing for the whole session whether or not the
|
|
45
|
+
skill is ever invoked. That is a second fixed load, it was LARGER than the
|
|
46
|
+
budgeted one (76918 bytes across 236 descriptions), and nothing capped it.
|
|
47
|
+
`smoke-context-budget.sh` now gates the total and the per-description average
|
|
48
|
+
(the total can stay under budget while every description bloats), reports the
|
|
49
|
+
whole pre-task cost as one number, and `test/context-budget-gate.test.mjs` pins
|
|
50
|
+
all three ceilings as literals.
|
|
51
|
+
|
|
52
|
+
### No prose block loads twice
|
|
53
|
+
|
|
54
|
+
`smoke-context-duplication.sh` hashes normalised prose blocks across the files
|
|
55
|
+
that can co-load in one run. 12.7.0 removed three sections the orchestrator had
|
|
56
|
+
inlined from `multi-agent-refs/` - one already stale, naming a path the code no
|
|
57
|
+
longer used - found by hand with nothing to prevent a recurrence.
|
|
58
|
+
|
|
59
|
+
Scope mattered more than the algorithm. A first draft scanned the command and
|
|
60
|
+
skill trees too and reported 10+ duplicates, every one legitimate: the
|
|
61
|
+
`commands/multi-agent/X` vs `skills/shared/core/multi-agent-X` pairs are the
|
|
62
|
+
cross-CLI parity contract, which `cross-cli-contract.md` REQUIRES byte-identical,
|
|
63
|
+
and the mode commands repeat prose but only one loads per run. A gate that fires
|
|
64
|
+
on deliberate design trains people to ignore it. Narrowed to the ref tree plus the
|
|
65
|
+
orchestrator, it found one real case: three pickers each restating the Language
|
|
66
|
+
matrix that `rules.md` already carries on every run - and the copies had already
|
|
67
|
+
drifted, two saying "Commit/PR/Jira/Wiki" and one "Commit/PR/Jira". Replaced with
|
|
68
|
+
a one-line pointer, 678 bytes reclaimed.
|
|
69
|
+
|
|
70
|
+
### agent-guard stopped blocking safe pushes
|
|
71
|
+
|
|
72
|
+
The force-push detector ended in `-\S*f\S*`, matching any flag containing an `f`:
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
git pull --ff-only && git push origin main -> BLOCKED
|
|
76
|
+
git push --follow-tags origin main -> BLOCKED
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Neither rewrites history, and `--follow-tags` is the normal way to push a release
|
|
80
|
+
tag with its commit, so the guard blocked a routine release step. False positives
|
|
81
|
+
are not free: people learn to route around the hook, which loses more safety than
|
|
82
|
+
the guard buys. The short-flag arm is now `-[A-Za-z]*f[A-Za-z]*` (a single dash
|
|
83
|
+
followed only by letters, so `-f`/`-fq` still match and no `--long-flag` can), and
|
|
84
|
+
force detection is scoped to the `git push` segment so a neighbouring command's
|
|
85
|
+
`-f` no longer implicates it (`rm -f stale.log && git push origin main`).
|
|
86
|
+
|
|
87
|
+
Fail-closed behaviour is unchanged and now asserted: an untokenizable command, and
|
|
88
|
+
a bare force-push whose target branch cannot be determined, still block.
|
|
89
|
+
`test/agent-guard.test.mjs` covers 30 cases across both directions.
|
|
90
|
+
|
|
91
|
+
### Also
|
|
92
|
+
|
|
93
|
+
- Trees an older installer created and no current one manages are pruned on
|
|
94
|
+
install (`pruneAbandonedTrees`). `~/.claude/eval/` held 31 files that no
|
|
95
|
+
installer wrote and no uninstall removed - wipe-before-copy only protects trees
|
|
96
|
+
still being written, so an abandoned one persists forever.
|
|
97
|
+
- shellcheck in CI scans `git ls-files '*.sh'` instead of `find pipeline`, so a
|
|
98
|
+
shell script added outside `pipeline/` is no longer silently unchecked.
|
|
99
|
+
|
|
100
|
+
## [12.8.0] - 2026-07-26
|
|
101
|
+
|
|
102
|
+
12.7.0 fixed nine gates that reported success without checking anything. This
|
|
103
|
+
release is the level above that: gates that check correctly and never run, and a
|
|
104
|
+
shipped surface that could not work where it was shipped to.
|
|
105
|
+
|
|
106
|
+
### 117 maintainer smokes stopped shipping to users
|
|
107
|
+
|
|
108
|
+
`pipeline/scripts/` was copied wholesale into `~/.claude/scripts/`, so every
|
|
109
|
+
repository CI gate landed on every user's machine. Measured from a real install:
|
|
110
|
+
of 117 installed smoke scripts, **100 failed** and 17 passed only by accident of
|
|
111
|
+
not needing the repo. They resolve the repo root as `dirname($0)/../..`, which in
|
|
112
|
+
an install layout is `$HOME`, and then read `package.json` / `install.js` /
|
|
113
|
+
`node_modules`. What a user saw:
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
$ bash ~/.claude/scripts/smoke-install-layout.sh
|
|
117
|
+
FAIL: install.js missing
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
A green repo gate, run in the wrong place, indistinguishable from a broken
|
|
121
|
+
installation. Worse, `/multi-agent:update` actively instructed users to run
|
|
122
|
+
`smoke-cross-cli-behavior.sh`, which reported "3 failed" for the same reason.
|
|
123
|
+
|
|
124
|
+
The default for `smoke-*.sh` is now EXCLUDE, in both the install and the tarball.
|
|
125
|
+
A smoke ships only when a command actually invokes it - `CONSUMER_SMOKES` in
|
|
126
|
+
`install/_dev-only-files.mjs`, currently the two that `/multi-agent:update` runs.
|
|
127
|
+
Both now resolve paths through the new `pipeline/scripts/_smoke-root.sh`, which
|
|
128
|
+
handles the repo and install layouts, so `smoke-cross-cli-behavior.sh` reports
|
|
129
|
+
20/20 from an install instead of 17/20.
|
|
130
|
+
|
|
131
|
+
Also withheld: `run-smokes.mjs`, `eval-*.mjs`, `pipeline/eval/**`, `scorecard.mjs`,
|
|
132
|
+
the linters, `validate-schemas.mjs`, and the fixture corpora - all of them need
|
|
133
|
+
devDependencies or the repo tree. Install drops from 201 files to 73; the tarball
|
|
134
|
+
from 977 to 753.
|
|
135
|
+
|
|
136
|
+
`smoke-consumer-smoke-surface.sh` holds four invariants: the allowlist equals what
|
|
137
|
+
commands invoke (drift either way fails), every consumer smoke uses the shared
|
|
138
|
+
resolver, no maintainer smoke survives the install filter, and none survives into
|
|
139
|
+
the tarball. `smoke-pack-contents.sh` now evaluates `files[]` the way npm does
|
|
140
|
+
(last matching pattern wins, so a broad negation may be followed by a narrower
|
|
141
|
+
re-inclusion - verified against npm directly) and reports an inert negation that
|
|
142
|
+
guards nothing. `smoke-install-leak-gate.sh` step 4 previously asserted the
|
|
143
|
+
OPPOSITE contract, naming three "essential" smokes that had to install; it now
|
|
144
|
+
derives the expected set from `CONSUMER_SMOKES`.
|
|
145
|
+
|
|
146
|
+
Fixed along the way: both installers computed the excluded-file count by summing
|
|
147
|
+
`countFiles()` over the exclusion array, which returns 0 for every non-literal
|
|
148
|
+
entry, so the count would have under-reported by 100+ files.
|
|
149
|
+
|
|
150
|
+
### Four gates that were tested, green, and never ran
|
|
151
|
+
|
|
152
|
+
Every gate in this repo checks its own behaviour. Nothing checked that a gate is
|
|
153
|
+
*reachable*. Four were not - each with a passing unit test and a passing smoke,
|
|
154
|
+
and no reference from `pipeline/commands/`, `multi-agent-refs/`, `agents/`,
|
|
155
|
+
`skills/` or `install/`:
|
|
156
|
+
|
|
157
|
+
- **`test-integrity-gate.mjs`** - the anti-reward-hacking control for a suite made
|
|
158
|
+
green by deleting tests. Now Phase 4 Step 1.76, emitting blocking findings that
|
|
159
|
+
merge into the reviewer findings at Step 3.0 (before the no-findings
|
|
160
|
+
short-circuit, which would otherwise skip triage entirely on a diff whose only
|
|
161
|
+
finding is a deleted test). No opt-out: a run that can switch off its own
|
|
162
|
+
anti-reward-hacking control cannot be trusted to report a pass.
|
|
163
|
+
- **`review-scope.mjs`** - the reviewer-count cost gate. Now Phase 4 Step 1.77,
|
|
164
|
+
behind `prefs.global.reviewScopeGate` (default true). Every diff had been paying
|
|
165
|
+
for the full reviewer set.
|
|
166
|
+
- **`audit-log.sh`** - the PAT-lookup audit trail. `.gitignore` carried defensive
|
|
167
|
+
globs for it and `/multi-agent:prune-logs` documented managing it, but
|
|
168
|
+
`credential-store.sh` never called it, so the trail was always empty. Now
|
|
169
|
+
written on every `get`, on both the success and the miss path, through the
|
|
170
|
+
Python delegate and the raw-shell path. Since the trail now actually grows,
|
|
171
|
+
`audit-log.sh` self-rotates past `AUDIT_ROTATE_AT_BYTES` (1 MB) rather than
|
|
172
|
+
depending on a launchd/cron install nothing ever told a user to set up.
|
|
173
|
+
- **`write-state.mjs`** - the atomic `agent-state.json` writer with an advisory
|
|
174
|
+
lock, written for exactly the multi-repo race the phase docs create by sharing
|
|
175
|
+
one state file across worktrees. `operations.md` now documents it as the
|
|
176
|
+
required mechanism, with the exit codes a caller must handle, and
|
|
177
|
+
`phase-0-init.md` points at it where the shared file is introduced.
|
|
178
|
+
|
|
179
|
+
Phase 4 Step 1.75 now scores the diff **once without `--top`**: both new gates
|
|
180
|
+
need every scored file (a shrinking test file ranked 20th; whether any file is
|
|
181
|
+
high-stakes), and the top-5 prompt hint is derived from that report instead of a
|
|
182
|
+
second git walk.
|
|
183
|
+
|
|
184
|
+
`smoke-gate-wiring.sh` sweeps every runtime script for a reference from the
|
|
185
|
+
instruction surface, with maintainer tooling listed explicitly and by reason
|
|
186
|
+
rather than being the silent default for anything unreferenced. It also pins the
|
|
187
|
+
four wirings individually, and asserts the test-integrity gate is fed the full
|
|
188
|
+
report rather than the truncated hint. It found two more scripts on the first run
|
|
189
|
+
(`review-watch.sh`, `audit-log-rotate.sh`), both correctly user-launched.
|
|
190
|
+
|
|
191
|
+
### Removed: the v9.1.0 legacy-v2 analysis escape hatch
|
|
192
|
+
|
|
193
|
+
Triple-dead and still documented in three files. Its own text said "escape hatch
|
|
194
|
+
removed in v9.2.0" - this is 12.8.0. The `prefs.global.legacyV2AnalysisAllowed` it
|
|
195
|
+
depended on was never added to `prefs.schema.json`, and `global` is
|
|
196
|
+
`additionalProperties: false`, so a user who configured it as documented produced
|
|
197
|
+
a preferences file that fails validation. `template_version < v3` now simply
|
|
198
|
+
aborts. The reclaimed budget paid for the two new phase-4 steps, so the fixed
|
|
199
|
+
per-run context ceiling did not move.
|
|
200
|
+
|
|
201
|
+
### Gate honesty
|
|
202
|
+
|
|
203
|
+
- **The context ceiling can no longer be bypassed.** It was
|
|
204
|
+
`${CONTEXT_BUDGET_CEILING:-60000}` - the only env-overridable threshold in the
|
|
205
|
+
suite, so `CONTEXT_BUDGET_CEILING=999999` passed while measuring nothing. It is
|
|
206
|
+
a literal now, pinned by `test/context-budget-gate.test.mjs` (the companion to
|
|
207
|
+
`test/coverage-gate.test.mjs`, which already protects the coverage floors from
|
|
208
|
+
the same move). Headroom was 3.5% when this was written - precisely when raising
|
|
209
|
+
the number looks attractive. The test also prints current headroom so it shrinks
|
|
210
|
+
visibly instead of silently.
|
|
211
|
+
- **The coverage claim names its denominator.** "coverage floors held" reads as a
|
|
212
|
+
repo-wide figure; c8 instruments JavaScript, and this repo carries ~30k lines of
|
|
213
|
+
shell and Python that no coverage number covers at all. The metric is now
|
|
214
|
+
"coverage floors held (JavaScript only)" and states the excluded size, matching
|
|
215
|
+
what the scorecard's UNMEASURED section already does for whole categories.
|
|
216
|
+
|
|
217
|
+
### Also
|
|
218
|
+
|
|
219
|
+
- `smoke-consumer-smoke-surface.sh` and `smoke-gate-wiring.sh` added to `ci-lite`;
|
|
220
|
+
the consumer-surface gate also runs in `release`, since a publish is the only
|
|
221
|
+
moment tarball contents become permanent.
|
|
222
|
+
- `test/dev-only-files.test.mjs` (20 tests) covers the exclusion predicate,
|
|
223
|
+
including that a hypothetical new smoke is withheld by default and that runtime
|
|
224
|
+
scripts the phases invoke are not.
|
|
225
|
+
|
|
19
226
|
## [12.7.0] - 2026-07-26
|
|
20
227
|
|
|
21
228
|
A refactor pass whose theme turned out to be one recurring defect: gates that
|
package/install/_common.mjs
CHANGED
|
@@ -239,6 +239,54 @@ export function pruneLegacyMultiAgentSkills(skillsDir) {
|
|
|
239
239
|
return removed;
|
|
240
240
|
}
|
|
241
241
|
|
|
242
|
+
/**
|
|
243
|
+
* Trees an older installer created and no current one manages.
|
|
244
|
+
*
|
|
245
|
+
* A tree that stops being copied does NOT stop existing: the wipe-before-copy
|
|
246
|
+
* pattern only runs for trees the installer still writes, so dropping one from
|
|
247
|
+
* the installer leaves whatever it last wrote on every existing machine forever.
|
|
248
|
+
*
|
|
249
|
+
* `eval/` is the case that prompted this. An older version copied the eval
|
|
250
|
+
* corpora into `~/.claude/eval/`; the harnesses that read them are maintainer CI
|
|
251
|
+
* tooling and no longer ship, so neither installer references `eval` any more and
|
|
252
|
+
* neither did uninstall - 31 stale files sat there with nothing left that could
|
|
253
|
+
* ever read or remove them.
|
|
254
|
+
*
|
|
255
|
+
* @type {ReadonlyArray<{dir: string, reason: string}>}
|
|
256
|
+
*/
|
|
257
|
+
export const ABANDONED_TREES = Object.freeze([
|
|
258
|
+
{
|
|
259
|
+
dir: "eval",
|
|
260
|
+
reason: "eval corpora; the harnesses that read them are maintainer-only and no longer ship",
|
|
261
|
+
},
|
|
262
|
+
]);
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Remove trees an older install left behind under `root`.
|
|
266
|
+
*
|
|
267
|
+
* @param {string} root e.g. `$HOME/.claude`
|
|
268
|
+
* @returns {number} directories removed
|
|
269
|
+
*/
|
|
270
|
+
export function pruneAbandonedTrees(root) {
|
|
271
|
+
let removed = 0;
|
|
272
|
+
for (const { dir, reason } of ABANDONED_TREES) {
|
|
273
|
+
const target = join(root, dir);
|
|
274
|
+
if (!existsSync(target)) continue;
|
|
275
|
+
if (dryRun) {
|
|
276
|
+
console.log(` [dry-run] would remove abandoned tree ${target} (${reason})`);
|
|
277
|
+
removed++;
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
try {
|
|
281
|
+
rmSync(target, { recursive: true, force: true });
|
|
282
|
+
removed++;
|
|
283
|
+
} catch {
|
|
284
|
+
/* non-fatal - a leftover tree must never fail an install */
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return removed;
|
|
288
|
+
}
|
|
289
|
+
|
|
242
290
|
/**
|
|
243
291
|
* Copy a single file, honouring dry-run mode.
|
|
244
292
|
* @param {string} src
|
|
@@ -1,18 +1,138 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Dev-only files excluded from end-user installs.
|
|
2
|
+
* Dev-only files excluded from end-user installs and from the published tarball.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Two separate reasons live here, and they must not be conflated:
|
|
5
|
+
*
|
|
6
|
+
* 1. LEAK: the file references the maintainer's private upstream or carries
|
|
7
|
+
* personal data (org names, hostnames, emails) on the `from:` side of a
|
|
8
|
+
* substitution rule. Shipping it is a disclosure, not just noise.
|
|
9
|
+
*
|
|
10
|
+
* 2. MAINTAINER CI: the file is a repository gate. It resolves the repo root
|
|
11
|
+
* as `dirname($0)/../..` and reads `package.json` / `install.js` /
|
|
12
|
+
* `node_modules` / the git tree. In an install layout that path is `$HOME`,
|
|
13
|
+
* so the script reports things like `FAIL: install.js missing` - a green
|
|
14
|
+
* repo check that reads as a broken installation to whoever ran it.
|
|
15
|
+
* Measured before this list existed: of 117 installed smoke scripts, 100
|
|
16
|
+
* failed that way and 17 passed only by accident of not needing the repo.
|
|
17
|
+
*
|
|
18
|
+
* Reason 2 is why the default for `smoke-*.sh` is EXCLUDE. A smoke ships only
|
|
19
|
+
* when a command actually tells a user to run it - that set is CONSUMER_SMOKES
|
|
20
|
+
* below, and `smoke-consumer-smoke-surface.sh` fails if the two drift apart.
|
|
8
21
|
*
|
|
9
22
|
* @module install/_dev-only-files
|
|
10
23
|
*/
|
|
11
24
|
|
|
25
|
+
import { readdirSync } from "node:fs";
|
|
26
|
+
import { join } from "node:path";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Smoke suites an end user is instructed to run, and which therefore must work
|
|
30
|
+
* from an installed layout (`~/.claude/scripts/`) as well as from the repo.
|
|
31
|
+
*
|
|
32
|
+
* `/multi-agent:update` runs both as its post-update verification. Anything
|
|
33
|
+
* added here MUST resolve its paths through `_smoke-root.sh`, never through a
|
|
34
|
+
* bare `dirname($0)/../..`.
|
|
35
|
+
*/
|
|
36
|
+
export const CONSUMER_SMOKES = Object.freeze([
|
|
37
|
+
"smoke-schema-validation.sh",
|
|
38
|
+
"smoke-cross-cli-behavior.sh",
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Every `smoke-*.sh` that is NOT in CONSUMER_SMOKES. Expressed as one regex so a
|
|
43
|
+
* newly added smoke is excluded by default rather than shipping until someone
|
|
44
|
+
* notices - the safe direction for reason 2 above.
|
|
45
|
+
*/
|
|
46
|
+
export const MAINTAINER_SMOKES = new RegExp(
|
|
47
|
+
`^smoke-(?!${CONSUMER_SMOKES.map((n) => n.replace(/^smoke-/, "").replace(/\./g, "\\.")).join(
|
|
48
|
+
"|",
|
|
49
|
+
)}$)`,
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Repository tooling that is not a smoke: the smoke runner itself, the eval
|
|
54
|
+
* harnesses, the scorecard, and the linters. All of them need devDependencies
|
|
55
|
+
* (`ajv`, `c8`, `eslint`) or the repo tree, neither of which exists in an
|
|
56
|
+
* install.
|
|
57
|
+
*/
|
|
58
|
+
const DEV_ONLY_TOOLING = Object.freeze([
|
|
59
|
+
"run-smokes.mjs",
|
|
60
|
+
"scorecard.mjs",
|
|
61
|
+
"lint-skills.mjs",
|
|
62
|
+
"lint-mcp-refs.mjs",
|
|
63
|
+
"check-md-links.mjs",
|
|
64
|
+
"validate-schemas.mjs", // validates the repo's own schema files, needs ajv
|
|
65
|
+
"sync-parity-check.sh",
|
|
66
|
+
"benchmark-phase-0.sh",
|
|
67
|
+
"test-gap-rules", // rule corpus for the repo's own test-gap gate
|
|
68
|
+
]);
|
|
69
|
+
|
|
70
|
+
/** Eval harnesses (`eval-*.mjs`) read `pipeline/eval/**`, which never ships. */
|
|
71
|
+
const EVAL_HARNESSES = /^eval-.*\.mjs$/;
|
|
72
|
+
|
|
12
73
|
export const DEV_ONLY_SCRIPTS = Object.freeze([
|
|
74
|
+
// --- reason 1: leak ---
|
|
13
75
|
"smoke-figma-config-schema.sh",
|
|
14
76
|
"smoke-personal-data.sh", // scanner contains the patterns it detects
|
|
15
77
|
"smoke-install-leak-gate.sh", // same - scanner shadows the install pipeline
|
|
16
78
|
"validate-prefs.mjs", // needs ajv (devDependency); end users have no node_modules here
|
|
17
79
|
"fixtures", // smoke test fixtures live in the repo only, never ship to users
|
|
80
|
+
// --- reason 2: maintainer CI ---
|
|
81
|
+
...DEV_ONLY_TOOLING,
|
|
82
|
+
EVAL_HARNESSES,
|
|
83
|
+
MAINTAINER_SMOKES,
|
|
18
84
|
]);
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* True when `name` (a path relative to `pipeline/scripts/`) must not be
|
|
88
|
+
* installed. Shared by both installers' excluded-file counters and by
|
|
89
|
+
* `smoke-consumer-smoke-surface.sh`, so the count and the gate can never
|
|
90
|
+
* disagree with what `copyDir` actually filtered.
|
|
91
|
+
*
|
|
92
|
+
* @param {string} name
|
|
93
|
+
* @returns {boolean}
|
|
94
|
+
*/
|
|
95
|
+
export function isDevOnlyScript(name) {
|
|
96
|
+
const rel = String(name).split("\\").join("/").replace(/^\//, "");
|
|
97
|
+
return DEV_ONLY_SCRIPTS.some((pat) =>
|
|
98
|
+
typeof pat === "string" ? rel === pat || rel.startsWith(pat + "/") : pat.test(rel),
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* How many files under `scriptsDir` `copyDir` will filter out.
|
|
104
|
+
*
|
|
105
|
+
* Walks the tree and applies `isDevOnlyScript` to the same relative paths
|
|
106
|
+
* `copyDir`'s filter sees, so the number printed by an install is the number
|
|
107
|
+
* actually withheld. The previous form summed `countFiles()` over the exclusion
|
|
108
|
+
* array, which silently returned 0 for every non-literal (regex) entry.
|
|
109
|
+
*
|
|
110
|
+
* @param {string} scriptsDir absolute path to `pipeline/scripts`
|
|
111
|
+
* @returns {number}
|
|
112
|
+
*/
|
|
113
|
+
export function countDevOnlyFiles(scriptsDir) {
|
|
114
|
+
let excluded = 0;
|
|
115
|
+
const walk = (abs, rel) => {
|
|
116
|
+
for (const entry of readdirSync(abs, { withFileTypes: true })) {
|
|
117
|
+
const childRel = rel ? `${rel}/${entry.name}` : entry.name;
|
|
118
|
+
const childAbs = join(abs, entry.name);
|
|
119
|
+
if (entry.isDirectory()) {
|
|
120
|
+
// An excluded directory takes its whole subtree with it, exactly as
|
|
121
|
+
// cpSync's filter does when it returns false for the directory itself.
|
|
122
|
+
if (isDevOnlyScript(childRel)) excluded += countTree(childAbs);
|
|
123
|
+
else walk(childAbs, childRel);
|
|
124
|
+
} else if (isDevOnlyScript(childRel)) {
|
|
125
|
+
excluded++;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
const countTree = (abs) => {
|
|
130
|
+
let n = 0;
|
|
131
|
+
for (const entry of readdirSync(abs, { withFileTypes: true })) {
|
|
132
|
+
n += entry.isDirectory() ? countTree(join(abs, entry.name)) : 1;
|
|
133
|
+
}
|
|
134
|
+
return n;
|
|
135
|
+
};
|
|
136
|
+
walk(scriptsDir, "");
|
|
137
|
+
return excluded;
|
|
138
|
+
}
|
package/install/claude.mjs
CHANGED
|
@@ -27,13 +27,14 @@ import {
|
|
|
27
27
|
ensureDir,
|
|
28
28
|
ensureRealDir,
|
|
29
29
|
isDryRun,
|
|
30
|
+
pruneAbandonedTrees,
|
|
30
31
|
pruneLegacyMultiAgentSkills,
|
|
31
32
|
removePipelineAgentFiles,
|
|
32
33
|
wipeDir,
|
|
33
34
|
writeFile,
|
|
34
35
|
} from "./_common.mjs";
|
|
35
36
|
import { copyExternalSkillsFiltered } from "./_platform-filter.mjs";
|
|
36
|
-
import { DEV_ONLY_SCRIPTS } from "./_dev-only-files.mjs";
|
|
37
|
+
import { DEV_ONLY_SCRIPTS, countDevOnlyFiles } from "./_dev-only-files.mjs";
|
|
37
38
|
|
|
38
39
|
/**
|
|
39
40
|
* Dead matcher written by pre-v11.4.1 installs. Hook matchers only ever see
|
|
@@ -64,6 +65,14 @@ export function installClaude(ctx) {
|
|
|
64
65
|
const CLAUDE_LIB = join(home, ".claude", "lib");
|
|
65
66
|
const CLAUDE_MA_REFS = join(home, ".claude", "multi-agent-refs");
|
|
66
67
|
|
|
68
|
+
// Before laying anything down: drop trees an older install created that no
|
|
69
|
+
// current installer manages. Wipe-before-copy only protects trees still being
|
|
70
|
+
// written, so an abandoned one persists on every existing machine forever.
|
|
71
|
+
const prunedTrees = pruneAbandonedTrees(join(home, ".claude"));
|
|
72
|
+
if (prunedTrees > 0) {
|
|
73
|
+
console.log(` [Claude Code] Removed ${prunedTrees} abandoned tree(s) from an older install`);
|
|
74
|
+
}
|
|
75
|
+
|
|
67
76
|
installCommands(pipelineSrc, CLAUDE_COMMANDS, useSymlinks);
|
|
68
77
|
installMultiAgentRefs(pipelineSrc, CLAUDE_MA_REFS, useSymlinks);
|
|
69
78
|
installScripts(pipelineSrc, CLAUDE_SCRIPTS, useSymlinks);
|
|
@@ -201,13 +210,10 @@ function installScripts(pipelineSrc, dest, useSymlinks) {
|
|
|
201
210
|
if (!useSymlinks) ensureRealDir(dest);
|
|
202
211
|
wipeDir(dest);
|
|
203
212
|
copyDir(scriptsSrc, dest, { exclude: DEV_ONLY_SCRIPTS, useSymlinks });
|
|
204
|
-
// Count files actually excluded
|
|
205
|
-
//
|
|
206
|
-
//
|
|
207
|
-
const excludedCount =
|
|
208
|
-
(n, name) => n + countFiles(join(scriptsSrc, name)),
|
|
209
|
-
0,
|
|
210
|
-
);
|
|
213
|
+
// Count files actually excluded by walking the tree: DEV_ONLY_SCRIPTS mixes
|
|
214
|
+
// literal names, whole directories, and regexes (the maintainer-smoke rule),
|
|
215
|
+
// so summing countFiles() over the array reported 0 for every regex entry.
|
|
216
|
+
const excludedCount = countDevOnlyFiles(scriptsSrc);
|
|
211
217
|
const scriptCount = countFiles(scriptsSrc) - excludedCount;
|
|
212
218
|
console.log(` -> ${scriptCount} files copied to ${dest} (${excludedCount} dev-only excluded)`);
|
|
213
219
|
}
|
package/install/copilot.mjs
CHANGED
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
writeFile,
|
|
24
24
|
} from "./_common.mjs";
|
|
25
25
|
import { copyExternalSkillsFiltered } from "./_platform-filter.mjs";
|
|
26
|
-
import { DEV_ONLY_SCRIPTS } from "./_dev-only-files.mjs";
|
|
26
|
+
import { DEV_ONLY_SCRIPTS, countDevOnlyFiles } from "./_dev-only-files.mjs";
|
|
27
27
|
import { generateCopilotInstructions } from "./_copilot-instructions.mjs";
|
|
28
28
|
|
|
29
29
|
/**
|
|
@@ -166,13 +166,10 @@ function installScripts(pipelineSrc, dest, useSymlinks) {
|
|
|
166
166
|
if (!useSymlinks) ensureRealDir(dest);
|
|
167
167
|
wipeDir(dest);
|
|
168
168
|
copyDir(scriptsSrc, dest, { exclude: DEV_ONLY_SCRIPTS, useSymlinks });
|
|
169
|
-
// Count files actually excluded
|
|
170
|
-
//
|
|
171
|
-
//
|
|
172
|
-
const excludedCount =
|
|
173
|
-
(n, name) => n + countFiles(join(scriptsSrc, name)),
|
|
174
|
-
0,
|
|
175
|
-
);
|
|
169
|
+
// Count files actually excluded by walking the tree: DEV_ONLY_SCRIPTS mixes
|
|
170
|
+
// literal names, whole directories, and regexes (the maintainer-smoke rule),
|
|
171
|
+
// so summing countFiles() over the array reported 0 for every regex entry.
|
|
172
|
+
const excludedCount = countDevOnlyFiles(scriptsSrc);
|
|
176
173
|
const scriptCount = countFiles(scriptsSrc) - excludedCount;
|
|
177
174
|
console.log(` -> ${scriptCount} files copied to ${dest} (${excludedCount} dev-only excluded)`);
|
|
178
175
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mmerterden/multi-agent-pipeline",
|
|
3
|
-
"version": "12.
|
|
3
|
+
"version": "12.9.0",
|
|
4
4
|
"description": "8-phase AI development pipeline with full orchestration on Claude Code and Copilot CLI. Analysis, planning, TDD, CLI-aware parallel review with consensus surfacing + Fable triage, default-FAIL evidence gates, secret + intent guards, per-phase cost ledger, persistent learnings memory, wiki generation, commit automation. Token-preserving uninstall.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -75,7 +75,22 @@
|
|
|
75
75
|
"!pipeline/scripts/smoke-personal-data.sh",
|
|
76
76
|
"!pipeline/scripts/validate-prefs.mjs",
|
|
77
77
|
"!pipeline/scripts/smoke-install-leak-gate.sh",
|
|
78
|
-
"!docs/internal/**"
|
|
78
|
+
"!docs/internal/**",
|
|
79
|
+
"!pipeline/scripts/smoke-*.sh",
|
|
80
|
+
"!pipeline/scripts/run-smokes.mjs",
|
|
81
|
+
"!pipeline/scripts/eval-*.mjs",
|
|
82
|
+
"!pipeline/scripts/scorecard.mjs",
|
|
83
|
+
"!pipeline/scripts/lint-skills.mjs",
|
|
84
|
+
"!pipeline/scripts/lint-mcp-refs.mjs",
|
|
85
|
+
"!pipeline/scripts/check-md-links.mjs",
|
|
86
|
+
"!pipeline/scripts/validate-schemas.mjs",
|
|
87
|
+
"!pipeline/scripts/sync-parity-check.sh",
|
|
88
|
+
"!pipeline/scripts/benchmark-phase-0.sh",
|
|
89
|
+
"!pipeline/scripts/fixtures/**",
|
|
90
|
+
"!pipeline/scripts/test-gap-rules/**",
|
|
91
|
+
"!pipeline/eval/**",
|
|
92
|
+
"pipeline/scripts/smoke-schema-validation.sh",
|
|
93
|
+
"pipeline/scripts/smoke-cross-cli-behavior.sh"
|
|
79
94
|
],
|
|
80
95
|
"devDependencies": {
|
|
81
96
|
"@eslint/js": "^10.0.1",
|
|
@@ -65,7 +65,7 @@ When citing a Locked decision in code or docs, prefer `Locked <n> (<short label>
|
|
|
65
65
|
27. **Evidence digest caches Phase 1b and 1c.** `evidence_digest = sha256(featureName || sorted(platforms) || repoEvidence.summary || conventions.summary)`. When the same feature name is invoked again against the same set of repos and the digest matches, Phase 1b and 1c are skipped and the cached `evidence.repoEvidence` / `evidence.conventions` is reused. Cache TTL is 24 hours; manual invalidation via `--no-cache` flag.
|
|
66
66
|
28. **SwiftUI Preview block mandatory (iOS projection, SwiftUI only).** When the iOS file is produced AND the affected view is a SwiftUI view (detected via `import SwiftUI` + `: View` protocol conformance in `evidence.repoEvidence[<repo>].buckets.uiComponents`), Section 13.6 renders a Preview block table covering at minimum: canonical default (LTR Light), Dark, RTL, Dynamic Type accessibilityLarge, and one error variant. Loading state and edge-case variants are added when distinct from canonical. UIKit-only features (no SwiftUI view artefact) drop Section 13.6 with note `(N/A: UIKit-only feature)`. Preview macro convention (`#Preview` for Swift 5.9+ vs legacy `PreviewProvider`) is read from `evidence.conventions[<repo>].previewMacro`. Each Preview variant listed in Section 13.6 must have a matching row in Section 15.2 Snapshot Tests; a Preview without a snapshot row triggers a Section 20 Risk.
|
|
67
67
|
29. **Variant usage explicit and bounded.** Section 6 inventory rows list which variants this feature consumes per component (concrete enum case + bool value). New Section 6.X (Variant Usage Matrix) catalogues the full variant axis vs. used subset with a rationale per excluded variant. Sections 13.6 (Preview) and 15.2 (Snapshot) cover only the used subset; expanding the variant set requires updating Section 6.X first.
|
|
68
|
-
30. **Analysis as self-contained design bridge - no MCP outside analysis phase (BLOCKING, pipeline-wide
|
|
68
|
+
30. **Analysis as self-contained design bridge - no MCP outside analysis phase (BLOCKING, pipeline-wide).** The analysis document is the sole design source for every downstream phase. After Phase 1 of `/multi-agent:analysis` produces `analysis/<feature>-<platform>.md`, Phase 2 Planning, Phase 3 Dev, Phase 4 Review, Phase 5 Test, Phase 6 Commit, and Phase 7 Report consume only the analysis document plus repo Code Connect mappings (`*.figma.swift` / `*.figma.kt`). Calling `mcp__claude_ai_Figma__*`, hitting `api.figma.com`, or fetching a `figma.com/design/...` URL during Phase 2+ is a violation. Applies to every mode that runs Phase 2+: `/multi-agent`, `/multi-agent:autopilot`, `/multi-agent:local`, `/multi-agent:local-autopilot`, `/multi-agent:dev`, `/multi-agent:dev-autopilot`, `/multi-agent:dev-local`, `/multi-agent:dev-local-autopilot`. Hard requirement (v9.0.0): Phase 2 Pre-item and Phase 3 Pre-item (BLOCKING) abort the run when the analysis document is missing. Memory: `[[mcp-only-in-analysis]]`. Generic rule rationale and access matrix: see `pipeline/rules/figma-pipeline.md` "MUST: No MCP outside analysis phase".
|
|
69
69
|
31. **Business-rule to acceptance-criterion to test traceability (AI + human spine).** The analysis is a development handoff that both an AI implementer and a human reviewer must act on, so it is bound by one shared-ID vocabulary. Every business rule carries a stable id `BR-<slug>-NN` (Section 4.4). Each rule maps to at least one acceptance criterion written Given / When / Then (binary - two readers must not be able to disagree on pass/fail). Each acceptance criterion maps to unit-test scenarios in Section 15.1, one row per case across happy / boundary / error / empty-nil (enumerate at least the failure modes; agents hallucinate error handling when it is omitted). The same ids thread onward: Section 15.6 UI-test flows reference the `BR-` ids and use stable selectors (accessibilityIdentifier / testTag), Section 16 accessibility items reuse those identifiers, Section 11 analytics events cite their triggering rule or story, and Section 5/7 layout cells carry token + Figma node refs. Never invent copy or values (blank beats a guess; a missing source becomes a Section 20 Open Question). **Mode-aware gate:** in Full mode a business rule with no acceptance criterion, or an acceptance criterion with no Section 15.1 scenario, fails the dispatch gate. In **Lite mode Section 15 is not rendered**, so the rule-to-test half does not apply - Section 4.4 still lists each rule with its Given/When/Then acceptance criterion (the acceptance criterion is itself the testable statement), and the 15.1 mapping is deferred to whenever the feature is later analyzed in Full or implemented via `/multi-agent:dev`. The rule-to-acceptance-criterion half always holds, in both modes.
|
|
70
70
|
|
|
71
71
|
## Input
|
|
@@ -113,18 +113,36 @@ if isinstance(name, str) and name.strip():
|
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
# --- Subcommands ------------------------------------------------------------
|
|
116
|
+
# Append one PAT-lookup audit event. Every `get` goes through here, which is the
|
|
117
|
+
# point of the control: the audit trail described in the .gitignore hardening and
|
|
118
|
+
# managed by `/multi-agent:prune-logs` was written by audit-log.sh, but nothing
|
|
119
|
+
# ever called it, so the trail was always empty and the control was inert.
|
|
120
|
+
#
|
|
121
|
+
# Never fails the caller and never echoes a secret: audit-log.sh hashes the repo
|
|
122
|
+
# URL, logs the logical key (not the value), and is silent on error by design.
|
|
123
|
+
audit_lookup() {
|
|
124
|
+
local logical="$1" success="$2"
|
|
125
|
+
local audit="$SCRIPT_DIR/../scripts/audit-log.sh"
|
|
126
|
+
[ -f "$audit" ] || return 0
|
|
127
|
+
bash "$audit" pat_lookup "$logical" "${USER:-unknown}" "${MULTI_AGENT_REPO_URL:-}" "$success" \
|
|
128
|
+
>/dev/null 2>&1 || true
|
|
129
|
+
}
|
|
130
|
+
|
|
116
131
|
do_get() {
|
|
117
132
|
local key="${1:-}"
|
|
118
133
|
[ -z "$key" ] && { echo "usage: $0 get <key>" >&2; exit 3; }
|
|
134
|
+
local logical="$key"
|
|
119
135
|
key=$(resolve_key "$key")
|
|
120
136
|
if delegate_to_python; then
|
|
121
137
|
local val rc
|
|
122
138
|
val=$(python3 "$KEYCHAIN_PY" get "$key" 2>/dev/null) || rc=$?
|
|
123
139
|
rc=${rc:-0}
|
|
124
140
|
if [ "$rc" -eq 0 ] && [ -n "$val" ]; then
|
|
141
|
+
audit_lookup "$logical" true
|
|
125
142
|
printf '%s' "$val"
|
|
126
143
|
return 0
|
|
127
144
|
fi
|
|
145
|
+
audit_lookup "$logical" false
|
|
128
146
|
return 1
|
|
129
147
|
fi
|
|
130
148
|
local val=""
|
|
@@ -149,8 +167,10 @@ if (\$c) { \$c.GetNetworkCredential().Password }
|
|
|
149
167
|
echo "ERR: unsupported platform" >&2; exit 2 ;;
|
|
150
168
|
esac
|
|
151
169
|
if [ -z "$val" ]; then
|
|
170
|
+
audit_lookup "$logical" false
|
|
152
171
|
return 1
|
|
153
172
|
fi
|
|
173
|
+
audit_lookup "$logical" true
|
|
154
174
|
printf '%s' "$val"
|
|
155
175
|
}
|
|
156
176
|
|
|
@@ -14,7 +14,7 @@ First step of every `multi-agent` flow **that touches a remote provider**.
|
|
|
14
14
|
> repos, account-picker is bypassed and the state file's `accountId` is left
|
|
15
15
|
> `null` with `tokens={}`. Phases 6/7 read these as "local-only" signals.
|
|
16
16
|
|
|
17
|
-
> **Language**:
|
|
17
|
+
> **Language**: see `picker-contract.md` + `rules.md` Language Application matrix.
|
|
18
18
|
|
|
19
19
|
## Steps
|
|
20
20
|
|
|
@@ -6,7 +6,7 @@ description: "Internal - dev context (extra repos) picker for multi-agent."
|
|
|
6
6
|
|
|
7
7
|
Selects extra repos the pipeline may touch beyond the primary repo(s) - typically submodules (e.g. SDKs vendored inside an app repo) or sibling libraries. Each candidate is enriched with a `canPush` flag so the picker can pre-select repos that the active account is allowed to edit, and surface read-only ones as advisory context the pipeline will not modify.
|
|
8
8
|
|
|
9
|
-
> **Language**:
|
|
9
|
+
> **Language**: see `picker-contract.md` + `rules.md` Language Application matrix.
|
|
10
10
|
|
|
11
11
|
## Steps
|
|
12
12
|
|
|
@@ -6,7 +6,7 @@ description: "Internal - multi-select repo picker for multi-agent."
|
|
|
6
6
|
|
|
7
7
|
Used by `picker-issue`, `picker-jira`, `gh-num`, and `freetext` flows.
|
|
8
8
|
|
|
9
|
-
> **Language**:
|
|
9
|
+
> **Language**: see `picker-contract.md` + `rules.md` Language Application matrix.
|
|
10
10
|
|
|
11
11
|
## Steps
|
|
12
12
|
|