@event4u/agent-config 3.0.0 → 3.1.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.
Files changed (207) hide show
  1. package/.agent-src/commands/install-via-agent.md +129 -0
  2. package/.agent-src/commands/video/from-script.md +1 -1
  3. package/.agent-src/commands/video.md +1 -1
  4. package/.agent-src/contexts/execution/cheap-question-mechanics.md +81 -0
  5. package/.agent-src/rules/caveman-speak.md +2 -2
  6. package/.agent-src/rules/context-hygiene.md +36 -0
  7. package/.agent-src/rules/engineering-safety-floor.md +102 -0
  8. package/.agent-src/rules/finance-safety-floor.md +114 -0
  9. package/.agent-src/rules/git-history-discipline.md +1 -1
  10. package/.agent-src/rules/no-cheap-questions.md +34 -32
  11. package/.agent-src/rules/provider-lifecycle-discipline.md +4 -4
  12. package/.agent-src/rules/strategy-safety-floor.md +114 -0
  13. package/.agent-src/skills/agents-md-thin-root/SKILL.md +15 -9
  14. package/.agent-src/skills/async-python-patterns/SKILL.md +1 -1
  15. package/.agent-src/skills/project-analysis-node-express/SKILL.md +1 -1
  16. package/.agent-src/skills/readme-reviewer/SKILL.md +52 -3
  17. package/.agent-src/skills/readme-writing/SKILL.md +52 -4
  18. package/.agent-src/skills/readme-writing-package/SKILL.md +48 -5
  19. package/.agent-src/skills/systematic-debugging/SKILL.md +41 -0
  20. package/.agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  21. package/.agent-src/templates/hooks/pre-commit-frontmatter +66 -0
  22. package/.agent-src/templates/hooks/pre-commit-roadmap-progress +78 -39
  23. package/.agent-src/templates/scripts/work_engine/_lib/agent_settings.py +4 -1
  24. package/.agent-src/templates/scripts/work_engine/orchestration.py +25 -11
  25. package/.claude-plugin/marketplace.json +2 -1
  26. package/AGENTS.md +10 -8
  27. package/CHANGELOG.md +223 -125
  28. package/README.md +165 -553
  29. package/config/agent-settings.template.yml +0 -7
  30. package/config/discovery/packs.yml +20 -0
  31. package/config/discovery/unassigned-artefacts.yml +2 -0
  32. package/config/gitignore-block.txt +19 -3
  33. package/dist/cli/commands/uiServe.js +13 -4
  34. package/dist/cli/commands/uiServe.js.map +1 -1
  35. package/dist/cli/registry.js +2 -0
  36. package/dist/cli/registry.js.map +1 -1
  37. package/dist/discovery/deprecation-report.md +7 -0
  38. package/dist/discovery/discovery-manifest.json +2107 -1409
  39. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  40. package/dist/discovery/discovery-manifest.summary.md +9 -9
  41. package/dist/discovery/orphan-report.md +10 -0
  42. package/dist/discovery/packs.json +1002 -0
  43. package/dist/discovery/trust-report.md +26 -0
  44. package/dist/discovery/workspaces.json +705 -0
  45. package/dist/mcp/registry-manifest.json +4 -4
  46. package/dist/router.json +1623 -0
  47. package/dist/server/app.js +11 -3
  48. package/dist/server/app.js.map +1 -1
  49. package/dist/server/io/atomicMultiWrite.js +3 -1
  50. package/dist/server/io/atomicMultiWrite.js.map +1 -1
  51. package/dist/server/io/yamlIO.js +22 -0
  52. package/dist/server/io/yamlIO.js.map +1 -1
  53. package/dist/server/routes/ping.js +8 -0
  54. package/dist/server/routes/ping.js.map +1 -1
  55. package/dist/server/routes/schema.js +2 -2
  56. package/dist/server/routes/schema.js.map +1 -1
  57. package/dist/server/routes/settings.js +104 -23
  58. package/dist/server/routes/settings.js.map +1 -1
  59. package/dist/server/routes/userMd.js +37 -27
  60. package/dist/server/routes/userMd.js.map +1 -1
  61. package/dist/server/routes/wizard.js +256 -20
  62. package/dist/server/routes/wizard.js.map +1 -1
  63. package/dist/server/schemas/settings.js +0 -1
  64. package/dist/server/schemas/settings.js.map +1 -1
  65. package/dist/server/token.js +10 -3
  66. package/dist/server/token.js.map +1 -1
  67. package/dist/server/writeRoot.js +28 -11
  68. package/dist/server/writeRoot.js.map +1 -1
  69. package/dist/server/writeRoot.test.js +22 -4
  70. package/dist/server/writeRoot.test.js.map +1 -1
  71. package/dist/shared/userMd/formAdapter.js +29 -51
  72. package/dist/shared/userMd/formAdapter.js.map +1 -1
  73. package/dist/shared/userMd/schema.js +32 -104
  74. package/dist/shared/userMd/schema.js.map +1 -1
  75. package/dist/shared/userMd/utils.js +64 -50
  76. package/dist/shared/userMd/utils.js.map +1 -1
  77. package/dist/ui/assets/index-D-DY1ywI.js +35 -0
  78. package/dist/ui/assets/index-D-DY1ywI.js.map +1 -0
  79. package/dist/ui/index.html +1 -1
  80. package/docs/adrs/router/0001-three-tier-routing.md +5 -5
  81. package/docs/adrs/smoke/0001-per-tier-smoke-scripts.md +1 -1
  82. package/docs/architecture.md +3 -3
  83. package/docs/archive/CHANGELOG-pre-3.1.0.md +167 -0
  84. package/docs/catalog.md +30 -26
  85. package/docs/contracts/CHANGELOG-conventions.md +1 -1
  86. package/docs/contracts/agent-user-schema.md +6 -9
  87. package/docs/contracts/consumer-bridge.md +79 -0
  88. package/docs/contracts/discovery-manifest.md +209 -0
  89. package/docs/contracts/discovery-manifest.schema.json +77 -4
  90. package/docs/contracts/explain-trace.schema.json +1 -1
  91. package/docs/contracts/file-ownership-matrix.json +197 -13
  92. package/docs/contracts/frontmatter-contract.md +140 -0
  93. package/docs/contracts/gui-wizard.md +223 -0
  94. package/docs/contracts/installer-agent-mode.md +137 -0
  95. package/docs/contracts/kernel-membership.md +1 -1
  96. package/docs/contracts/mcp-tool-inventory.md +9 -9
  97. package/docs/contracts/namespace.md +6 -6
  98. package/docs/contracts/provider-lifecycle.md +5 -5
  99. package/docs/contracts/rule-router.md +4 -4
  100. package/docs/contracts/settings-api.md +53 -6
  101. package/docs/contracts/smoke-contracts.md +3 -3
  102. package/docs/contracts/trust-and-safety.md +144 -0
  103. package/docs/customization.md +2 -2
  104. package/docs/decisions/ADR-007-agent-discovery-scopes.md +12 -0
  105. package/docs/decisions/ADR-013-discovery-frontmatter-contract.md +24 -0
  106. package/docs/decisions/ADR-015-discovery-manifest-contract.md +146 -0
  107. package/docs/decisions/ADR-016-installer-architecture.md +189 -0
  108. package/docs/decisions/ADR-017-monorepo-physical-layout.md +261 -0
  109. package/docs/decisions/ADR-018-trust-and-safety-layer.md +159 -0
  110. package/docs/decisions/ADR-019-router-json-dist-location.md +124 -0
  111. package/docs/decisions/ADR-020-global-only-consumer-scope.md +123 -0
  112. package/docs/decisions/ADR-021-deployment-shape.md +153 -0
  113. package/docs/decisions/INDEX.md +7 -0
  114. package/docs/deploy/connector-setup.md +129 -0
  115. package/docs/deploy/env-vars.md +70 -0
  116. package/docs/deploy/policy-cookbook.md +130 -0
  117. package/docs/deploy/quickstart.md +112 -0
  118. package/docs/distribution/public-install-smoke.md +68 -0
  119. package/docs/distribution/registries.md +55 -0
  120. package/docs/distribution/telemetry-privacy.md +128 -0
  121. package/docs/distribution/telemetry-schema.md +174 -0
  122. package/docs/featured-skills.md +95 -0
  123. package/docs/getting-started-by-role.md +19 -1
  124. package/docs/getting-started.md +2 -2
  125. package/docs/guidelines/agent-infra/installed-tools-manifest.md +11 -8
  126. package/docs/guidelines/docs/readme-size-and-splitting.md +53 -1
  127. package/docs/installation.md +27 -14
  128. package/docs/maintainers/dev-mode.md +105 -0
  129. package/docs/setup/per-ide/claude-desktop.md +3 -2
  130. package/docs/wizard.md +39 -4
  131. package/package.json +18 -1
  132. package/scripts/__pycache__/validate_frontmatter.cpython-312.pyc +0 -0
  133. package/scripts/_cli/cmd_doctor.py +150 -2
  134. package/scripts/_cli/cmd_explain.py +2 -1
  135. package/scripts/_cli/cmd_migrate_to_global.py +415 -0
  136. package/scripts/_cli/cmd_settings_migrate.py +146 -0
  137. package/scripts/_cli/explain_last/route.py +2 -1
  138. package/scripts/_dispatch.bash +36 -3
  139. package/scripts/_lib/__pycache__/__init__.cpython-312.pyc +0 -0
  140. package/scripts/_lib/__pycache__/agent_src.cpython-312.pyc +0 -0
  141. package/scripts/_lib/agent_settings.py +4 -1
  142. package/scripts/_lib/agent_src.py +157 -0
  143. package/scripts/agent-config +17 -6
  144. package/scripts/audit_skill_descriptions.py +18 -6
  145. package/scripts/build_discovery_manifest.py +373 -17
  146. package/scripts/check_artefact_checksums.py +104 -0
  147. package/scripts/check_cluster_patterns.py +20 -4
  148. package/scripts/check_command_count_messaging.py +33 -14
  149. package/scripts/check_council_references.py +43 -4
  150. package/scripts/check_overlay_cascade_subdirs.py +7 -3
  151. package/scripts/check_references.py +5 -2
  152. package/scripts/check_reply_consistency.py +32 -9
  153. package/scripts/check_template_pin_drift.py +24 -7
  154. package/scripts/check_token_optimizer_freshness.py +18 -3
  155. package/scripts/compile_router.py +34 -2
  156. package/scripts/compress.py +162 -44
  157. package/scripts/config/presets.py +19 -1
  158. package/scripts/config/profiles.py +16 -1
  159. package/scripts/discovery_stats.py +70 -0
  160. package/scripts/expected_perms.json +47 -0
  161. package/scripts/generate_index.py +78 -46
  162. package/scripts/generate_ownership_matrix.py +98 -43
  163. package/scripts/generate_pack_manifests.py +183 -0
  164. package/scripts/install +18 -1
  165. package/scripts/install.py +934 -59
  166. package/scripts/install.sh +27 -9
  167. package/scripts/lint_agents_layout.py +93 -13
  168. package/scripts/lint_agents_md.py +1 -1
  169. package/scripts/lint_archived_skills.py +32 -16
  170. package/scripts/lint_bench_corpus.py +14 -2
  171. package/scripts/lint_command_tiers.py +15 -2
  172. package/scripts/lint_featured_skills.py +139 -0
  173. package/scripts/lint_framework_leakage.py +33 -6
  174. package/scripts/lint_global_paths.py +147 -0
  175. package/scripts/lint_orchestration_dsl.py +6 -3
  176. package/scripts/lint_pack_boundaries.py +147 -0
  177. package/scripts/lint_pack_first_win.py +103 -0
  178. package/scripts/lint_readme_jargon.py +131 -0
  179. package/scripts/lint_readme_size.py +33 -0
  180. package/scripts/lint_rule_interactions.py +23 -5
  181. package/scripts/lint_rule_tiers.py +12 -3
  182. package/scripts/lint_trust_coherence.py +212 -0
  183. package/scripts/measure_rule_budget.py +22 -4
  184. package/scripts/move_artefact.py +143 -0
  185. package/scripts/new_skill.py +148 -0
  186. package/scripts/plan_physical_move.py +353 -0
  187. package/scripts/refine_ticket_detect.py +30 -7
  188. package/scripts/schemas/command.schema.json +4 -0
  189. package/scripts/skill_linter.py +248 -118
  190. package/scripts/skill_trigger_eval.py +28 -8
  191. package/scripts/smoke/kernel.sh +1 -1
  192. package/scripts/smoke/router.sh +24 -5
  193. package/scripts/smoke/skills.sh +15 -7
  194. package/scripts/smoke_quickstart.py +11 -2
  195. package/scripts/snapshot_agent_outputs.py +144 -0
  196. package/scripts/update_counts.py +45 -17
  197. package/scripts/validate_decision_engine.py +9 -1
  198. package/scripts/validate_discovery_manifest.py +94 -0
  199. package/scripts/validate_frontmatter.py +39 -20
  200. package/scripts/verify_physical_move.py +185 -0
  201. package/templates/agent-user.md +0 -1
  202. package/templates/agent-user.yml +21 -0
  203. package/templates/minimal/agents-overrides-readme.md +46 -0
  204. package/templates/minimal/overrides-gitkeep +2 -0
  205. package/dist/ui/assets/index-BTRcKDlB.js +0 -39
  206. package/dist/ui/assets/index-BTRcKDlB.js.map +0 -1
  207. package/templates/minimal/agents-gitkeep +0 -2
@@ -0,0 +1,105 @@
1
+ # Maintainer Dev Mode (`AGENT_CONFIG_DEV_MODE=1`)
2
+
3
+ **Audience.** Maintainers of `@event4u/agent-config` working **inside**
4
+ the package repo. Consumers never see this flag; consumer installs
5
+ land in `~/.event4u/agent-config/` per [ADR-020](../decisions/ADR-020-global-only-consumer-scope.md).
6
+
7
+ **Status.** Forward-looking — the gate ships in Phase 3 of the
8
+ `road-to-global-only-install` roadmap. This document is the
9
+ canonical reference once Phase 3 lands.
10
+
11
+ ## Why the flag exists
12
+
13
+ Phase 3 of `road-to-global-only-install` flips `SCOPE_SUPPORT` so that
14
+ **every** consumer scope on `scripts/install.py` is global. The
15
+ package repo itself is structurally identical to a consumer repo
16
+ (same `.augment/`, `.claude/`, `.cursor/` projection layout) which
17
+ means maintainer dev-installs would otherwise be blocked by the same
18
+ gate.
19
+
20
+ `AGENT_CONFIG_DEV_MODE=1` is the explicit, audit-visible opt-out.
21
+ With the flag set, the installer:
22
+
23
+ 1. Allows project-scope writes back into the repo tree (so a
24
+ maintainer can run `task dev:install-global` and iterate on the
25
+ working copy).
26
+ 2. **Skips** writing `agents/.event4u-bridge.yml` into the package
27
+ repo (per `consumer-bridge § Writer contract`). The repo is the
28
+ source, not a consumer of itself.
29
+ 3. Treats `~/.event4u/agent-config/` as a peer install — touches are
30
+ limited to the working-copy projection.
31
+
32
+ Without the flag, `scripts/install.py` refuses to write anywhere
33
+ under the repo tree and points at this document.
34
+
35
+ ## When to set it
36
+
37
+ Set the flag for these workflows, and **only** these:
38
+
39
+ - `task dev:install-global` — refresh the user-scope projection from the working tree.
40
+ - `task dev:install:gui` — iterate the unified Setup-Wizard / Installer-GUI before merge.
41
+ - `task dev:setup` / `task dev:setup:dry-run` — exercise the onboarding wizard.
42
+ - `task release` rehearsal — verify the published shape matches the dev shape.
43
+
44
+ Do **not** set it for:
45
+
46
+ - Consumer project work (the flag is undefined behaviour outside this repo).
47
+ - CI runs on the package itself (CI uses the flag transparently via the dev tasks above; bare runs MUST NOT export it).
48
+ - Production maintainer machines that also consume the package as a user (set per-shell, not in `~/.zshrc`).
49
+
50
+ ## How to set it
51
+
52
+ Per-command (preferred):
53
+
54
+ ```bash
55
+ AGENT_CONFIG_DEV_MODE=1 task dev:install-global
56
+ ```
57
+
58
+ Per-shell session:
59
+
60
+ ```bash
61
+ export AGENT_CONFIG_DEV_MODE=1
62
+ task dev:install:gui
63
+ unset AGENT_CONFIG_DEV_MODE
64
+ ```
65
+
66
+ The `unset` discipline matters: a stale `=1` in your shell environment
67
+ is the most common way a project-scope write sneaks into a consumer
68
+ repo. Future work (`agent-config doctor`, Phase 5.4) will warn when
69
+ the flag is set outside the package repo.
70
+
71
+ ## Safety properties
72
+
73
+ - **Audit-visible.** Every install run logs whether the flag was set
74
+ at the top of the transaction log.
75
+ - **No silent fallback.** If `scripts/install.py` detects the
76
+ package repo signature (presence of `.agent-src.uncompressed/` plus
77
+ `dist/router.json`) and the flag is **not** set, the install
78
+ refuses with a one-line error pointing here.
79
+ - **Not for consumers.** Setting the flag in a consumer project is
80
+ defined as undefined behaviour. The installer will not actively
81
+ refuse (because it cannot distinguish a misconfigured shell from a
82
+ legitimate vendored maintainer install), but it will print a single
83
+ warning line on every run.
84
+
85
+ ## Interaction with the bridge marker
86
+
87
+ Per [`consumer-bridge`](../contracts/consumer-bridge.md), the bridge
88
+ marker is the in-repo pointer to the global root. Under
89
+ `AGENT_CONFIG_DEV_MODE=1`:
90
+
91
+ - The marker is **not** written into the package repo — the repo's
92
+ `agents/` directory is the project surface, not a consumer surface.
93
+ - A pre-existing marker in the package repo is treated as stale and
94
+ removed on the next dev install (with an audit log line).
95
+ - Consumer adapters reading the marker from inside the package repo
96
+ during local development should expand `global_root` against the
97
+ current user's `$HOME` per the reader contract — same as in a
98
+ consumer repo.
99
+
100
+ ## References
101
+
102
+ - [ADR-020](../decisions/ADR-020-global-only-consumer-scope.md) — the global-only decision.
103
+ - [`consumer-bridge`](../contracts/consumer-bridge.md) — bridge marker schema.
104
+ - [`road-to-global-only-install`](../../agents/roadmaps/road-to-global-only-install.md) — Phase 3 SCOPE_SUPPORT flip + Phase 5 migration order.
105
+ - [`taskfiles/dev.yml`](../../taskfiles/dev.yml) — `dev:install-global`, `dev:install:gui`, `dev:setup` task entries.
@@ -9,8 +9,9 @@ server inside Claude Desktop. macOS / Windows / Linux. ~10 minutes.
9
9
  > ZIP per skill under
10
10
  > `~/.event4u/agent-config/claude-desktop/bundles/` so you can drag /
11
11
  > drop them into the Customize panel. The v1 npm / composer install
12
- > scheme is retired; the new global-first scheme is ADR-007 and writes
13
- > through `~/.event4u/agent-config/installed.lock` (legacy
12
+ > scheme is retired; the current global-only scheme follows ADR-007 +
13
+ > [ADR-020](../../decisions/ADR-020-global-only-consumer-scope.md) and
14
+ > writes through `~/.event4u/agent-config/installed.lock` (legacy
14
15
  > `~/.config/agent-config/installed.lock` read as fallback).
15
16
 
16
17
  ## Prerequisites
package/docs/wizard.md CHANGED
@@ -83,10 +83,30 @@ for the full failure-mode matrix.
83
83
 
84
84
  ## Auto-launch from `npx … init`
85
85
 
86
- Tracked under
87
- [`agents/roadmaps/wizard-install-py-wiring.md`](../agents/roadmaps/wizard-install-py-wiring.md)
88
- (carved out from the parent roadmap). Until that ships, run
89
- `agent-config ui:serve --open` manually after `npx … init`.
86
+ `scripts/install.py` acts as a supervisor at the tail of a successful
87
+ install: it evaluates a gate (TTY, `CI`, `--no-ui`,
88
+ `AGENT_CONFIG_NO_UI`), then spawns `node <pkg>/.../cli.js gui
89
+ --project-root <root>` and waits for the child's
90
+ `WIZARD_READY url=<http://127.0.0.1:PORT/>` handshake on stdout
91
+ (strict regex
92
+ `^WIZARD_READY url=(http://(?:127\.0\.0\.1|localhost):\d+/)\r?$`).
93
+ On match, the parent prints a banner and blocks on the child until
94
+ the user closes the tab or sends Ctrl-C.
95
+
96
+ Progressive readiness backoff: `10s → 20s → 40s → 80s` (cumulative
97
+ budget 150s) — generous enough for cold-start `node_modules`
98
+ extraction on slow disks. On timeout the parent kills the child,
99
+ prints the last 20 stderr lines, and exits 0 — the install itself
100
+ is unaffected.
101
+
102
+ Suppress the auto-launch with `--no-ui`, `AGENT_CONFIG_NO_UI=1`, or
103
+ by running in CI (`CI=1`). Preview the gate verdict without
104
+ installing anything via `python3 scripts/install.py --dry-run`,
105
+ which prints a plan summary and exits 0 with zero filesystem
106
+ writes.
107
+
108
+ Skill: [`agents/roadmaps/archive/wizard-install-py-wiring.md`](../agents/roadmaps/archive/wizard-install-py-wiring.md)
109
+ (archived after ship).
90
110
 
91
111
  ## Accessibility
92
112
 
@@ -99,6 +119,21 @@ Tracked under
99
119
  receives `tabIndex={-1}` and `.focus()` so screen readers
100
120
  announce the new heading.
101
121
 
122
+ ## Headless / CI / no-browser
123
+
124
+ When the wizard cannot or should not open — CI runs, SSH sessions
125
+ without X forwarding, headless servers, automated provisioning —
126
+ take the flag path instead. Three equivalent ways to suppress the
127
+ GUI: pass `--no-ui` to `npx … init`, export `AGENT_CONFIG_NO_UI=1`
128
+ in the environment, or run inside a `CI=1` context (auto-detected).
129
+ With the GUI suppressed, pass profile + pack on the command line —
130
+ `npx -y @event4u/agent-config init --no-ui --profile=developer
131
+ --pack=engineering-base` — or hand-edit `.agent-settings.yml`
132
+ directly. Preview the gate verdict and the planned writes with
133
+ `python3 scripts/install.py --dry-run` (zero filesystem writes,
134
+ exits 0). Settings can still be hand-edited at any time; the GUI
135
+ is opt-in, not required.
136
+
102
137
  ## Disabling the GUI
103
138
 
104
139
  Set `AGENT_CONFIG_NO_UI=1` in the environment to skip every
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@event4u/agent-config",
3
- "version": "3.0.0",
3
+ "version": "3.1.0",
4
4
  "description": "Universal AI Agent OS \u2014 audited skills, governance rules, commands, and templates for AI coding tools (Claude Code, Cursor, Windsurf, Copilot).",
5
5
  "license": "MIT",
6
6
  "private": false,
@@ -13,6 +13,21 @@
13
13
  "url": "https://github.com/event4u-app/agent-config/issues"
14
14
  },
15
15
  "homepage": "https://github.com/event4u-app/agent-config#readme",
16
+ "keywords": [
17
+ "ai-agent",
18
+ "mcp",
19
+ "claude-code",
20
+ "cursor",
21
+ "windsurf",
22
+ "copilot",
23
+ "llm",
24
+ "agent-governance",
25
+ "ai-video",
26
+ "skills",
27
+ "prompt-engineering",
28
+ "typescript",
29
+ "python"
30
+ ],
16
31
  "files": [
17
32
  ".agent-src/",
18
33
  ".augment-plugin/",
@@ -60,6 +75,8 @@
60
75
  }
61
76
  },
62
77
  "devDependencies": {
78
+ "@inquirer/prompts": "^7.10.1",
79
+ "@playwright/test": "^1.60.0",
63
80
  "@preact/preset-vite": "^2.10.5",
64
81
  "@testing-library/preact": "^3.2.4",
65
82
  "@types/js-yaml": "^4.0.9",
@@ -17,14 +17,21 @@ Drift categories (manifest ↔ filesystem):
17
17
  (P5.2). Hand-edited tags or accidental cross-package writes show up
18
18
  here; files without frontmatter are skipped (P5.1 contract).
19
19
 
20
- Health checks (nine categories, see :data:`CHECK_IDS`):
20
+ Health checks (see :data:`CHECK_IDS`):
21
21
  scope · manifest-integrity · lockfile-freshness · bridge-drift ·
22
22
  mcp-mode · mcp-beta-readiness · offline-readiness · python-runtime ·
23
- unsupported-combos.
23
+ tier-usage-readiness · council-cli · unsupported-combos ·
24
+ wizard-state.
24
25
  Each emits a structured ``{id, status, message, remedy}`` record with
25
26
  ``status`` ∈ ``ok`` / ``warn`` / ``fail`` (rendered ``✅`` / ``⚠️`` /
26
27
  ``❌``). ``--check <id>`` runs a single check.
27
28
 
29
+ Repair affordances: ``--repair wizard-state`` resets a malformed or
30
+ orphaned ``state/wizard-state.json`` under the user-global root (the
31
+ file the unified Setup-Wizard uses for resume continuity). Used as the
32
+ recovery path when ``1.9``'s npm downgrade is not viable
33
+ (road-to-global-only-install § 1.10 / A6).
34
+
28
35
  Exit codes: ``0`` (clean) · ``1`` (drift or any ``fail`` check) · ``2``
29
36
  (error such as "manifest missing"). Both human and ``--json`` output
30
37
  emit the drift category lists and the structured checks array. Every
@@ -443,8 +450,16 @@ CHECK_IDS = (
443
450
  "tier-usage-readiness",
444
451
  "council-cli",
445
452
  "unsupported-combos",
453
+ "wizard-state",
446
454
  )
447
455
 
456
+ #: Repair targets that ``--repair <id>`` accepts. Each id maps to a
457
+ #: function in :func:`_run_repair` that resets the named artefact and
458
+ #: returns an exit code. Additive set: introduce by adding a new id
459
+ #: here, a runner inside :func:`_run_repair`, and (optionally) a
460
+ #: matching health check above.
461
+ REPAIR_IDS = ("wizard-state",)
462
+
448
463
  #: Six gates that govern the MCP `experimental → beta` promotion. The
449
464
  #: artefact path under each gate id mirrors `tests/test_mcp_beta_gates.py`
450
465
  #: and the contract in `docs/contracts/mcp-beta-criteria.md`.
@@ -983,6 +998,88 @@ def _check_unsupported_combos(manifest: dict[str, Any]) -> dict[str, Any]:
983
998
  }
984
999
 
985
1000
 
1001
+ def _wizard_state_path() -> Path:
1002
+ """Return the on-disk location of the unified Setup-Wizard state file.
1003
+
1004
+ Mirrors ``STATE_REL`` in :mod:`src/server/routes/wizard.ts` — the
1005
+ server writes ``<writeRoot>/state/wizard-state.json``. Under the
1006
+ global-only install scope (ADR-020) ``writeRoot`` resolves to
1007
+ :func:`user_global_paths.event4u_root` (typically
1008
+ ``~/.event4u/agent-config/``); the ``EVENT4U_CONFIG_HOME`` override
1009
+ is honoured implicitly so tests stay hermetic.
1010
+ """
1011
+ from scripts._lib.user_global_paths import event4u_root
1012
+ return event4u_root() / "state" / "wizard-state.json"
1013
+
1014
+
1015
+ def _check_wizard_state() -> dict[str, Any]:
1016
+ """Health-check the resumable Setup-Wizard state file.
1017
+
1018
+ Returns ``ok`` when the file is absent (no active session) or when
1019
+ its JSON shape matches the contract from
1020
+ :mod:`src/server/routes/wizard.ts` (``step: int``, ``partial: dict``,
1021
+ optional ``totalSteps: int``, ``startedAt: str | null``).
1022
+ Returns ``fail`` when the file exists but is unreadable, not valid
1023
+ JSON, or violates the shape — ``--repair wizard-state`` resets it.
1024
+ """
1025
+ state_pth = _wizard_state_path()
1026
+ if not state_pth.exists():
1027
+ return {
1028
+ "id": "wizard-state", "status": "ok",
1029
+ "message": "no active wizard session",
1030
+ "remedy": "",
1031
+ }
1032
+ try:
1033
+ raw = state_pth.read_text(encoding="utf-8")
1034
+ except OSError as exc:
1035
+ return {
1036
+ "id": "wizard-state", "status": "fail",
1037
+ "message": f"unreadable wizard-state at {state_pth}: {exc}",
1038
+ "remedy": "agent-config doctor --repair wizard-state",
1039
+ }
1040
+ try:
1041
+ data = json.loads(raw)
1042
+ except json.JSONDecodeError as exc:
1043
+ return {
1044
+ "id": "wizard-state", "status": "fail",
1045
+ "message": f"malformed JSON in wizard-state ({exc.msg} at line {exc.lineno})",
1046
+ "remedy": "agent-config doctor --repair wizard-state",
1047
+ }
1048
+ if not isinstance(data, dict):
1049
+ return {
1050
+ "id": "wizard-state", "status": "fail",
1051
+ "message": f"wizard-state root is {type(data).__name__}, expected object",
1052
+ "remedy": "agent-config doctor --repair wizard-state",
1053
+ }
1054
+ step = data.get("step")
1055
+ partial = data.get("partial", {})
1056
+ if not isinstance(step, int) or step < 0:
1057
+ return {
1058
+ "id": "wizard-state", "status": "fail",
1059
+ "message": f"wizard-state.step is {step!r}, expected non-negative integer",
1060
+ "remedy": "agent-config doctor --repair wizard-state",
1061
+ }
1062
+ if not isinstance(partial, dict):
1063
+ return {
1064
+ "id": "wizard-state", "status": "fail",
1065
+ "message": f"wizard-state.partial is {type(partial).__name__}, expected object",
1066
+ "remedy": "agent-config doctor --repair wizard-state",
1067
+ }
1068
+ total = data.get("totalSteps")
1069
+ if total is not None and (not isinstance(total, int) or total < 1):
1070
+ return {
1071
+ "id": "wizard-state", "status": "fail",
1072
+ "message": f"wizard-state.totalSteps is {total!r}, expected positive integer or omitted",
1073
+ "remedy": "agent-config doctor --repair wizard-state",
1074
+ }
1075
+ suffix = f" of {total}" if isinstance(total, int) else ""
1076
+ return {
1077
+ "id": "wizard-state", "status": "ok",
1078
+ "message": f"resumable wizard session at step {step + 1}{suffix}",
1079
+ "remedy": "",
1080
+ }
1081
+
1082
+
986
1083
  def _run_checks(
987
1084
  project_root: Path,
988
1085
  manifest: dict[str, Any],
@@ -1008,6 +1105,7 @@ def _run_checks(
1008
1105
  "tier-usage-readiness": lambda: _check_tier_usage_readiness(project_root),
1009
1106
  "council-cli": lambda: _check_council_cli(project_root),
1010
1107
  "unsupported-combos": lambda: _check_unsupported_combos(manifest),
1108
+ "wizard-state": _check_wizard_state,
1011
1109
  }
1012
1110
  out: list[dict[str, Any]] = []
1013
1111
  for cid in CHECK_IDS:
@@ -1115,6 +1213,14 @@ def _parse(argv: list[str]) -> argparse.Namespace:
1115
1213
  "layer chain, wrapper, and install-mode; short-circuits "
1116
1214
  "the drift report"),
1117
1215
  )
1216
+ parser.add_argument(
1217
+ "--repair", default=None, metavar="ID",
1218
+ choices=list(REPAIR_IDS),
1219
+ help=("reset a recoverable artefact and exit "
1220
+ f"({' · '.join(REPAIR_IDS)}); short-circuits the drift "
1221
+ "report. Idempotent — absent files report 'nothing to "
1222
+ "repair' and exit 0."),
1223
+ )
1118
1224
  return parser.parse_args(argv)
1119
1225
 
1120
1226
 
@@ -1165,8 +1271,50 @@ def _run_context(opts: argparse.Namespace) -> int:
1165
1271
  return 0
1166
1272
 
1167
1273
 
1274
+ def _run_repair(opts: argparse.Namespace) -> int:
1275
+ """Handle ``--repair <id>``: reset the named artefact, no drift report.
1276
+
1277
+ Currently the only target is ``wizard-state`` — unlinks the
1278
+ resumable session file so the next ``agent-config setup`` boots
1279
+ from step 1. Absent files are a no-op (idempotent re-run).
1280
+ """
1281
+ target = opts.repair
1282
+ if target == "wizard-state":
1283
+ state_pth = _wizard_state_path()
1284
+ payload = {
1285
+ "id": "wizard-state",
1286
+ "path": str(state_pth),
1287
+ "action": "remove" if state_pth.exists() else "noop",
1288
+ }
1289
+ if state_pth.exists():
1290
+ try:
1291
+ state_pth.unlink()
1292
+ except OSError as exc:
1293
+ payload["action"] = "error"
1294
+ payload["error"] = str(exc)
1295
+ if opts.json:
1296
+ print(json.dumps(payload, indent=2))
1297
+ else:
1298
+ print(f"❌ doctor: could not remove {state_pth}: {exc}",
1299
+ file=sys.stderr)
1300
+ return 2
1301
+ if opts.json:
1302
+ print(json.dumps(payload, indent=2))
1303
+ else:
1304
+ if payload["action"] == "remove":
1305
+ print(f"✅ doctor: reset wizard-state ({state_pth})")
1306
+ else:
1307
+ print(f"✅ doctor: nothing to repair (no wizard-state at {state_pth})")
1308
+ return 0
1309
+ # argparse `choices=` already constrains target; defensive default.
1310
+ print(f"❌ doctor: unknown repair target {target!r}", file=sys.stderr)
1311
+ return 2
1312
+
1313
+
1168
1314
  def main(argv: list[str] | None = None) -> int:
1169
1315
  opts = _parse(list(argv) if argv is not None else sys.argv[1:])
1316
+ if opts.repair is not None:
1317
+ return _run_repair(opts)
1170
1318
  if opts.trace_root:
1171
1319
  return _run_trace_root(opts)
1172
1320
  if opts.context:
@@ -42,6 +42,7 @@ from scripts._lib.agent_settings import (
42
42
  from scripts.config import presets, profiles
43
43
 
44
44
  ROUTER_FILENAME = "router.json"
45
+ ROUTER_RELATIVE = Path("dist") / ROUTER_FILENAME
45
46
 
46
47
 
47
48
  def _resolve_root(arg: str | None) -> tuple[Path, str]:
@@ -60,7 +61,7 @@ def _load_user_settings(project_root: Path) -> dict[str, Any]:
60
61
 
61
62
 
62
63
  def _load_router(project_root: Path) -> dict[str, Any]:
63
- path = project_root / ROUTER_FILENAME
64
+ path = project_root / ROUTER_RELATIVE
64
65
  if not path.exists():
65
66
  return {}
66
67
  try: