@mmerterden/multi-agent-pipeline 19.1.1 → 19.1.3

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 CHANGED
@@ -14,6 +14,43 @@ Internal file-layout changes that don't affect the slash-command surface are sti
14
14
 
15
15
  ---
16
16
 
17
+ ## [19.1.3] - 2026-09-21
18
+
19
+ ### Fixed
20
+
21
+ - **Routing no longer hands back a rung the caller cannot dispatch to.** The
22
+ router permitted a non-Anthropic rung at `bulk-read`, and `bulk-read.sh`
23
+ passes the rung straight to `claude -p --model <rung>` - a CLI that speaks the
24
+ Anthropic ladder and nothing else. `research_ask` never consulted the router
25
+ at all. So no call site could honour an external rung, and the permission was
26
+ empty: a rule naming one produced a failed call rather than a cheaper one.
27
+
28
+ `model-dispatch.sh` now refuses a non-Anthropic rung at every call site and
29
+ falls to the NEXT rung the rule named, not to the caller's default - the user
30
+ asked for a preference order, and only the part that cannot be honoured is
31
+ dropped. `provider` in `cost-table.json` stays, because it is what makes the
32
+ refusal checkable and is the seam a future call site would use.
33
+
34
+ `smoke-model-dispatch.sh` asserts the reason rather than the rule:
35
+ `bulk-read.sh` still dispatches through the Anthropic CLI. The day a call site
36
+ grows its own provider path, that assertion is what says the refusal may be
37
+ lifted.
38
+
39
+ ---
40
+
41
+ ## [19.1.2] - 2026-09-21
42
+
43
+ ### Added
44
+
45
+ - **`doctor` reports the update policy.** `update-policy` prints the effective
46
+ `updateCheck.enabled`, `.ttlHours` and `.autoUpdate`, and says which of them
47
+ came from the schema rather than from the preferences file. The knob that
48
+ decides whether a run updates itself before it starts was otherwise only
49
+ discoverable by reading the schema. Reported, never written - a preferences
50
+ file is the user's, and a diagnostic that edits one is not a diagnostic.
51
+
52
+ ---
53
+
17
54
  ## [19.1.1] - 2026-09-21
18
55
 
19
56
  ### Changed
package/docs/facts.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$comment": "Generated by pipeline/scripts/gen-facts.mjs. Do not hand-edit: the site reads this, and a number edited here instead of at its source is the drift this file removes.",
3
- "generatedAt": "2026-09-20",
4
- "version": "19.1.1",
3
+ "generatedAt": "2026-09-21",
4
+ "version": "19.1.3",
5
5
  "phaseSchema": 2,
6
6
  "phases": [
7
7
  {
package/manifest.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@mmerterden/multi-agent-pipeline",
3
- "version": "19.1.1",
4
- "commit": "f45e4e1e3b101d0c795794793b6274ddd72d6ded",
5
- "generatedAt": "2026-09-20T20:39:30.640Z",
3
+ "version": "19.1.3",
4
+ "commit": "ab89855c58cbddec0ea834d80efc7dfc3f630c69",
5
+ "generatedAt": "2026-09-21T06:32:52.127Z",
6
6
  "algorithm": "sha256",
7
7
  "fileCount": 1057,
8
8
  "files": {
9
- "CHANGELOG.md": "26f5a11dbe3b8ef1db89e2e2cfa38005709394c11e2be9bd8c9b0122d3f5f40c",
9
+ "CHANGELOG.md": "a17d55e5b1f00236a491817510d60a845f6ff31a9b466dc56fed3daf19cb2deb",
10
10
  "LICENSE": "8d8c51aaf1e1538b180c69965fa038db71be86798cc3e26a143bebfefe16a43e",
11
11
  "README.md": "63cba0443ff6750cf979ca39093f80f414a9ffa72bdf08a196a4fb821520fb5d",
12
12
  "README.tr.md": "429823b93ae79e783ac91559251ff741d01b147146fffb6fb30f8a980ca157c2",
@@ -31,7 +31,7 @@
31
31
  "docs/best-practices.md": "aeaaa12d518233cb09577c1dbaee00d62cd26f9f7e0400c681fe38742d6a7ba2",
32
32
  "docs/ecosystem.md": "051520cefee38d34894ef563ab987be15dc47cc9172fc6692ee440a77fa1f2c0",
33
33
  "docs/engineering.md": "550528cd47c7d2c921729c68d0782a00ac8e77a4bdfec479f6749f9c3235d188",
34
- "docs/facts.json": "91633e93a671f2d32ce1b1bfb1b797bda6e8430108e2a70e5f662654321de728",
34
+ "docs/facts.json": "58ed4849960aee5efe4cbbb66e576db5b077468e9bcb6f936bae4cbcba6fe51d",
35
35
  "docs/features.md": "927e5e54f7b1348ce2cddecb0d11115dc5ae9db20558b12d67932d38b26dad3f",
36
36
  "docs/performance.md": "bbf2f3922c25dcba0c1a9e69ef420e3871a729af5b35009a49b48a5d3c5cd151",
37
37
  "docs/recovery-guide.md": "1cc014295f8c8ffba1f1c44397b657e925f1bc0e4757217d6e69e8cfb271f3cb",
@@ -58,7 +58,7 @@
58
58
  "install/templates/codex-instructions.md": "dd78eb9553e725c039380457245793a5fc748ae37ebf5ccfe11be5ed5b9a4025",
59
59
  "install/templates/copilot-instructions.md": "18161f2a8c995044aeae69d47200795b8babcf8b80d5e09e8d91dcfb7c841ce2",
60
60
  "install/templates/multi-agent-autopilot.plist.template": "d79de637b17330e720be43b96bfcdd51797bd9ce219eafaa3dd5a8ece96e4814",
61
- "package.json": "8cf3ab5b8ab1eb0b5449c89fe00412623fa9b20e3afa41fe6868ae6bf3a07e0c",
61
+ "package.json": "0fab4746bdc36850af7a7e5900578ea934202f13e8bbe71831ab619a4e28c9a0",
62
62
  "pipeline/agents/android-architect.md": "baa5787b3a4c800dd065e27e05f44462927c9938c8bb676c80f18cb10e2b12c4",
63
63
  "pipeline/agents/backend-architect.md": "a83bd7f8b0a5289a1ef9a9b59d60fc759b8db260606ff2df427cf5ade5db8530",
64
64
  "pipeline/agents/bulk-reader.md": "0b9d415fab2c449d43329d759049af9401255d06b32d57a38c95e684837b19bf",
@@ -164,7 +164,7 @@
164
164
  "pipeline/lib/issue-fetcher.sh": "c496ad6b07c0f6c64c69cb6293a0a4f3010b3ce3945a525e602c0d51c665ac67",
165
165
  "pipeline/lib/jira-publish.sh": "a32e6a682ae730ddd9c6df64b9ece1fb73c797111c63f5c247d7e733668da6b7",
166
166
  "pipeline/lib/md2confluence-v3.py": "3677ca47931e4b3d0fd2ce3e11299fc5f6d24799eb8332e37dd60b6c3c4653a2",
167
- "pipeline/lib/model-dispatch.sh": "929b665bda7116da219cc9e0f3e0b17b1e73472482293d251803e35822c8daa0",
167
+ "pipeline/lib/model-dispatch.sh": "467ee01628c2a4a706410fdc7b04b0dd92ad2c2cbdd2150f6fd91f2d7a773796",
168
168
  "pipeline/lib/model-rung.sh": "befcd1a250a04f9ea9c8dca18c9d8905286330197e54da41ad4fd12b845f604d",
169
169
  "pipeline/lib/multi-repo-pipeline.sh": "392b06b4d40924b16a74d85dfceb68a5aeed8a50aed832a5ca92f27e2a5d7e80",
170
170
  "pipeline/lib/outbound-gate.mjs": "76547f21ca92793f4c59e1dbf91c498278614281607e3ecbeeddbafe7e3a6a56",
@@ -218,11 +218,11 @@
218
218
  "pipeline/multi-agent-refs/features/cost-analysis.md": "42f1b90b41ef454f29ccfd3f93c959920f0e74cbca65e140695e9a30d6d90b87",
219
219
  "pipeline/multi-agent-refs/features/design-conformance.md": "8bcc1c9221f39ec8764c6aa20e59f968d6beb777f644c61adf7a38e35af1a9d5",
220
220
  "pipeline/multi-agent-refs/features/dev-critic.md": "08e01edef04b5ac216f8f43332126966d33da7b95625e47a8a6cd8ac105b263f",
221
- "pipeline/multi-agent-refs/features/doctor.md": "61fb489841e408c275d4d82769930544b13de9aa61b94656e77238bc9908cb89",
221
+ "pipeline/multi-agent-refs/features/doctor.md": "7fb8f00dfca3d44fa256b97f2669d414d27fe73408e9b2c651067f042c8a73b8",
222
222
  "pipeline/multi-agent-refs/features/external-context-injection.md": "8fcf5229f92276af7714638602ec4da076f9f3f0a170303e3c1e178f55e39863",
223
223
  "pipeline/multi-agent-refs/features/jira-context.md": "6f6fe697451e061909aa125366defbcc0baae5cecd8341bfabbad09011256684",
224
224
  "pipeline/multi-agent-refs/features/maturity-followup.md": "2a6166869101b00ea273b92e3fe1ea07b375a0190aaab6b42ff5691db3748eec",
225
- "pipeline/multi-agent-refs/features/model-fallback.md": "fb982661127075930450cfff89857c892569e5068b60fab2fb59af2fc5613791",
225
+ "pipeline/multi-agent-refs/features/model-fallback.md": "7ac6a048c3ac2c494fbe03b027850703dba0995dc9dc0c63ceb14e0a8c8925b2",
226
226
  "pipeline/multi-agent-refs/features/package-manager.md": "b9e52f798f5dcba43abff1fe5cad8d5d5374058c9a6847c993975c665acab05a",
227
227
  "pipeline/multi-agent-refs/features/plan-todos.md": "8780d80b4541daee2b05080759a0694ef7e6270abe9a4621deb92ec7bac94e8e",
228
228
  "pipeline/multi-agent-refs/features/prior-fix-detection.md": "f194c444f1685bb103e1aa16164ca6737ba9910b7c8f89874924da950838b074",
@@ -320,7 +320,7 @@
320
320
  "pipeline/schemas/prefs.schema.json": "1a5e041385d251fb62fa39771ca0a357b9ce2270d39fe0b3dd84c9fc0f4c8f7b",
321
321
  "pipeline/schemas/review-file-exclusions.json": "2e8c44b45d21d2c82fc95aa2bf76ad3fbf2d5c68180c7b2ebf89c4a926688d77",
322
322
  "pipeline/schemas/reviewer-output.schema.json": "1bc8dd8feb416ea2101457e071d72b5a6ab0aefa7da3d42063bbe1c153755754",
323
- "pipeline/schemas/route-config.schema.json": "ddade61393ee7c2f5863ca26324af70a1d2153ef7221526878ea07a4d5c9b54c",
323
+ "pipeline/schemas/route-config.schema.json": "6f606926c257b7ad3d2972e53d0b772714774140c473b9cb8c7cd9e52731020f",
324
324
  "pipeline/schemas/scope-check.schema.json": "f96973c59372b36b7d68b9ac4b03490e7da8d54acf29a35947f5f610e82b98ea",
325
325
  "pipeline/schemas/secret-patterns.json": "d9a7bec7b2ba704b73f2302c3498874c6298cde69bde0f181932c282d3fe0b0e",
326
326
  "pipeline/schemas/skill-observation.schema.json": "05f16c6e5055ef99a102b12b578ba841251267d41dd7343f8ab66cd8070180ff",
@@ -374,7 +374,7 @@
374
374
  "pipeline/scripts/crush-json.mjs": "303382670f8f170c8c6710202853a2de2915625813cd157185f9be4e2b27258e",
375
375
  "pipeline/scripts/diff-explain.mjs": "79137dafe5a3b90a5496611c215669f26d92ab070dc4a71f1d37ed095a49c471",
376
376
  "pipeline/scripts/diff-risk-score.mjs": "b3e8da42fea1258162e40f4eb7809074831339b7563874527f7e825f7aa795ca",
377
- "pipeline/scripts/doctor.mjs": "06ba164a2b869256ddb140d86af6f59129304c142218ccbb98a9b284e32055a7",
377
+ "pipeline/scripts/doctor.mjs": "73edeadd54d9cca035bf94b80e7f394da6e0692d3e803da1baa37db76f44e3ce",
378
378
  "pipeline/scripts/evidence-gate.mjs": "54bd99ad7ead241cdb9b6281994dac31695fd0c7cf92222249803abc1a48f78a",
379
379
  "pipeline/scripts/feedback-send.mjs": "8ec3f8fcafbe0de6c916a13a151d009cb804522537621b239a183591dddeec85",
380
380
  "pipeline/scripts/finding-fingerprint.mjs": "fa12ab10701db5d2bfff014e41d1cd4c0b2b772aee5fc69205de14b2761b0c83",
@@ -480,7 +480,7 @@
480
480
  "pipeline/scripts/website-deploy-commit.sh": "523482438b265ca1f1883ec5fb450e803566a1239eaccdc423e12c5426140a84",
481
481
  "pipeline/scripts/worktree-finalize.sh": "f905ba69272d32afc75b5deb8406efabfe0865bdeec2e150a521d5a1b0c51b8a",
482
482
  "pipeline/scripts/write-state.mjs": "1367280057d2b747da55ba62af6f884a647f2675c0bde10c39dc68c84ff7daac",
483
- "pipeline/skills/.skill-manifest.json": "22eac6907c5d5e7a774d5d64148645f633863b60335596bb2dcee1020f787e63",
483
+ "pipeline/skills/.skill-manifest.json": "fc25b360a03932f8c5575e337cc476524d15c199fb6ff8e76b23fe3668267f22",
484
484
  "pipeline/skills/.skills-index.json": "05545f7603267576f614d6eedd0369bfcf5b8d1aadbf4c791295559f930b4bb2",
485
485
  "pipeline/skills/shared/README.md": "12692c36caaf2f4567cf21b788dad66e1bc34cae345dc8045e0569834b819414",
486
486
  "pipeline/skills/shared/core/apple-archive-compliance/SKILL.md": "6675a53441f2f3bedc82284998f402bd51fa251c7491a595e12c6c4016830337",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mmerterden/multi-agent-pipeline",
3
- "version": "19.1.1",
3
+ "version": "19.1.3",
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",
@@ -22,10 +22,16 @@
22
22
  #
23
23
  # Layer 1 an Anthropic rung. Reachable from every call site, because it
24
24
  # writes to a seam that already exists and opens no new network path.
25
- # Layer 2 a non-Anthropic rung. Reachable ONLY from a call site the pipeline
26
- # makes itself. A subagent is dispatched by the HOST, so a rule that
27
- # prefers an external rung for a subagent cannot be honoured - and
28
- # this script says so on stderr rather than pretending it was.
25
+ # Layer 2 a non-Anthropic rung. NO call site can dispatch to one today, and
26
+ # the router refuses it rather than returning a rung its caller will
27
+ # hand to a CLI that does not know the name:
28
+ # - a subagent is dispatched by the HOST;
29
+ # - bulk-read shells out to `claude -p --model <rung>`;
30
+ # - research_ask runs in the MCP server and picks its own model.
31
+ # `provider` in cost-table.json is what makes this checkable, and it
32
+ # is the seam a future call site would use. Until one exists, the
33
+ # refusal is the honest answer: an allowed rung nothing can honour is
34
+ # a rule the user believes worked.
29
35
 
30
36
  set -uo pipefail
31
37
 
@@ -124,12 +130,12 @@ for rung in $MATCH; do
124
130
  echo "model-dispatch: rule prefers 'fable' but modelFallback.fableEnabled is false; skipping it" >&2
125
131
  continue
126
132
  fi
127
- if [ "$PROVIDER" != "anthropic" ] && [ "$CALL_SITE" = "subagent" ]; then
128
- # The honest limit, said out loud at the moment it bites. Subagent dispatch
129
- # belongs to the host; we cannot send one to another provider, and a router
130
- # that quietly downgraded to an Anthropic rung here would leave the user
131
- # believing a rule worked that never could.
132
- echo "model-dispatch: rung '$rung' is $PROVIDER and subagent dispatch belongs to the host; skipping it" >&2
133
+ if [ "$PROVIDER" != "anthropic" ]; then
134
+ # The honest limit, said out loud at the moment it bites. Every call site
135
+ # that asks here hands the rung to something that only speaks the Anthropic
136
+ # ladder, so returning an external rung would produce a failed call rather
137
+ # than a cheaper one.
138
+ echo "model-dispatch: rung '$rung' is $PROVIDER and no call site can dispatch to one yet; skipping it" >&2
133
139
  continue
134
140
  fi
135
141
  record "$rung" "rule matched"
@@ -126,6 +126,15 @@ The installed `.pipeline-version` matches the package version resolved from the
126
126
  checkout or the published package. WARN on a mismatch: the run works, it just
127
127
  is not the version the user thinks they have.
128
128
 
129
+ ### update-policy
130
+
131
+ Whether a run updates itself before it starts, read the way the run reads it:
132
+ `updateCheck.enabled`, `.ttlHours` and `.autoUpdate`. The schema defaults all
133
+ three, so a preferences file that omits them behaves exactly like one that sets
134
+ them - which is why they are worth reporting. INFO when a value comes from the
135
+ schema rather than from the file, so the policy is legible in the place somebody
136
+ would change it. Reported, never written: a preferences file is the user's.
137
+
129
138
  ### script-surface
130
139
 
131
140
  Every script a shipped command names by path exists in the installed tree. BLOCK:
@@ -193,13 +193,17 @@ Two limits are enforced by the script rather than written here and hoped for:
193
193
  - **A rule may not turn the fable rung on.** With `modelFallback.fableEnabled`
194
194
  false, a rule preferring `fable` falls past it to the next rung. Otherwise
195
195
  `/multi-agent:model off` would have a second door.
196
- - **A subagent stays inside the Anthropic ladder.** Subagent dispatch belongs to
197
- the host, so a rule preferring a non-Anthropic rung cannot be honoured there;
198
- the script says so on stderr and moves on rather than substituting an
199
- Anthropic rung and leaving the user believing a rule worked that never could.
200
- `bulk-read.sh` is a call site the pipeline makes itself, so the same rung IS
201
- allowed there. That asymmetry is why `/multi-agent:route-status` reports scope
202
- per call site instead of one on/off.
196
+ - **Every call site stays inside the Anthropic ladder.** No call site can
197
+ dispatch to another provider today: a subagent is dispatched by the host,
198
+ `bulk-read.sh` shells out to `claude -p --model <rung>`, and `research_ask`
199
+ runs in the MCP server and picks its own model. A rule preferring an external
200
+ rung falls to the next rung the rule named, and the script says why on stderr.
201
+ Returning the external rung instead would hand a caller a name it passes
202
+ straight to a CLI that does not know it - a failed call, not a cheaper one.
203
+ `provider` in `cost-table.json` is what makes the refusal checkable and is the
204
+ seam a future call site would use; `smoke-model-dispatch.sh` asserts that
205
+ `bulk-read.sh` still dispatches through the Anthropic CLI, so the day that
206
+ changes, the gate says the refusal may be lifted.
203
207
 
204
208
  The script never fails and never returns an empty rung: a missing preferences
205
209
  file, a missing `jq`, unparseable JSON, an out-of-scope call site and a rung
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://github.com/mmerterden/multi-agent-pipeline/pipeline/schemas/route-config.schema.json",
4
4
  "title": "Model routing configuration",
5
- "description": "Policy-driven model selection, OFF by default. Lives under prefs.global.modelRouting. Two layers, and the difference is the whole safety argument. Layer 1 chooses a rung INSIDE the Anthropic ladder and writes to the existing PHASE_MODEL_OVERRIDE seam - no new network path, no new account. Layer 2 sends a call to a non-Anthropic provider, and only at the call sites the pipeline makes itself (bulk-read.sh, research_ask), never at a call site the host owns. `scope` has no `host-session` member and that absence is enforced here rather than written in prose: rewriting the host's base URL would route the user's ENTIRE session, including work that has nothing to do with this pipeline, through a third layer - breaking the subscription's auth model and silently changing which model answers. Shipping this disabled is the same reasoning as modelFallback.fableEnabled: a cost control that is on by default is not a control.",
5
+ "description": "Policy-driven model selection, OFF by default. Lives under prefs.global.modelRouting. Two layers, and the difference is the whole safety argument. Layer 1 chooses a rung INSIDE the Anthropic ladder and writes to the existing PHASE_MODEL_OVERRIDE seam - no new network path, no new account. Layer 2 would send a call to a non-Anthropic provider, and no call site can do that yet: a subagent is dispatched by the host, bulk-read.sh shells out to `claude -p --model <rung>`, and research_ask runs in the MCP server and picks its own model. model-dispatch.sh therefore refuses a non-Anthropic rung everywhere and falls to the rule's next rung, because an allowed rung nothing can honour is a rule the user believes worked. `provider` in cost-table.json is the seam a future call site would use. `scope` has no `host-session` member and that absence is enforced here rather than written in prose: rewriting the host's base URL would route the user's ENTIRE session, including work that has nothing to do with this pipeline, through a third layer - breaking the subscription's auth model and silently changing which model answers. Shipping this disabled is the same reasoning as modelFallback.fableEnabled: a cost control that is on by default is not a control.",
6
6
  "type": "object",
7
7
  "additionalProperties": false,
8
8
  "required": ["enabled"],
@@ -80,6 +80,7 @@ const CHECK_IDS = [
80
80
  "host-platform",
81
81
  "install-present",
82
82
  "install-version",
83
+ "update-policy",
83
84
  "script-surface",
84
85
  "skill-siblings",
85
86
  "state-writable",
@@ -240,6 +241,43 @@ function checkInstallVersion() {
240
241
  ok("install-version");
241
242
  }
242
243
 
244
+ // Whether a run updates itself before it starts, read the way the run reads it.
245
+ //
246
+ // The schema defaults `enabled`, `ttlHours` and `autoUpdate`, so a preferences
247
+ // file with no `updateCheck` block behaves exactly like one that sets all three
248
+ // - and that is the problem this reports. The setting that decides whether a
249
+ // run silently updates itself is then invisible in the file somebody opens to
250
+ // change it, and the only way to learn the answer is to read the schema.
251
+ //
252
+ // Reported, never written. A preferences file is the user's, and a diagnostic
253
+ // that edits one is not a diagnostic.
254
+ function checkUpdatePolicy() {
255
+ const prefs = readJson(join(CLAUDE, "multi-agent-preferences.json"));
256
+ if (!prefs) {
257
+ skip("update-policy", "no preferences file to read");
258
+ return;
259
+ }
260
+ const uc = prefs.global?.updateCheck;
261
+ const effective = {
262
+ enabled: uc?.enabled ?? true,
263
+ ttlHours: uc?.ttlHours ?? 24,
264
+ autoUpdate: uc?.autoUpdate ?? true,
265
+ };
266
+ const spelled = uc && typeof uc === "object" ? Object.keys(uc) : [];
267
+ const missing = ["enabled", "ttlHours", "autoUpdate"].filter((k) => !spelled.includes(k));
268
+ const summary = `enabled=${effective.enabled}, autoUpdate=${effective.autoUpdate}, ttlHours=${effective.ttlHours}`;
269
+ if (missing.length === 0) {
270
+ ok("update-policy");
271
+ return;
272
+ }
273
+ report(
274
+ "update-policy",
275
+ "INFO",
276
+ `${summary} - ${missing.join(", ")} come from the schema, not from your preferences file`,
277
+ "set global.updateCheck in ~/.claude/multi-agent-preferences.json to make the policy explicit",
278
+ );
279
+ }
280
+
243
281
  function checkScriptSurface() {
244
282
  const cmdDir = join(CLAUDE, "commands", "multi-agent");
245
283
  if (!existsSync(cmdDir)) {
@@ -957,6 +995,7 @@ function main() {
957
995
  }
958
996
 
959
997
  checkInstallVersion();
998
+ checkUpdatePolicy();
960
999
  checkScriptSurface();
961
1000
  checkSkillSiblings();
962
1001
  checkStateWritable();
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": "1.0.0",
3
- "generatedAt": "2026-09-20T20:24:07Z",
3
+ "generatedAt": "2026-09-21T06:17:16Z",
4
4
  "skillCount": 216,
5
5
  "entries": [
6
6
  {