@mmerterden/multi-agent-pipeline 20.11.0 → 20.12.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/manifest.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@mmerterden/multi-agent-pipeline",
3
- "version": "20.11.0",
4
- "commit": "5124f984fab4c9ec2413942e9e9d30020dd046d1",
5
- "generatedAt": "2026-09-29T13:01:15.322Z",
3
+ "version": "20.12.0",
4
+ "commit": "67faa4d431ba4b6d942630d6acbcfedbec220c9a",
5
+ "generatedAt": "2026-10-01T18:29:41.758Z",
6
6
  "algorithm": "sha256",
7
- "fileCount": 1230,
7
+ "fileCount": 1231,
8
8
  "files": {
9
- "CHANGELOG.md": "c23b53aae6aa02d811dcb0a1698d36e23f5c19f77b10ea4677d62e28e1f56920",
9
+ "CHANGELOG.md": "787a8bd3432c3fdbbf572f8d8ef13c548291f0a46691abba28811943d516eaef",
10
10
  "LICENSE": "8d8c51aaf1e1538b180c69965fa038db71be86798cc3e26a143bebfefe16a43e",
11
11
  "README.md": "0613eebb0b38062bd94c44043595d1950b5c9b470874dd8e402c052fd20960cf",
12
12
  "README.tr.md": "0c6b5e6943d6c3d3bf3a7ed99c705fb9a78d15d7722e6252f58404d3f6d3524d",
@@ -33,7 +33,7 @@
33
33
  "docs/best-practices.md": "f8b75b0d38b5896a6a852bb30a1edddeb24dab8b5b7b1d9313290e1241c72024",
34
34
  "docs/ecosystem.md": "e6c00f30e95b5e178f77bdfbefd686c8a4164f39c03865a89ab23465d1a37acf",
35
35
  "docs/engineering.md": "550528cd47c7d2c921729c68d0782a00ac8e77a4bdfec479f6749f9c3235d188",
36
- "docs/facts.json": "ec1d8856d83f528813d5a3c5777c717753248fb287ae0427ba1b2f14c16d92fa",
36
+ "docs/facts.json": "8f5a257e848433f3c3deca22a452e5407d7853913b5c7f60a304ff5a5aab4fee",
37
37
  "docs/features.md": "6c9f0efb00c683e6f611c440c87847b187f8b0eee59e9e812f3a241695bc2ae1",
38
38
  "docs/performance.md": "bbf2f3922c25dcba0c1a9e69ef420e3871a729af5b35009a49b48a5d3c5cd151",
39
39
  "docs/recovery-guide.md": "59a8f1b66b34cb9ff5507b3d54dac3b9915e184aa044d9c758d18cac818d510e",
@@ -51,8 +51,8 @@
51
51
  "install/_plugin-skills.mjs": "bf287053133f940df0ca434cfc5ee522fd27e80dc7f7e60ccc963ecaabee047a",
52
52
  "install/_telemetry.mjs": "5433d753f909beed2c8789272bba05e245482c472f83d3444f58000c4c9a634b",
53
53
  "install/_unattended-profile.mjs": "e1ed82bde35d19105ee12c2a551eb778912b0e5c93ab227fd15c993098d98de0",
54
- "install/catalog-history.json": "3bc89ad70da08bfea483551fb3d38fab2219e7dffe16dd97c636c2a2e52dd532",
55
- "install/claude.mjs": "c7b0a6eff15e967778e6ab8c463949c79dade05309878b03836f023da704fb3a",
54
+ "install/catalog-history.json": "15422563f83ca98278f94ae3e34730112e65888262e1da0442768352f46a75d4",
55
+ "install/claude.mjs": "6af22bb9b2594aee502c36550020d2c9c4c3222061ed8a88a0885687cd91ebec",
56
56
  "install/codex.mjs": "2c2238cd9dd2f95b4a50a505387e8e49ddc1513ae13998f05f1ed3e3a71b18e7",
57
57
  "install/copilot.mjs": "300a74f17084d1a38f16428ca7477394c29e33cd755156e19b92e457999be234",
58
58
  "install/index.mjs": "07ec26ecdd81cc983f146faf4b7095c98edce39c89a6fd83c24ce43229d2d9e7",
@@ -62,11 +62,11 @@
62
62
  "install/templates/multi-agent-autopilot-awake.plist.template": "ddd25c76c56fcbb38a89de8e35637372d70ace993d6bc487a88ca9cfb702fe1f",
63
63
  "install/templates/multi-agent-autopilot.plist.template": "122c8a61cd927edd99375d6e3da40f1d42bcceed5b6641785c8b28bb930bc77e",
64
64
  "install/unattended-profile-legacy.json": "f52e86b998956f09ad306b1735a6c10b58580d7082d8a55665a4c6bb15c3aa1f",
65
- "package.json": "eb7cea544573dc7c026884f773d1c644de4eb5818c2c42ab787777d8ee0fd37d",
65
+ "package.json": "64ce800c06e8882db62e426c03d3f729ec4344f44d9c6f50602d539c319ec054",
66
66
  "pipeline/agents/android-architect.md": "11fb74543cb0ebcf9525ca6654fe95d53da7229e0bf4a1149b31fd9005abc453",
67
67
  "pipeline/agents/backend-architect.md": "b02a606c301e4714131aed70ae6d16eeb77fbc819a001e2752cff2488d4246de",
68
68
  "pipeline/agents/bulk-reader.md": "6597052cd7d205bac2b1c7633f5232fe1a40730664a913b8eb77efd5a786dc25",
69
- "pipeline/agents/code-reviewer.md": "995c72b5d79bc64e45230b54e8ac5b90fbab73f187a509b331f83e5ec3493800",
69
+ "pipeline/agents/code-reviewer.md": "213798685e9bff10a08f0774f57bcfbab42ee15993095ec3a2c84e13a3453725",
70
70
  "pipeline/agents/dev-critic.md": "33843216b79977ce2926fc9c54f8c9eef30b91632a11ef624647229b61a9b36a",
71
71
  "pipeline/agents/explorer.md": "12564aec29d4581684bf84313b86a4add5a6989d3b8db5c35769674cab5649c6",
72
72
  "pipeline/agents/ios-architect.md": "e1fe0c034a4dfa76794ed04ba9f4bf85d5cdd3a5d6b948cc0635e8dda8baeef9",
@@ -104,7 +104,7 @@
104
104
  "pipeline/commands/multi-agent/language/SKILL.md": "4b58b25bda9449a52df005f8d3417ace2f8b3a18d84524dc021a9561f1d16b9d",
105
105
  "pipeline/commands/multi-agent/log/SKILL.md": "033bafcf4c9b8d117a892f50a3039fd11d753b4db070ed14c50e8388613482fa",
106
106
  "pipeline/commands/multi-agent/manual-test/SKILL.md": "7b81b1ff572fb4865495283c39f93d5c803fff2e8ce9008bf1a2ab51046c3d38",
107
- "pipeline/commands/multi-agent/model/SKILL.md": "4d0a5a9fcea459134a8d72d0ffff32134f0762f79cbb56638a717002ccb8da3c",
107
+ "pipeline/commands/multi-agent/model/SKILL.md": "56b636f8ab42c24defd3d1260f1a5e4dd332f4556d9b6d1a700322cbfa6f4f3d",
108
108
  "pipeline/commands/multi-agent/prune-logs/SKILL.md": "a7b22228ec338623c386cfd3744092488eed6871c65b531bdb6a0eb8dc8c2e9b",
109
109
  "pipeline/commands/multi-agent/prune-prompts/SKILL.md": "1aefd2b737b34aaff18f3fd12090322c04dbc41c981200622d7989c9ad45e9c2",
110
110
  "pipeline/commands/multi-agent/purge/SKILL.md": "c881805f80d7db909bcf09f1a5c980ebaf0c31d9a8ee9cae4f729387ff4ca19d",
@@ -129,8 +129,8 @@
129
129
  "pipeline/commands/multi-agent/stack/SKILL.md": "6d64e96b9a2d41d25ff077afa84300172cac9bf9dd78ec1d9fbbaf45df34f5dd",
130
130
  "pipeline/commands/multi-agent/status/SKILL.md": "a2ed316434aca49f692aafe11f95873822b9b955558a53f4f6a9667ee32a3659",
131
131
  "pipeline/commands/multi-agent/steer/SKILL.md": "07e738c228ba328f717f86e97e7af207df090b16433e28e613d430c3ff0aee3e",
132
- "pipeline/commands/multi-agent/store-ready/SKILL.md": "746d04a4766cb6bd2a8b615ac43c4fed000302a4adc0bd2a9e51f92975bbb7ad",
133
- "pipeline/commands/multi-agent/sync/SKILL.md": "17049e9ef98613825b0c4d074655477f033df460c9b760728c661f6c843a7276",
132
+ "pipeline/commands/multi-agent/store-ready/SKILL.md": "2fd7a908ee84669361bded693e7af53a17eb811e27cb4aa94237c0fce80591fa",
133
+ "pipeline/commands/multi-agent/sync/SKILL.md": "8d2a1d86a97cb27c4af5feba0eaa64bc937f979c3cba1e38dc1821557c243300",
134
134
  "pipeline/commands/multi-agent/test-accessibility/SKILL.md": "d62992541303a0776ca088d5f3d5475108fd3fba5e25af04efb3ea4bbbd1fe21",
135
135
  "pipeline/commands/multi-agent/test-dark-mode/SKILL.md": "6bfe158c94f76fc6f4d06454978d0c5cad6ec00645ab48c1d1cee15ad93ae095",
136
136
  "pipeline/commands/multi-agent/test-dynamic-type/SKILL.md": "c093fe1a78a4266ad4cf1e48b3cc233d9abd7e1c524d0b8e5db0c81d8b4d58fd",
@@ -162,6 +162,7 @@
162
162
  "pipeline/contract/types/index.d.ts": "5b738dd173c4946f8dae9cb926ad63b8adac68b77000085895608bfd87ac1aa9",
163
163
  "pipeline/lib/_jira-auth.sh": "fc468736057f8841d824eb1a4db527442562f8979fea333ebaef5527f4369e9f",
164
164
  "pipeline/lib/account-resolver.sh": "f7bef9beba3f839268509361651be0b1e1f319f2f23821f90ba466fdf185a8f6",
165
+ "pipeline/lib/agent-model-sync.mjs": "2e0264ca8d8fef761563db219d588fed9067847043f960f68f055c186c20b668",
165
166
  "pipeline/lib/analysis-jira-write.sh": "477e7b4e2aaaa8289ab0cdb315f04c3a9b22beb8f075bbb73b6fa174d6679197",
166
167
  "pipeline/lib/ask-choice.sh": "b77282645cf64d016cdebf43d1ee299874a1411f5c51b6ae3b6c580d1bbfe78a",
167
168
  "pipeline/lib/autopilot-activation.sh": "29102f7936ea8b8238fac4e9c557bdbb39e2ea5afdc3b6cdd20622593d36da9b",
@@ -193,7 +194,7 @@
193
194
  "pipeline/lib/json-file-lock.mjs": "6ff8d8d7b890fc549e07287eca1e8261b69f930ec523d08fb73786677aa0a45a",
194
195
  "pipeline/lib/md2confluence-v3.py": "3677ca47931e4b3d0fd2ce3e11299fc5f6d24799eb8332e37dd60b6c3c4653a2",
195
196
  "pipeline/lib/model-dispatch.sh": "7edd5c92d41ee3bf74972e0827ccbb671853d5410e7fb0af0b7cb020f95e05c5",
196
- "pipeline/lib/model-rung.sh": "2a5e04af3c6829d4c72e5f518e668a9cc2feffe6f2abb90a68c485f52e81959a",
197
+ "pipeline/lib/model-rung.sh": "8cecb4d390f306270713bd162e311dee879995e393970405c9a8f9af11da1727",
197
198
  "pipeline/lib/multi-repo-pipeline.sh": "5cca6bcc4fb857448ddadb31add343952be89423a328267e56496c7b7cdc7710",
198
199
  "pipeline/lib/normalize-text.mjs": "c2eeed65ab85cdc27a12ecaadc45dfa00b49a7611250c9ad881b9e93882a51c1",
199
200
  "pipeline/lib/outbound-gate.mjs": "1ebc840e975f19b0da4699b3a37361ecc8a2be1a7a558d2b2fb39b8b64244011",
@@ -263,13 +264,13 @@
263
264
  "pipeline/multi-agent-refs/features/cost-analysis.md": "42f1b90b41ef454f29ccfd3f93c959920f0e74cbca65e140695e9a30d6d90b87",
264
265
  "pipeline/multi-agent-refs/features/design-conformance.md": "a88ca595827cafa0c87c2facfcf9190dac69485e0596e5c198c6fc1613880452",
265
266
  "pipeline/multi-agent-refs/features/dev-critic.md": "08e01edef04b5ac216f8f43332126966d33da7b95625e47a8a6cd8ac105b263f",
266
- "pipeline/multi-agent-refs/features/doctor.md": "cf26e9fe4b51a47223ea0160c7c3c9c7ea177e70ec60c379271687e42fd4f594",
267
+ "pipeline/multi-agent-refs/features/doctor.md": "708aba37c507bf7559af816e6328a11ba0acdc6d61d023bc10afc727d068876a",
267
268
  "pipeline/multi-agent-refs/features/external-context-injection.md": "fe0373f27c3e9aae660a55a3ba9a8163df329b4d95c727d6023b6b3afaf71d66",
268
269
  "pipeline/multi-agent-refs/features/jira-context.md": "7d14802ed9ad1747e1a06dec26f22ff9e8959e03c47d7a018af45b8274aea47c",
269
270
  "pipeline/multi-agent-refs/features/maturity-followup.md": "e663e5bcf0b3b4d4d2a67f4516dbb9fb12520dfbdedb47a9c5f57c13a9a0633e",
270
- "pipeline/multi-agent-refs/features/model-fallback.md": "54063d87eb5db3e17c5666fdf0e04a88b9e13255d06f3de63875c4eb0868cad2",
271
+ "pipeline/multi-agent-refs/features/model-fallback.md": "e6b9635124f3557013dfbfa71d414a9d9abe9ddc89f2228a5496086c8e3b0c72",
271
272
  "pipeline/multi-agent-refs/features/package-manager.md": "b9e52f798f5dcba43abff1fe5cad8d5d5374058c9a6847c993975c665acab05a",
272
- "pipeline/multi-agent-refs/features/phone-api.md": "5e81290c85c4cb2cd83e3925b0fb502a19298ae3699aac351194f8ce3f21c791",
273
+ "pipeline/multi-agent-refs/features/phone-api.md": "817eeaffd3772b0a80811b0cd17dee3498f08f6eccf8fd7e56deb0bc24c1c434",
273
274
  "pipeline/multi-agent-refs/features/plan-critic.md": "d218c8599d893211480a412d46efdbba4aa48e3b18e8bb0ebfd66782f3b8ebc5",
274
275
  "pipeline/multi-agent-refs/features/plan-todos.md": "8780d80b4541daee2b05080759a0694ef7e6270abe9a4621deb92ec7bac94e8e",
275
276
  "pipeline/multi-agent-refs/features/prior-fix-detection.md": "9bd172fa116b0c334576ee3df2dc478810a7e282422410e045be74ac2299253d",
@@ -288,9 +289,9 @@
288
289
  "pipeline/multi-agent-refs/features/stack-adapters.md": "71e23385da872ceed35263f4838f154800aa2da55bb6688ccca74726b70a3ef5",
289
290
  "pipeline/multi-agent-refs/features/stack-skill-routing.md": "1989f7f64be6c5ee0a42d8e9962aead7cd7472d795915ab953d89d9382f317e6",
290
291
  "pipeline/multi-agent-refs/features/unattended-gates.md": "46b516cac81a4eacf5d58bc3b1613e2266c26f1559be8dac060d1dc6b7d7ca01",
291
- "pipeline/multi-agent-refs/features/unattended-security.md": "eeb8656845ac78ab612b2e3352b241a6ba2d8b8f6d4a6d5c2e7cab1f6592a4d6",
292
+ "pipeline/multi-agent-refs/features/unattended-security.md": "18da3d51c175a42ab6312051a50b9d0339524dad2cc34e0350de62fbf0cece91",
292
293
  "pipeline/multi-agent-refs/features/url-enrichment.md": "72e01a799d9fa54b56c762ba404a9f9236adbbf98320e395a96f6b0f5917e76b",
293
- "pipeline/multi-agent-refs/features/usage-reporting.md": "6c232060da49033ef2c9ccead69084dfb7cfe45333f670b407b8afada8b99007",
294
+ "pipeline/multi-agent-refs/features/usage-reporting.md": "095d86b839c1289c1023919da3972e561c1bbd4a32a1fc9f8e694abe887005ac",
294
295
  "pipeline/multi-agent-refs/features/verify-by-test.md": "b0735adaa7ead797167b8d9ea48a10851a138083c6e638738cc96c66b02a1e37",
295
296
  "pipeline/multi-agent-refs/features/verify.md": "1a6db5e7239eac01e51100353013abd16bf785a7fce88b40139fca27f4166be4",
296
297
  "pipeline/multi-agent-refs/features/visual-evidence.md": "2b6aa2e4ff05c7a1fbe9e0fedfa4b28b9ad01c93a64f1ca6f6cd3e02293866aa",
@@ -481,7 +482,7 @@
481
482
  "pipeline/scripts/crush-json.mjs": "303382670f8f170c8c6710202853a2de2915625813cd157185f9be4e2b27258e",
482
483
  "pipeline/scripts/diff-explain.mjs": "194d0635639607cb1af3de064453162e01c0d59dc0c2e187d21d45bae3bc559c",
483
484
  "pipeline/scripts/diff-risk-score.mjs": "b3e8da42fea1258162e40f4eb7809074831339b7563874527f7e825f7aa795ca",
484
- "pipeline/scripts/doctor.mjs": "a832682b5afb5641fbae180aa9f1cf8a0678d8c8daded452b86b9d717e049b19",
485
+ "pipeline/scripts/doctor.mjs": "0ac54a2630fc00b39bdf430f9466f91e6b52296a23dca8694ac1a1bd894bcfea",
485
486
  "pipeline/scripts/evidence-gate.mjs": "347a8d3fa1d17d996de345585c216bd7071f4c1d687999daf6e48881720ea5df",
486
487
  "pipeline/scripts/feedback-send.mjs": "d3ed66e9dacdfb4b5eacf1b244bf66752304fb4fcb144531441b458f3ab806be",
487
488
  "pipeline/scripts/finding-fingerprint.mjs": "fa12ab10701db5d2bfff014e41d1cd4c0b2b772aee5fc69205de14b2761b0c83",
@@ -518,7 +519,7 @@
518
519
  "pipeline/scripts/maturity-followup.mjs": "531eb9578a71ea5bb2510bb24a48f264c36aaf7adceca7b4ec3dccd1c63ae412",
519
520
  "pipeline/scripts/memory-load.sh": "c6a1976372ed033c9f3b55e757e22d05b4ef0d9774bfe185453d8847525b4a89",
520
521
  "pipeline/scripts/memory-save.sh": "ab6b4c3d78707e0ac66fe56726308782568f247463d4c7edf2316b2dd399ee6b",
521
- "pipeline/scripts/migrate-prefs.mjs": "bb389c2527edaa5bf317ee1be55c2f449da21100fc9660943764d2bcc757d665",
522
+ "pipeline/scripts/migrate-prefs.mjs": "9d38ef7dac14c48bf1c6c8053680999728d3179c598e6c9e53f5a66290fc323e",
522
523
  "pipeline/scripts/migrate-state.mjs": "b6fb42f56ea56ab10c00e947f013e3aecaeef1aa0d819493eedbef7cdf7d4370",
523
524
  "pipeline/scripts/note-session.sh": "1466d93f401c66074705635117a052a3f811ca22f928defa1ec2e4c804c19fc1",
524
525
  "pipeline/scripts/observations.mjs": "edef08a8ab8f967030da0575d83f501eb6256460c0b72c2ba377c629b3ee0674",
@@ -583,13 +584,13 @@
583
584
  "pipeline/scripts/token-budget-report.mjs": "508bbc659df478c535c0ecdd3f8611372af8fef0b0932c7b19a247af2b2b9265",
584
585
  "pipeline/scripts/triage-memory.mjs": "2d8541145f1749593ae5728fb4c9eb3001484edb5e4d37be473aeec34d1dbe7c",
585
586
  "pipeline/scripts/ui-tree-dumper.swift": "a09e42ef069eafa08c05afd8333e05b754ef6a7d8b9687712039b0f2c9801273",
586
- "pipeline/scripts/unattended_policy.py": "0f5dc7284421c529368e1dcbf30e3a85e43e73076fd09909ac185cf4ba1eff90",
587
+ "pipeline/scripts/unattended_policy.py": "f1df6470c798bd7d4bc9f8338eaabf98bdcfab00198d98b7e2e654aab84b709c",
587
588
  "pipeline/scripts/uninstall.mjs": "880923522ef59fdd98cb7ecb1bdc85c0f15d1e2eed2d79d6d353e88712ca95fa",
588
589
  "pipeline/scripts/update-check.sh": "9c00647e7ca4249414b2e5af08f61705ab5bd2106b6f66ae8ebce8aa143dc957",
589
590
  "pipeline/scripts/update-issue-progress.sh": "ccfef87743e91272e009e396ac07461d2784b2fa84a06c802a582526eeeb9ed9",
590
591
  "pipeline/scripts/usage-identity.mjs": "47c1f907fb1ab47672687ea0cbf2d0737985ae91ec2b48b4fe260e14c3d35d70",
591
592
  "pipeline/scripts/usage-register.mjs": "cc19d7ce5efe57255ce1521d33ad4b9a75b6833385ed6edcc5e339faf3c6e06c",
592
- "pipeline/scripts/usage-report.mjs": "93c3f4055592c9cf5007cccf4710d06900576ccc10d1523c631acd13ca68cf10",
593
+ "pipeline/scripts/usage-report.mjs": "af1672206b1fa7d6612fa70aa46fd63372552619fdba866d89819a12ba943549",
593
594
  "pipeline/scripts/validate-analysis-doc.mjs": "ae56b7578ef51e008a99f84eba9def3e0f1dd2bd87a3880f2083aa98b365f7a1",
594
595
  "pipeline/scripts/validate-analysis.mjs": "d2dc9ccf45f6ca2702908761939edd830d18b3b273016a4b449f26fe285d5ea1",
595
596
  "pipeline/scripts/validate-code-graph.mjs": "0ceac2f5b59f641334d54dc30bee5265361fb5fe1fc97e2f9791b546efe79941",
@@ -609,7 +610,7 @@
609
610
  "pipeline/scripts/worktrees.mjs": "90dfd09b70fffe9b56c331a4ae2692cc248f4898f4ee6aee4239ef0b20888a90",
610
611
  "pipeline/scripts/write-state.mjs": "e1198bf970eace07d3eff22b394808a526e473df65d3d58aa2d5a12cfdac8b49",
611
612
  "pipeline/scripts/write-store-config.mjs": "b994a061f6febb2ca3d396074d8b51d2c1c1f991d3072d321703380da3639a56",
612
- "pipeline/skills/.skill-manifest.json": "d387288c7de2f39f0b58a95eae996e30639b04187f3f7b3cbddaa97bab78034e",
613
+ "pipeline/skills/.skill-manifest.json": "9e1b32d035cb49c4d56121c2d422121a7dda643717168c78b484527b0c168e1f",
613
614
  "pipeline/skills/.skills-index.json": "042b1090f3e2ca310c6ce70d1bb304e8c7f96bd73133e504b95d83801608e470",
614
615
  "pipeline/skills/shared/README.md": "a144fb39993470b6379d9660e0ee0371bdc858aee3d7c1b375cb6aba03d046db",
615
616
  "pipeline/skills/shared/core/apple-archive-compliance/SKILL.md": "7239fef717ea45b79276bfcc2c3903acd57950b6e00dd6a2a66399c4b09cbd51",
@@ -1001,7 +1002,7 @@
1001
1002
  "pipeline/skills/shared/external/swift-api-design-guidelines/references/conventions-and-special-rules.md": "2adea2a5e6180eb4c4562870ee8ba94fbc0bb9d1fcafaaec0fbf9b16a20d5243",
1002
1003
  "pipeline/skills/shared/external/swift-api-design-guidelines/references/naming-and-clarity.md": "9e110f42e060d32b96947433c63702a426a8ad6c7ad52c3969e0cdcbac237e3d",
1003
1004
  "pipeline/skills/shared/external/swift-api-design-guidelines/references/side-effects-and-mutating-pairs.md": "f9d50d1af854ada5d1ac3f50ba25a598b3ae8d55fc82242b5a62ed9582712ba2",
1004
- "pipeline/skills/shared/external/swift-architecture/SKILL.md": "36566e7a79f2e6c760cc31fb1ee1a635c0e32988357c20af3502a03ecb371753",
1005
+ "pipeline/skills/shared/external/swift-architecture/SKILL.md": "295f6ce1488a93f64e5e78228b90863924fe21911769307844cc73f621c04ab1",
1005
1006
  "pipeline/skills/shared/external/swift-architecture/evals/evals.json": "05c57f42d5f442c213890b78e841e85ff101e348f4c52f2fed06cc5d4d3e5c08",
1006
1007
  "pipeline/skills/shared/external/swift-charts/SKILL.md": "4500143e373e617ab71e6292e1958bb2b34346ff9c5794ef3f2446bdd7e6de4a",
1007
1008
  "pipeline/skills/shared/external/swift-charts/evals/evals.json": "4edf3a32dab0849858a87ec117edccd54becfe7c15f4a27e3049ae4eca884304",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mmerterden/multi-agent-pipeline",
3
- "version": "20.11.0",
3
+ "version": "20.12.0",
4
4
  "description": "6-phase AI development pipeline with full orchestration on Claude Code, Copilot CLI and Codex 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",
@@ -20,12 +20,12 @@
20
20
  },
21
21
  "scripts": {
22
22
  "start": "node index.js",
23
- "test": "npm run format:check && npm run lint && node --test test/*.test.mjs && node pipeline/scripts/run-smokes.mjs && node pipeline/scripts/lint-skills.mjs && node pipeline/scripts/gen-skills-index.mjs --check && node pipeline/scripts/gen-catalog-history.mjs --check && node pipeline/scripts/lint-personas.mjs && node pipeline/scripts/lint-mcp-refs.mjs && node pipeline/scripts/eval-triage.mjs && node pipeline/scripts/eval-golden-tasks.mjs && node pipeline/scripts/eval-intent.mjs && node pipeline/scripts/eval-recall.mjs && node pipeline/scripts/eval-guard-robustness.mjs && node pipeline/scripts/validate-schemas.mjs && node pipeline/scripts/validate-prefs.mjs && node pipeline/scripts/scorecard.mjs",
24
- "test:unit": "node --test test/*.test.mjs",
23
+ "test": "npm run format:check && npm run lint && node --test test/*.test.mjs && node --test test/*.serial.mjs && node pipeline/scripts/run-smokes.mjs && node pipeline/scripts/lint-skills.mjs && node pipeline/scripts/gen-skills-index.mjs --check && node pipeline/scripts/gen-catalog-history.mjs --check && node pipeline/scripts/lint-personas.mjs && node pipeline/scripts/lint-mcp-refs.mjs && node pipeline/scripts/eval-triage.mjs && node pipeline/scripts/eval-golden-tasks.mjs && node pipeline/scripts/eval-intent.mjs && node pipeline/scripts/eval-recall.mjs && node pipeline/scripts/eval-guard-robustness.mjs && node pipeline/scripts/validate-schemas.mjs && node pipeline/scripts/validate-prefs.mjs && node pipeline/scripts/scorecard.mjs",
24
+ "test:unit": "node --test test/*.test.mjs && node --test test/*.serial.mjs",
25
25
  "test:smoke": "node pipeline/scripts/run-smokes.mjs",
26
26
  "lint:skills": "node pipeline/scripts/lint-skills.mjs",
27
- "test:quick": "node --test test/*.test.mjs && node pipeline/scripts/lint-skills.mjs && node pipeline/scripts/lint-personas.mjs && node pipeline/scripts/lint-mcp-refs.mjs",
28
- "test:coverage": "c8 --clean --reporter=none node --test test/*.test.mjs && c8 --clean=false --check-coverage --reporter=text --reporter=lcov node pipeline/scripts/run-smokes.mjs",
27
+ "test:quick": "node --test test/*.test.mjs && node --test test/*.serial.mjs && node pipeline/scripts/lint-skills.mjs && node pipeline/scripts/lint-personas.mjs && node pipeline/scripts/lint-mcp-refs.mjs",
28
+ "test:coverage": "c8 --clean --reporter=none node --test test/*.test.mjs && c8 --clean=false --reporter=none node --test test/*.serial.mjs && c8 --clean=false --check-coverage --reporter=text --reporter=lcov node pipeline/scripts/run-smokes.mjs",
29
29
  "lint": "eslint .",
30
30
  "lint:fix": "eslint . --fix",
31
31
  "format": "prettier --write \"**/*.{js,mjs,json,yml}\" --ignore-path .gitignore --ignore-path .prettierignore",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: code-reviewer
3
- description: "Code reviewer for multi-agent Phase 3 - security, architecture, quality, performance. Default model is fable (opus is the first fallback); Phase 3 orchestrator overrides to sonnet for Reviewer 3."
3
+ description: "Code reviewer for multi-agent Phase 3 - security, architecture, quality, performance. Model follows the fable switch (fable when on, opus when off); Phase 3 orchestrator overrides to sonnet for Reviewer 3."
4
4
  model: fable
5
5
  preferredModel: fable
6
6
  modelRationale: "Reviewer 1 tier - deep security + architecture review runs on fable, the top tier (opus is the first fallback). Phase 3 orchestrator overrides to sonnet for Reviewer 3 (quality/correctness focus) via CLAUDE_CODE_SUBAGENT_MODEL before dispatch. Copilot CLI adds Reviewer 2 on gpt-5.4 for cross-model diversity."
@@ -38,6 +38,13 @@ This command sets both, together:
38
38
  Flipping one without the other is the defect this closes, so the pair moves as
39
39
  one write or not at all.
40
40
 
41
+ The same step re-applies the installed agents (`lib/agent-model-sync.mjs`): every
42
+ agent under `~/.claude/agents` whose `preferredModel` is `fable` gets
43
+ `model: fable` while the switch is on and `model: opus` while it is off. An agent
44
+ spawned without an explicit model runs on that line, so without this the switch
45
+ would cover routed dispatches only. The installer applies the same rule on every
46
+ install and update, so a sync never puts the agents back on a rung that is off.
47
+
41
48
  ## What it reports with no argument
42
49
 
43
50
  The current rung, the pricing model, and **what the switch means on this host** -
@@ -295,9 +295,9 @@ project. Without the credential, skip this line and say why.
295
295
  `bundletool validate` is Gate 1 in every case; reporting it here would fake a
296
296
  cleared gate.
297
297
 
298
- **Open question, not a fact:** whether an upload into a deleted edit consumes its
299
- `versionCode` is not verified. After a Gate 2 run that uploaded, say so in the
300
- report, so the user decides whether to re-use that `versionCode`.
298
+ Google does not document whether an upload into a discarded edit reserves its
299
+ `versionCode`; fastlane's `validate_only` runs the same sequence as a pre-release
300
+ check. If the real upload is later refused as "already used", raise `versionCode`.
301
301
 
302
302
  ## Step 6 - Gate 3, policy review
303
303
 
@@ -363,7 +363,7 @@ Not run
363
363
  Gate 2: no local Play validator - Google Play not onboarded; the authoritative check is server-side only
364
364
 
365
365
  Open questions
366
- Gate 2 (Android): whether the throwaway-edit upload consumed versionCode <n> is not verified
366
+ Gate 2 (Android): versionCode <n> went to a discarded edit; Google does not document whether that reserves it
367
367
 
368
368
  Before rollout (human inputs, not verified)
369
369
  Phased rollout: <1% -> 10% -> 50% -> 100% | full>
@@ -134,12 +134,10 @@ Step 0 gate rules and why: `features/doctor.md`.
134
134
  ~/multi-agent-pipeline/pipeline/commands/ 2>/dev/null \
135
135
  && { echo "ABORT: corporate reference leaked into pipeline/commands"; exit 1; } || true
136
136
  ```
137
- - **Localized picker descriptions are an installed-tree artifact.** When
138
- `outputLanguage=tr`, the installed `description:` values are Turkish and a
139
- `description-en:` sidecar holds the English original (see
140
- `localize-commands.mjs`). The repo must ALWAYS receive the English form:
141
- after copying commands into `pipeline/commands/`, normalize the copy and
142
- verify no sidecar or Turkish description leaked:
137
+ - **Installed-tree artifacts.** Picker descriptions are Turkish with a
138
+ `description-en:` sidecar when `outputLanguage=tr`, and fable-tier agents
139
+ carry `model: opus` while `fableEnabled` is off. The repo always gets the
140
+ English form and `model: fable`, so normalize after copying:
143
141
  ```bash
144
142
  # normalize: restore English descriptions, strip description-en sidecars
145
143
  node ~/multi-agent-pipeline/pipeline/scripts/localize-commands.mjs restore \
@@ -149,6 +147,7 @@ Step 0 gate rules and why: `features/doctor.md`.
149
147
  # backstop: repo description lines must be English (Turkish-diacritics proxy)
150
148
  grep -rn '^description:.*[çğıöşüÇĞİÖŞÜ]' ~/multi-agent-pipeline/pipeline/commands/ 2>/dev/null \
151
149
  && { echo "ABORT: localized description leaked into pipeline/commands"; exit 1; } || true
150
+ node ~/multi-agent-pipeline/pipeline/lib/agent-model-sync.mjs --dir ~/multi-agent-pipeline/pipeline/agents --fable-on
152
151
  ```
153
152
 
154
153
  6. **Cross-platform smoke gate** (final step of the REPO sync, before push):
@@ -0,0 +1,95 @@
1
+ /**
2
+ * agent-model-sync.mjs - installed agents follow the fable switch.
3
+ *
4
+ * prefs.global.modelFallback.fableEnabled is the one switch for the fable rung.
5
+ * The router honours it for every dispatch it resolves, but an agent spawned
6
+ * without an explicit model runs on its frontmatter `model:`. So every installed
7
+ * agent whose `preferredModel` is fable has that line written from the switch:
8
+ * fable while it is on, opus while it is off. Sources keep `model: fable`; only
9
+ * installed copies change. Called by model-rung.sh on every flip and by the
10
+ * installer after it lays the agents down.
11
+ *
12
+ * Usage:
13
+ * node agent-model-sync.mjs [--dir <agents dir>] [--prefs <prefs file>] [--fable-on]
14
+ *
15
+ * --fable-on ignores the switch and writes fable: the sync step uses it to put
16
+ * the repo copy back on its source form.
17
+ *
18
+ * @module pipeline/lib/agent-model-sync
19
+ */
20
+
21
+ import { existsSync, lstatSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
22
+ import { homedir } from "node:os";
23
+ import { join } from "node:path";
24
+ import { pathToFileURL } from "node:url";
25
+
26
+ const FRONTMATTER = /^---\n([\s\S]*?)\n---\n/;
27
+
28
+ /**
29
+ * @param {string} dir - an installed agents directory
30
+ * @param {boolean} fableOn
31
+ * @returns {string[]} the file names whose model line changed
32
+ */
33
+ export function applyFableRung(dir, fableOn) {
34
+ if (!existsSync(dir)) return [];
35
+ // A linked agents directory is the source tree itself (a maintainer's
36
+ // --symlink install); writing there would edit the shipped personas.
37
+ if (lstatSync(dir).isSymbolicLink()) return [];
38
+ const want = fableOn ? "fable" : "opus";
39
+ const changed = [];
40
+ for (const name of readdirSync(dir).sort()) {
41
+ if (!name.endsWith(".md")) continue;
42
+ const path = join(dir, name);
43
+ let text;
44
+ try {
45
+ text = readFileSync(path, "utf8");
46
+ } catch {
47
+ continue;
48
+ }
49
+ const front = FRONTMATTER.exec(text);
50
+ if (!front || !/^preferredModel:\s*fable\s*$/m.test(front[1])) continue;
51
+ const nextFront = front[1].replace(/^model:.*$/m, `model: ${want}`);
52
+ if (nextFront === front[1]) continue;
53
+ const next = text.replace(front[1], nextFront);
54
+ if (lstatSync(path).isSymbolicLink()) rmSync(path);
55
+ writeFileSync(path, next);
56
+ changed.push(name);
57
+ }
58
+ return changed;
59
+ }
60
+
61
+ /**
62
+ * @param {string} prefsPath
63
+ * @returns {boolean}
64
+ */
65
+ export function fableEnabled(prefsPath) {
66
+ try {
67
+ const prefs = JSON.parse(readFileSync(prefsPath, "utf8"));
68
+ return prefs?.global?.modelFallback?.fableEnabled === true;
69
+ } catch {
70
+ return false;
71
+ }
72
+ }
73
+
74
+ function arg(argv, flag) {
75
+ const i = argv.indexOf(flag);
76
+ return i >= 0 ? argv[i + 1] : undefined;
77
+ }
78
+
79
+ const invokedDirectly = process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href;
80
+ if (invokedDirectly) {
81
+ const argv = process.argv.slice(2);
82
+ const claude = join(homedir(), ".claude");
83
+ const dir = arg(argv, "--dir") ?? join(claude, "agents");
84
+ const prefs =
85
+ arg(argv, "--prefs") ??
86
+ (process.env.MULTI_AGENT_PREFS || undefined) ??
87
+ join(claude, "multi-agent-preferences.json");
88
+ const on = argv.includes("--fable-on") || fableEnabled(prefs);
89
+ const changed = applyFableRung(dir, on);
90
+ if (changed.length) {
91
+ process.stdout.write(
92
+ `agents on ${on ? "fable" : "opus"}: ${changed.map((n) => n.replace(/\.md$/, "")).join(", ")}\n`,
93
+ );
94
+ }
95
+ }
@@ -128,6 +128,13 @@ fi
128
128
  mv "$TMP" "$PREFS"
129
129
  trap - EXIT
130
130
 
131
+ # Agents spawned without an explicit model run on their frontmatter, so the
132
+ # installed fable-tier agents follow the switch in the same step.
133
+ if command -v node >/dev/null 2>&1 && [ -f "$_MA_MR_HERE/agent-model-sync.mjs" ]; then
134
+ node "$_MA_MR_HERE/agent-model-sync.mjs" --prefs "$PREFS" ||
135
+ echo "model-rung: installed agents could not be updated; run node $_MA_MR_HERE/agent-model-sync.mjs" >&2
136
+ fi
137
+
131
138
  echo "fable rung: $CUR -> $WANT"
132
139
  echo "cost pricing: $PRICING -> $WANT_PRICING"
133
140
  echo ""
@@ -293,6 +293,16 @@ readiness, not correctness: a machine that fails all four still runs the
293
293
  pipeline perfectly well with someone watching it. What they catch is the
294
294
  failure mode of an unwatched run - stopping without saying so.
295
295
 
296
+
297
+ ### usage-report
298
+
299
+ The outcome `usage-report.mjs` recorded for its last send attempt, read from
300
+ `logs/multi-agent/usage-last-send.json`. SKIP when `usageLog.enabled` is off or
301
+ nothing has been sent yet; OK when the last send was accepted; WARN for every
302
+ other outcome (`redirected`, `rejected`, `not-accepted`, `failed`, `skipped`),
303
+ with the status code or reason and the step that fixes it. The emitter runs
304
+ detached from phase-tracker with its output discarded, so this record is the
305
+ only surface on which a reporting path that stopped working shows up.
296
306
  ### unattended-contract
297
307
 
298
308
  Is `multi-agent-refs/unattended-contract.md` installed. It is the only place
@@ -132,6 +132,16 @@ evidence, and there is now one fewer of them. Triage also runs on `opus`, which
132
132
  makes it the same model as Reviewer 1; the Step 3 anonymisation requirement
133
133
  already covers that case and is not optional here.
134
134
 
135
+ **Installed agents follow the switch.** Five personas declare
136
+ `preferredModel: fable` (code-reviewer, plan-critic, and the iOS, Android and
137
+ backend architects). A dispatch the router resolves passes its model explicitly,
138
+ but an agent spawned without one runs on its frontmatter `model:`. So the
139
+ installed copies are written from `fableEnabled`: `fable` while it is on, `opus`
140
+ while it is off. `model-rung.sh` re-applies them on every flip and the installer
141
+ on every install; sources keep `model: fable`, so `lint-personas.mjs` still holds
142
+ `model === preferredModel` there. A `--symlink` install links the source tree and
143
+ is left alone.
144
+
135
145
  **Commands without Phase 0 resolve it themselves.** `/multi-agent:review`,
136
146
  `/multi-agent:review-analysis` and the untracked-branch tail of
137
147
  `/multi-agent:resume` dispatch reviewers and triage without a Phase 0 Step 0, so
@@ -303,4 +303,4 @@ Scripts: `contract-server.mjs` (phone branch, `isPhonePath`), `_phone-auth.mjs`
303
303
  `unattended-policy.json` (`publisherScripts`). Tests:
304
304
  `test/phone-api.test.mjs`, `test/contract-server.test.mjs`,
305
305
  `test/launch-request.test.mjs`, `test/redact.test.mjs`,
306
- `test/contract-kit.test.mjs`, `test/unattended-guard.test.mjs`.
306
+ `test/contract-kit.test.mjs`, `test/unattended-guard.serial.mjs`.
@@ -35,7 +35,7 @@ the variable the same way.
35
35
 
36
36
  With the variable unset every hook behaves as it did without this feature:
37
37
  `smoke-unattended-redteam.sh` runs the same inputs both ways, and
38
- `test/unattended-guard.test.mjs` holds the attended verdicts.
38
+ `test/unattended-guard.serial.mjs` holds the attended verdicts.
39
39
 
40
40
  ## The guard
41
41
 
@@ -722,7 +722,7 @@ Scripts: `agent-guard.sh`, `agent-guard.py`, `unattended_policy.py`,
722
722
  `lib/outbound-gate.mjs`, `lib/unattended-settings-location.mjs`,
723
723
  `install/_unattended-profile.mjs`. Data:
724
724
  `schemas/unattended-policy.json`, `schemas/unattended-profile.json`,
725
- `schemas/pr-request.schema.json`. Tests: `test/unattended-guard.test.mjs`,
725
+ `schemas/pr-request.schema.json`. Tests: `test/unattended-guard.serial.mjs`,
726
726
  `test/pr-request.test.mjs`, `test/autopilot-publish.test.mjs`,
727
727
  the runner's tests, `test/outbound-gate.test.mjs`,
728
728
  `test/unattended-profile.test.mjs`, `test/agent-guard.test.mjs`; smokes
@@ -147,6 +147,19 @@ reason, `unavailable` with the cause, `declined`, `registered`, `enabled`, `dry-
147
147
  "you turned it off" and "we could not reach the endpoint" are different facts
148
148
  about the same empty panel.
149
149
 
150
+ Every send attempt past the on-switch overwrites
151
+ `~/.claude/logs/multi-agent/usage-last-send.json` with its outcome: `accepted`,
152
+ `not-accepted` (the server answered but did not keep the record), `rejected`
153
+ (with the status code), `redirected` (with the host it pointed to; a redirect is
154
+ never followed, because the token would ride along), `failed` (network or
155
+ timeout), or `skipped` (`no-token`, `no-login`, `endpoint-not-allowed`). It
156
+ carries the run id, the event and the endpoint host, never the token. The
157
+ phase-tracker ping discards the emitter's output, so `/multi-agent:doctor`
158
+ reads this file (check `usage-report`) and turns anything but `accepted` into a
159
+ WARN with one step. `migrate-prefs.mjs` rewrites an endpoint on the platform
160
+ host to the site's domain, so a stored redirecting endpoint does not outlive an
161
+ update.
162
+
150
163
  `prefs.global.usageLog.endpoint` overrides where both calls go - the register URL
151
164
  is derived from it, so a self-hosted ingest gets its own registration rather than
152
165
  this one's. Absent means the shipped default, and every shipped default names the
@@ -99,6 +99,7 @@ const CHECK_IDS = [
99
99
  "mcp-surface",
100
100
  "disk-space",
101
101
  "worktree-residue",
102
+ "usage-report",
102
103
  ];
103
104
 
104
105
  // Only under `--profile server`. Kept in their own list so `--list-checks`
@@ -883,6 +884,52 @@ function checkMcpSurface() {
883
884
  );
884
885
  }
885
886
 
887
+ // usage-report.mjs runs detached with its output discarded, so the record it
888
+ // writes after every send attempt is the one place a broken reporting path
889
+ // becomes visible.
890
+ const USAGE_STEPS = {
891
+ "no-login": () => `run node ${join(HERE, "usage-identity.mjs")} --refresh`,
892
+ "no-token": () => `run node ${join(HERE, "usage-register.mjs")}`,
893
+ "endpoint-not-allowed": () => "set usageLog.endpoint to an https address",
894
+ rejected: () => `run node ${join(HERE, "usage-register.mjs")}`,
895
+ "not-accepted": () => `run node ${join(HERE, "usage-identity.mjs")} --refresh`,
896
+ redirected: (r) =>
897
+ r.location
898
+ ? `set usageLog.endpoint to the address on ${r.location} it redirects to`
899
+ : `run node ${join(HERE, "migrate-prefs.mjs")}`,
900
+ failed: () => "re-run a phase once the reporting endpoint is reachable",
901
+ };
902
+
903
+ function checkUsageReport() {
904
+ const prefs = readJson(join(CLAUDE, "multi-agent-preferences.json"));
905
+ const usage = prefs?.global?.usageLog;
906
+ if (usage?.enabled !== true || usage?.optOut === true) {
907
+ skip("usage-report", "usage reporting is off");
908
+ return;
909
+ }
910
+ const last = readJson(join(CLAUDE, "logs", "multi-agent", "usage-last-send.json"));
911
+ if (!last || typeof last.outcome !== "string") {
912
+ skip("usage-report", "no report has been sent from this machine yet");
913
+ return;
914
+ }
915
+ if (last.outcome === "accepted") {
916
+ ok("usage-report");
917
+ return;
918
+ }
919
+ const key = last.outcome === "skipped" ? last.reason : last.outcome;
920
+ const stepFor = USAGE_STEPS[key] ?? USAGE_STEPS.failed;
921
+ const detail = [last.status, last.reason, last.location && `to ${last.location}`]
922
+ .filter(Boolean)
923
+ .join(", ");
924
+ const when = last.t ? ` at ${last.t}` : "";
925
+ report(
926
+ "usage-report",
927
+ "WARN",
928
+ `the last usage report${last.id ? ` for ${last.id}` : ""} was ${last.outcome}${detail ? ` (${detail})` : ""}${when}, so runs are missing from the usage panel`,
929
+ stepFor(last),
930
+ );
931
+ }
932
+
886
933
  function checkDiskSpace() {
887
934
  let freeGb = null;
888
935
  try {
@@ -1175,6 +1222,7 @@ function main() {
1175
1222
  checkMcpSurface();
1176
1223
  checkDiskSpace();
1177
1224
  checkWorktreeResidue();
1225
+ checkUsageReport();
1178
1226
  if (PROFILE === "server") checkServerProfile(resolved);
1179
1227
 
1180
1228
  const blocked = results.filter((r) => r.severity === "BLOCK");
@@ -15,6 +15,7 @@ import path from "node:path";
15
15
  import os from "node:os";
16
16
  import { fileURLToPath } from "node:url";
17
17
  import { runMain } from "../lib/fatal.mjs";
18
+ import { canonicalEndpoint } from "../lib/usage-endpoint.mjs";
18
19
  import { lockTimeoutFrom, withFileLockSync, writeJsonAtomicSync } from "../lib/json-file-lock.mjs";
19
20
 
20
21
  const DEFAULT_FILE = path.join(os.homedir(), ".claude", "multi-agent-preferences.json");
@@ -445,9 +446,22 @@ function migrate(prefs) {
445
446
  // reportContent / wikiScope structure. Idempotent - safe to re-run.
446
447
  migrateReportChannels(out, changes);
447
448
 
449
+ // 13. usageLog.endpoint on the platform host: it answers every request with a
450
+ // redirect, and the usage clients never follow one with a credential attached.
451
+ migrateUsageEndpoint(out, changes);
452
+
448
453
  return { out, changes, alreadyMigrated: changes.length === 0 };
449
454
  }
450
455
 
456
+ function migrateUsageEndpoint(out, changes) {
457
+ const usage = out.global.usageLog;
458
+ if (!usage || typeof usage !== "object" || typeof usage.endpoint !== "string") return;
459
+ const next = canonicalEndpoint(usage.endpoint);
460
+ if (next === usage.endpoint) return;
461
+ changes.push(`usageLog.endpoint: ${usage.endpoint} → ${next}`);
462
+ usage.endpoint = next;
463
+ }
464
+
451
465
  // v5.7+ sub-migration: legacy jiraCommentDefault / confluenceDefault / wikiDefault booleans
452
466
  // → new reportChannels {pr,jira,confluence,wiki} object + wikiScope array. Also initializes
453
467
  // reportContent + autopilotReportTimeoutSeconds with schema defaults. Old fields are removed
@@ -570,15 +570,22 @@ def _run_base_sha(env=None):
570
570
  return sha if re.match(r"^[0-9a-f]{7,40}$", sha) else None
571
571
 
572
572
 
573
+ _TOP_CACHE = {}
574
+
575
+
573
576
  def _worktree_top(d):
577
+ if d in _TOP_CACHE:
578
+ return _TOP_CACHE[d]
574
579
  try:
575
580
  import subprocess
576
581
 
577
582
  r = subprocess.run(["git", "-C", d, "rev-parse", "--show-toplevel"],
578
583
  capture_output=True, text=True, timeout=3)
579
- return os.path.realpath(r.stdout.strip()) if r.returncode == 0 and r.stdout.strip() else None
584
+ top = os.path.realpath(r.stdout.strip()) if r.returncode == 0 and r.stdout.strip() else None
580
585
  except Exception:
581
- return None
586
+ top = None
587
+ _TOP_CACHE[d] = top
588
+ return top
582
589
 
583
590
 
584
591
  def _tracked_clean(path, env=None, cwd=None):
@@ -609,11 +616,11 @@ def _tracked_clean(path, env=None, cwd=None):
609
616
 
610
617
  # The file's path relative to the worktree root, so the base lookup
611
618
  # names it correctly and a file outside this worktree is rejected.
612
- top = g("rev-parse", "--show-toplevel")
619
+ top = _worktree_top(d)
613
620
  ls = g("ls-files", "--error-unmatch", "--", name)
614
621
  st = g("status", "--porcelain", "--untracked-files=all", "--", name)
615
- in_worktree = top.returncode == 0 and bool(top.stdout.strip())
616
- rel = os.path.relpath(p, os.path.realpath(top.stdout.strip())) if in_worktree else None
622
+ in_worktree = top is not None
623
+ rel = os.path.relpath(p, top) if in_worktree else None
617
624
  at_base = (
618
625
  g("cat-file", "-e", f"{base}:{rel}").returncode == 0
619
626
  and g("diff", "--quiet", base, "--", name).returncode == 0