@event4u/agent-config 9.26.0 → 9.28.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 (252) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/AGENTS.md +12 -11
  3. package/CHANGELOG.md +134 -207
  4. package/README.md +1 -1
  5. package/dist/agent-src/commands/analyze/conformance.md +43 -0
  6. package/dist/agent-src/commands/roadmap/next.md +65 -3
  7. package/dist/agent-src/contexts/execution/orchestration-telemetry.md +10 -3
  8. package/dist/agent-src/rules/active-remediation.md +2 -0
  9. package/dist/agent-src/rules/analysis-skill-routing.md +2 -0
  10. package/dist/agent-src/rules/architecture.md +2 -0
  11. package/dist/agent-src/rules/artifact-drafting-protocol.md +2 -0
  12. package/dist/agent-src/rules/artifact-engagement-recording.md +6 -1
  13. package/dist/agent-src/rules/augment-edit-discipline.md +2 -0
  14. package/dist/agent-src/rules/autonomous-execution.md +3 -1
  15. package/dist/agent-src/rules/brand-consistency.md +2 -0
  16. package/dist/agent-src/rules/brand-source-of-truth.md +35 -6
  17. package/dist/agent-src/rules/broken-access-control.md +2 -0
  18. package/dist/agent-src/rules/cli-output-handling.md +2 -0
  19. package/dist/agent-src/rules/code-comment-discipline.md +2 -0
  20. package/dist/agent-src/rules/code-provenance.md +2 -0
  21. package/dist/agent-src/rules/command-suggestion-policy.md +2 -0
  22. package/dist/agent-src/rules/commit-conventions.md +2 -0
  23. package/dist/agent-src/rules/communication-through-line.md +2 -0
  24. package/dist/agent-src/rules/content-quoting-floor.md +2 -0
  25. package/dist/agent-src/rules/context-hygiene.md +13 -1
  26. package/dist/agent-src/rules/copilot-routing.md +2 -0
  27. package/dist/agent-src/rules/council-availability.md +93 -0
  28. package/dist/agent-src/rules/cross-source-consistency.md +5 -0
  29. package/dist/agent-src/rules/decision-revisit-gate.md +2 -0
  30. package/dist/agent-src/rules/delegation-policy.md +31 -5
  31. package/dist/agent-src/rules/design-fidelity.md +5 -0
  32. package/dist/agent-src/rules/design-review-after-ui-write.md +4 -0
  33. package/dist/agent-src/rules/devcontainer-routing.md +2 -0
  34. package/dist/agent-src/rules/doc-screenshot-hygiene.md +2 -0
  35. package/dist/agent-src/rules/docker-commands.md +2 -0
  36. package/dist/agent-src/rules/domain-adoption-policy.md +2 -0
  37. package/dist/agent-src/rules/domain-safety-disclaimer.md +2 -0
  38. package/dist/agent-src/rules/domain-safety-pii.md +2 -0
  39. package/dist/agent-src/rules/domain-safety-retention.md +2 -0
  40. package/dist/agent-src/rules/downstream-changes.md +2 -0
  41. package/dist/agent-src/rules/engineering-safety-floor.md +2 -0
  42. package/dist/agent-src/rules/evaluator-independence.md +14 -3
  43. package/dist/agent-src/rules/external-code-graph-interop.md +4 -1
  44. package/dist/agent-src/rules/external-reference-deep-dive.md +2 -0
  45. package/dist/agent-src/rules/fast-path-marker-visibility.md +2 -0
  46. package/dist/agent-src/rules/finance-safety-floor.md +3 -1
  47. package/dist/agent-src/rules/framework-neutrality-in-generic-skills.md +2 -0
  48. package/dist/agent-src/rules/git-history-discipline.md +3 -1
  49. package/dist/agent-src/rules/guidelines.md +2 -0
  50. package/dist/agent-src/rules/history-discipline.md +5 -1
  51. package/dist/agent-src/rules/icon-consistency.md +2 -0
  52. package/dist/agent-src/rules/image-likeness-and-rights.md +2 -0
  53. package/dist/agent-src/rules/improve-before-implement.md +2 -0
  54. package/dist/agent-src/rules/invite-challenge.md +2 -0
  55. package/dist/agent-src/rules/laravel-routing.md +2 -0
  56. package/dist/agent-src/rules/laravel-translations.md +2 -0
  57. package/dist/agent-src/rules/legal-safety-floor.md +3 -1
  58. package/dist/agent-src/rules/lethal-trifecta-guard.md +2 -0
  59. package/dist/agent-src/rules/linked-projects-onboarding-gate.md +2 -0
  60. package/dist/agent-src/rules/low-impact-corpus-privacy-floor.md +2 -0
  61. package/dist/agent-src/rules/markdown-safe-codeblocks.md +2 -0
  62. package/dist/agent-src/rules/media-governance-routing.md +2 -0
  63. package/dist/agent-src/rules/media-sync-ground-truth.md +2 -0
  64. package/dist/agent-src/rules/minimal-safe-diff.md +2 -0
  65. package/dist/agent-src/rules/missing-tool-handling.md +2 -0
  66. package/dist/agent-src/rules/model-recommendation.md +2 -0
  67. package/dist/agent-src/rules/no-attribution-footers.md +2 -0
  68. package/dist/agent-src/rules/no-decorative-emojis-in-git-surfaces.md +2 -0
  69. package/dist/agent-src/rules/no-pr-progress-comments.md +5 -1
  70. package/dist/agent-src/rules/no-roadmap-references.md +2 -0
  71. package/dist/agent-src/rules/notes-first-reasoning.md +2 -0
  72. package/dist/agent-src/rules/onboarding-gate.md +5 -1
  73. package/dist/agent-src/rules/output-discipline.md +2 -0
  74. package/dist/agent-src/rules/package-ci-checks.md +2 -0
  75. package/dist/agent-src/rules/persona-governance.md +2 -0
  76. package/dist/agent-src/rules/php-coding.md +2 -0
  77. package/dist/agent-src/rules/prefer-enums-over-literals.md +2 -0
  78. package/dist/agent-src/rules/preservation-guard.md +2 -0
  79. package/dist/agent-src/rules/provider-lifecycle-discipline.md +2 -0
  80. package/dist/agent-src/rules/question-not-instruction.md +2 -0
  81. package/dist/agent-src/rules/reviewer-awareness.md +2 -0
  82. package/dist/agent-src/rules/roadmap-ci-steps-policy.md +6 -1
  83. package/dist/agent-src/rules/roadmap-progress-sync.md +2 -0
  84. package/dist/agent-src/rules/role-mode-adherence.md +3 -1
  85. package/dist/agent-src/rules/rule-type-governance.md +2 -0
  86. package/dist/agent-src/rules/runtime-safety.md +2 -0
  87. package/dist/agent-src/rules/scale-discipline.md +5 -1
  88. package/dist/agent-src/rules/secret-vcs-guard.md +2 -0
  89. package/dist/agent-src/rules/security-sensitive-stop.md +2 -0
  90. package/dist/agent-src/rules/self-repair-loop.md +103 -0
  91. package/dist/agent-src/rules/senior-engineering-discipline.md +2 -0
  92. package/dist/agent-src/rules/session-canary.md +62 -19
  93. package/dist/agent-src/rules/settings-ask-protocol.md +4 -0
  94. package/dist/agent-src/rules/size-enforcement.md +2 -0
  95. package/dist/agent-src/rules/skill-improvement-trigger.md +2 -0
  96. package/dist/agent-src/rules/skill-quality.md +2 -0
  97. package/dist/agent-src/rules/slash-command-routing-policy.md +2 -0
  98. package/dist/agent-src/rules/source-confidentiality.md +2 -0
  99. package/dist/agent-src/rules/source-discovery-gate.md +2 -0
  100. package/dist/agent-src/rules/source-of-truth.md +2 -0
  101. package/dist/agent-src/rules/spreadsheet-source-quality.md +2 -0
  102. package/dist/agent-src/rules/strategy-safety-floor.md +3 -1
  103. package/dist/agent-src/rules/symfony-routing.md +2 -0
  104. package/dist/agent-src/rules/think-before-action.md +2 -0
  105. package/dist/agent-src/rules/token-budget-discipline.md +5 -1
  106. package/dist/agent-src/rules/token-efficiency.md +2 -0
  107. package/dist/agent-src/rules/token-optimizer-maintenance.md +2 -0
  108. package/dist/agent-src/rules/tool-safety.md +7 -1
  109. package/dist/agent-src/rules/ui-audit-gate.md +4 -0
  110. package/dist/agent-src/rules/untrusted-input-defense.md +2 -0
  111. package/dist/agent-src/rules/upstream-proposal.md +2 -0
  112. package/dist/agent-src/rules/user-interaction.md +2 -0
  113. package/dist/agent-src/rules/user-interrupt-priority.md +2 -0
  114. package/dist/agent-src/skills/adversarial-review/SKILL.md +1 -1
  115. package/dist/agent-src/skills/analysis-autonomous-mode/SKILL.md +1 -1
  116. package/dist/agent-src/skills/performance-analysis/SKILL.md +1 -1
  117. package/dist/agent-src/skills/persona-improvement/SKILL.md +1 -1
  118. package/dist/agent-src/skills/project-analyzer/SKILL.md +1 -1
  119. package/dist/agent-src/skills/security-audit/SKILL.md +1 -1
  120. package/dist/agent-src/skills/sequential-thinking/SKILL.md +1 -1
  121. package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +1 -1
  122. package/dist/agent-src/skills/universal-project-analysis/SKILL.md +1 -1
  123. package/dist/agent-src/templates/AGENTS.md +6 -10
  124. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  125. package/dist/cli/registry.js +9 -0
  126. package/dist/cli/registry.js.map +1 -1
  127. package/dist/cli-delegate/chunk-22GCIJ2O.js +270 -0
  128. package/dist/cli-delegate/{chunk-BSLUHV2F.js → chunk-2BE5I2MN.js} +156 -141
  129. package/dist/cli-delegate/{chunk-AIIUU73N.js → chunk-7OMQCH4V.js} +1 -1
  130. package/dist/cli-delegate/{chunk-HJ6DAJHR.js → chunk-GB2FSUEE.js} +57 -7
  131. package/dist/cli-delegate/{chunk-WR6GTRHJ.js → chunk-JXARCGVN.js} +3 -3
  132. package/dist/cli-delegate/{chunk-SJQMJI7M.js → chunk-PQSAQKHW.js} +467 -305
  133. package/dist/cli-delegate/chunk-REKCMYOU.js +2991 -0
  134. package/dist/cli-delegate/cmd_analyze_session.js +1 -1
  135. package/dist/cli-delegate/cmd_brand_status.js +134 -0
  136. package/dist/cli-delegate/cmd_conformance.js +7 -7
  137. package/dist/cli-delegate/cmd_converge.js +3 -3
  138. package/dist/cli-delegate/cmd_doctor.js +4 -4
  139. package/dist/cli-delegate/cmd_explain.js +80 -330
  140. package/dist/cli-delegate/cmd_export.js +5 -5
  141. package/dist/cli-delegate/cmd_fleet.js +3 -3
  142. package/dist/cli-delegate/cmd_handoff.js +2 -2
  143. package/dist/cli-delegate/cmd_mcp_available.js +433 -0
  144. package/dist/cli-delegate/cmd_migrate.js +3 -3
  145. package/dist/cli-delegate/cmd_packs_active.js +153 -0
  146. package/dist/cli-delegate/cmd_preflight.js +1 -1
  147. package/dist/cli-delegate/cmd_prune.js +5 -5
  148. package/dist/cli-delegate/cmd_route_audit.js +8 -8
  149. package/dist/cli-delegate/cmd_route_explain.js +4 -4
  150. package/dist/cli-delegate/cmd_settings_get.js +272 -0
  151. package/dist/cli-delegate/cmd_settings_migrate.js +5 -5
  152. package/dist/cli-delegate/cmd_settings_set.js +7 -2967
  153. package/dist/cli-delegate/cmd_sync.js +5 -5
  154. package/dist/cli-delegate/cmd_uninstall.js +5 -5
  155. package/dist/cli-delegate/cmd_update.js +4 -4
  156. package/dist/cli-delegate/cmd_upgrade.js +4 -4
  157. package/dist/cli-delegate/cmd_validate.js +5 -5
  158. package/dist/cli-delegate/cmd_versions.js +4 -4
  159. package/dist/discovery/deprecation-report.md +1 -1
  160. package/dist/discovery/discovery-manifest.json +184 -124
  161. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  162. package/dist/discovery/discovery-manifest.summary.md +3 -3
  163. package/dist/discovery/orphan-report.md +1 -1
  164. package/dist/discovery/packs.json +7 -5
  165. package/dist/discovery/trust-report.md +11 -11
  166. package/dist/discovery/workspaces.json +6 -4
  167. package/dist/hooks/dispatch.js +9080 -5701
  168. package/dist/install/install.mjs +698 -508
  169. package/dist/mcp/registry-manifest.json +3 -3
  170. package/dist/mcp/server.json +2 -2
  171. package/dist/mcp/server.mjs +698 -437
  172. package/dist/router.json +1 -1
  173. package/dist/scripts/_lib/agent_settings.js +80 -15
  174. package/dist/scripts/_lib/agent_settings.js.map +1 -1
  175. package/dist/scripts/condense.js +33 -2
  176. package/dist/scripts/condense.js.map +1 -1
  177. package/dist/server/io/yamlIO.js +85 -0
  178. package/dist/server/io/yamlIO.js.map +1 -1
  179. package/dist/server/routes/settings.js +1 -12
  180. package/dist/server/routes/settings.js.map +1 -1
  181. package/dist/server/routes/wizard.js +52 -2
  182. package/dist/server/routes/wizard.js.map +1 -1
  183. package/dist/shared/settingsCarveOut.js +118 -0
  184. package/dist/shared/settingsCarveOut.js.map +1 -0
  185. package/dist/shared/settingsSurface.js +7 -1
  186. package/dist/shared/settingsSurface.js.map +1 -1
  187. package/dist/ui/assets/index-DmR0mbk4.js.map +1 -1
  188. package/docs/contracts/settings-classes.md +29 -8
  189. package/docs/guidelines/agent-infra/memory-access.md +14 -3
  190. package/package.json +1 -1
  191. package/src/agent-src/templates/AGENTS.md +6 -10
  192. package/src/config/budgets.yml +29 -0
  193. package/src/config/evaluator-budgets.json +4 -4
  194. package/src/config/gate-coverage.yml +26 -0
  195. package/src/scripts/_cli/cmd_brand_status.ts +174 -0
  196. package/src/scripts/_cli/cmd_doctor.ts +6 -21
  197. package/src/scripts/_cli/cmd_mcp_available.ts +266 -0
  198. package/src/scripts/_cli/cmd_packs_active.ts +225 -0
  199. package/src/scripts/_cli/cmd_settings_get.ts +324 -0
  200. package/src/scripts/_dispatch.bash +75 -0
  201. package/src/scripts/_lib/agent_settings.ts +82 -16
  202. package/src/scripts/_lib/carrier_divergence.ts +135 -0
  203. package/src/scripts/_lib/git_common_dir.ts +128 -0
  204. package/src/scripts/_lib/host_capability.ts +84 -1
  205. package/src/scripts/_lib/obligation_frequency.ts +391 -0
  206. package/src/scripts/_lib/prompt_shape.ts +88 -0
  207. package/src/scripts/_lib/review_skipped_record.ts +102 -0
  208. package/src/scripts/_lib/rule_layer_overlap.ts +254 -0
  209. package/src/scripts/_lib/self_repair.ts +342 -0
  210. package/src/scripts/_lib/self_repair_store.ts +100 -0
  211. package/src/scripts/_lib/session_register.ts +361 -0
  212. package/src/scripts/_lib/transcript_entry.ts +80 -0
  213. package/src/scripts/check_branch_freshness.ts +255 -0
  214. package/src/scripts/check_council_config_location.ts +125 -1
  215. package/src/scripts/check_enforcement_coverage.ts +192 -7
  216. package/src/scripts/check_rule_projection_integrity.ts +364 -0
  217. package/src/scripts/check_standing_rule_delivery.ts +314 -0
  218. package/src/scripts/condense.ts +36 -2
  219. package/src/scripts/config/profiles.ts +13 -3
  220. package/src/scripts/conformance_scan.ts +307 -24
  221. package/src/scripts/council_availability_hook.ts +188 -0
  222. package/src/scripts/council_cli.ts +126 -7
  223. package/src/scripts/generate_settings_reference.ts +232 -0
  224. package/src/scripts/hook_manifest.yaml +155 -33
  225. package/src/scripts/hooks/block_no_verify.ts +95 -3
  226. package/src/scripts/hooks/block_unauthorized_git.ts +217 -2
  227. package/src/scripts/hooks/concern_registry.ts +12 -0
  228. package/src/scripts/hooks/delegation_nudge_hook.ts +450 -0
  229. package/src/scripts/hooks/end_review_nudge_hook.ts +726 -0
  230. package/src/scripts/hooks/orchestration_record_hook.ts +342 -0
  231. package/src/scripts/install.ts +151 -0
  232. package/src/scripts/language_mirror_hook.ts +49 -6
  233. package/src/scripts/measure_scope_dedup.ts +19 -13
  234. package/src/scripts/onboarding_gate_hook.ts +40 -1
  235. package/src/scripts/orchestration_backfill.ts +354 -0
  236. package/src/scripts/orchestration_record.ts +4 -1
  237. package/src/scripts/preamble_byte_census.ts +9 -20
  238. package/src/scripts/release.ts +142 -36
  239. package/src/scripts/report_carrier_divergence.ts +364 -0
  240. package/src/scripts/report_skill_activation.ts +257 -0
  241. package/src/scripts/report_skill_obligation_violations.ts +579 -0
  242. package/src/scripts/routing_doctor.ts +77 -6
  243. package/src/scripts/schemas/rule.schema.json +5 -0
  244. package/src/scripts/self_repair_cli.ts +240 -0
  245. package/src/scripts/self_repair_hook.ts +241 -0
  246. package/src/scripts/session_canary_hook.ts +35 -3
  247. package/src/scripts/session_register_hook.ts +288 -0
  248. package/src/scripts/sessions_cli.ts +140 -0
  249. package/src/scripts/validate_frontmatter.ts +68 -0
  250. package/src/server/io/yamlIO.ts +87 -0
  251. package/src/shared/settingsCarveOut.ts +143 -0
  252. package/src/shared/settingsSurface.ts +7 -1
@@ -63,6 +63,8 @@ import { format_install_hints } from './ai_council/cli_hints.js';
63
63
  import {
64
64
  type AdvisorConfig,
65
65
  type CouncilConfig,
66
+ COUNCIL_CONFIG_ENV,
67
+ COUNCIL_CONFIG_USER_GLOBAL_REL,
66
68
  CouncilConfigError,
67
69
  load_council_config,
68
70
  resolve_api_key,
@@ -558,9 +560,22 @@ function build_members(settings: Dict, opts: BuildMembersOptions = {}): External
558
560
 
559
561
  const ai = _isDict(settings) ? ((settings['ai_council'] as Dict) || {}) : {};
560
562
  if (!_pyBool(ai['enabled'])) {
563
+ // The file this message names is load-bearing, and it named the wrong one
564
+ // for months. Measured across 10 sessions in one consumer project: the
565
+ // agent reasoned from `.agent-settings.yml` six times, concluded no
566
+ // council existed, and substituted a weaker path — and one session
567
+ // "fixed" it by COPYING the user-global config into the project tree,
568
+ // because this message told it that was where the switch lived. An error
569
+ // from the authoritative tool is the strongest signal in the room; when
570
+ // it points at the wrong file it does not merely fail to help, it
571
+ // manufactures the wrong belief. Name the real path (ADR-104: always
572
+ // user-global, never project-local) and name the verb that reports it.
561
573
  throw new CouncilDisabledError(
562
- 'ai_council.enabled is false in .agent-settings.yml — ' +
563
- 'flip it on before invoking council:* commands.',
574
+ `enabled is false in the council config (${COUNCIL_CONFIG_USER_GLOBAL_REL} ` +
575
+ 'under the user-global agent-config root) — flip it on before invoking ' +
576
+ 'council:* commands. This config is NEVER project-local (ADR-104), so do ' +
577
+ 'not look for it, or create it, in the project tree. ' +
578
+ '`agent-config council:status` prints the resolved path.',
564
579
  );
565
580
  }
566
581
  const members_cfg = (ai['members'] as Dict) || {};
@@ -608,7 +623,8 @@ function build_members(settings: Dict, opts: BuildMembersOptions = {}): External
608
623
  if (name in siblings) {
609
624
  throw new CouncilDisabledError(
610
625
  `--siblings targets member ${_pyReprStr(name)} but it is not ` +
611
- `enabled in .agent-settings.yml (ai_council.members.${name}.enabled).`,
626
+ `enabled in the council config (members.${name}.enabled in ` +
627
+ `${COUNCIL_CONFIG_USER_GLOBAL_REL} under the user-global root).`,
612
628
  );
613
629
  }
614
630
  continue;
@@ -714,8 +730,10 @@ function build_members(settings: Dict, opts: BuildMembersOptions = {}): External
714
730
  );
715
731
  }
716
732
  throw new CouncilDisabledError(
717
- 'no council member has `enabled: true` — enable at least one in ' +
718
- '.agent-settings.yml under ai_council.members.*.',
733
+ 'no council member has `enabled: true` — enable at least one under ' +
734
+ `members.* in ${COUNCIL_CONFIG_USER_GLOBAL_REL} (user-global root; ` +
735
+ 'never the project tree, ADR-104). `agent-config council:status` ' +
736
+ 'prints the resolved path.',
719
737
  );
720
738
  }
721
739
  return members;
@@ -1855,6 +1873,95 @@ function _emit_shadow_slo_banner(): void {
1855
1873
  }
1856
1874
  }
1857
1875
 
1876
+ /**
1877
+ * `council:status` — answer "is a council configured, and from where" without
1878
+ * spending anything, so no caller has to infer it from a filename.
1879
+ *
1880
+ * WHY THIS EXISTS. Measured 2026-08-08: an agent working in a consumer project
1881
+ * announced "Kein Council konfiguriert (keine `.agent-settings.yml`)" and
1882
+ * substituted a weaker subagent fan-out. The council was configured the whole
1883
+ * time, user-globally. The inference was wrong twice over — the council config
1884
+ * has not lived in `.agent-settings.yml` since the Phase-0 migration, and per
1885
+ * ADR-104 the project tree is **never** searched, so the presence or absence of
1886
+ * any project file says nothing at all about council availability.
1887
+ *
1888
+ * The guidance was not missing: `/council default` states this emphatically in
1889
+ * four places. But a command file only loads when the command is invoked, and
1890
+ * the agent was deciding *whether a capability existed* — a question it reached
1891
+ * before invoking anything. The always-loaded layer says "when the council is
1892
+ * enabled" and never says how to find out.
1893
+ *
1894
+ * So the fix is not more prose. It is a check that costs nothing to run and
1895
+ * leaves nothing to infer. Exit is always 0 — this reports a state, it does not
1896
+ * gate — and the verdict line is machine-greppable in both directions.
1897
+ */
1898
+ export function cmd_status(args: Args, opts: { env?: Record<string, string | undefined> } = {}): number {
1899
+ const env = opts.env ?? process.env;
1900
+ const override = env[COUNCIL_CONFIG_ENV];
1901
+ const path_ = resolve_config_path(REPO_ROOT, { env: env as never });
1902
+ const exists = fs.existsSync(String(path_));
1903
+ const provenance = override ? `${COUNCIL_CONFIG_ENV} override` : 'user-global';
1904
+
1905
+ let cfg: CouncilConfig | null = null;
1906
+ let parse_error: string | null = null;
1907
+ if (exists) {
1908
+ try {
1909
+ cfg = load_council_config(path_);
1910
+ } catch (exc) {
1911
+ parse_error = exc instanceof Error ? exc.message : String(exc);
1912
+ }
1913
+ }
1914
+
1915
+ const enabledMembers = cfg === null ? [] : [...cfg.members.entries()].filter(([, m]) => m.enabled);
1916
+ // "Configured" deliberately means: a readable file whose `enabled` is true and
1917
+ // which has at least one enabled member. A file that parses but enables
1918
+ // nothing is the case a caller most needs told apart from a missing file,
1919
+ // because only one of the two is fixed by writing a config.
1920
+ const configured = cfg !== null && cfg.enabled && enabledMembers.length > 0;
1921
+
1922
+ if (_getattr(args, 'json', false)) {
1923
+ _stdout(
1924
+ `${JSON.stringify({
1925
+ configured,
1926
+ path: String(path_),
1927
+ exists,
1928
+ provenance,
1929
+ enabled: cfg?.enabled ?? null,
1930
+ members_total: cfg === null ? null : cfg.members.size,
1931
+ members_enabled: cfg === null ? null : enabledMembers.length,
1932
+ member_names: enabledMembers.map(([n]) => n),
1933
+ parse_error,
1934
+ project_tree_consulted: false,
1935
+ })}\n`,
1936
+ );
1937
+ return 0;
1938
+ }
1939
+
1940
+ _stdout(`council:status · ${configured ? 'CONFIGURED' : 'NOT CONFIGURED'}\n`);
1941
+ _stdout(` config path ${String(path_)}\n`);
1942
+ _stdout(` resolved by ${provenance}\n`);
1943
+ _stdout(` file exists ${exists ? 'yes' : 'no'}\n`);
1944
+ if (parse_error !== null) {
1945
+ _stdout(` parse error ${parse_error}\n`);
1946
+ }
1947
+ if (cfg !== null) {
1948
+ _stdout(` enabled ${cfg.enabled ? 'yes' : 'no'}\n`);
1949
+ _stdout(` members ${enabledMembers.length} enabled of ${cfg.members.size}\n`);
1950
+ if (enabledMembers.length > 0) {
1951
+ _stdout(` ${enabledMembers.map(([n]) => n).join(', ')}\n`);
1952
+ }
1953
+ }
1954
+ _stdout('\n');
1955
+ _stdout(' The project tree is NEVER consulted (ADR-104). No project file —\n');
1956
+ _stdout(' `.agent-settings.yml` included — indicates council availability in\n');
1957
+ _stdout(' either direction. Do not infer it from one; run this instead.\n');
1958
+ if (!configured) {
1959
+ _stdout(`\n To configure: write ${COUNCIL_CONFIG_USER_GLOBAL_REL} under the user-global\n`);
1960
+ _stdout(' agent-config root, with `enabled: true` and at least one enabled member.\n');
1961
+ }
1962
+ return 0;
1963
+ }
1964
+
1858
1965
  function _apply_solo_dispatch(members: ExternalAIClient[]): [ExternalAIClient[], string | null] {
1859
1966
  let cfg: CouncilConfig;
1860
1967
  try {
@@ -2826,13 +2933,15 @@ interface Args {
2826
2933
  // shadow-report
2827
2934
  log: string | null;
2828
2935
  window_days: number;
2936
+ // status
2937
+ json: boolean;
2829
2938
  }
2830
2939
 
2831
- const _SUBCOMMANDS = ['estimate', 'run', 'debate', 'render', 'replay', 'quota', 'shadow-report'];
2940
+ const _SUBCOMMANDS = ['estimate', 'run', 'debate', 'render', 'replay', 'quota', 'shadow-report', 'status'];
2832
2941
 
2833
2942
  const _TOP_USAGE =
2834
2943
  `usage: ${_PROG} [-h]\n` +
2835
- ` {estimate,run,debate,render,replay,quota,shadow-report}\n` +
2944
+ ` {estimate,run,debate,render,replay,quota,shadow-report,status}\n` +
2836
2945
  ` ...\n`;
2837
2946
 
2838
2947
  function _topError(message: string): never {
@@ -2886,6 +2995,7 @@ function _defaultArgs(): Args {
2886
2995
  reset: null,
2887
2996
  log: null,
2888
2997
  window_days: 7,
2998
+ json: false,
2889
2999
  };
2890
3000
  }
2891
3001
 
@@ -3009,6 +3119,12 @@ function _specsFor(cmd: string): { positionals: string[]; opts: OptSpec[]; requi
3009
3119
  ],
3010
3120
  requiredOpts: [],
3011
3121
  };
3122
+ case 'status':
3123
+ return {
3124
+ positionals: [],
3125
+ opts: [{ flag: '--json', takesValue: false, apply: (o) => (o.json = true) }],
3126
+ requiredOpts: [],
3127
+ };
3012
3128
  default:
3013
3129
  return { positionals: [], opts: [], requiredOpts: [] };
3014
3130
  }
@@ -3213,6 +3329,9 @@ function main(argv: string[] | null = null): number {
3213
3329
  if (args.cmd === 'shadow-report') {
3214
3330
  return cmd_shadow_report(args);
3215
3331
  }
3332
+ if (args.cmd === 'status') {
3333
+ return cmd_status(args);
3334
+ }
3216
3335
  } catch (exc) {
3217
3336
  if (exc instanceof CouncilDisabledError) {
3218
3337
  _stderr(`❌ council:${args.cmd}: ${exc.message}\n`);
@@ -0,0 +1,232 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Generate the human-readable settings reference page.
4
+ *
5
+ * `road-to-zero-ceremony-settings` Phase 3 step 3. The user's global settings
6
+ * file is becoming sparse, so the long-form explanation that used to ride along
7
+ * as template comments has to survive somewhere the user can still read it.
8
+ *
9
+ * The page is derived from TWO sources that already exist, and from nothing
10
+ * else — the point is that there is no third surface to keep in sync:
11
+ *
12
+ * 1. the zod settings schema (`src/server/schemas/settings.ts`), flattened
13
+ * through the same `flattenSurface` the installer uses for its upgrade
14
+ * delta, so type / default / enum / description come from one place;
15
+ * 2. the A/B/C class table (`docs/contracts/settings-classes.md`), parsed by
16
+ * the same `parseSettingsClassRows` the lint and the writer use, so the
17
+ * page cannot claim a class the fence does not enforce.
18
+ *
19
+ * The package VERSION is deliberately absent from the output. Rendering it
20
+ * would make this page drift on every release bump and red `--check` for a
21
+ * change nobody made — the page describes the schema's shape, not the release
22
+ * that shipped it.
23
+ *
24
+ * Produces: docs/settings-reference.md
25
+ *
26
+ * Modes:
27
+ * --check Regenerate to memory and diff against the committed page.
28
+ * (default) Regenerate in place; exit 0 on success.
29
+ *
30
+ * Exit codes: 0 = ok, 1 = drift (--check), 2 = bad usage, 3 = internal.
31
+ */
32
+ import * as fs from 'node:fs';
33
+ import * as path from 'node:path';
34
+ import { fileURLToPath, pathToFileURL } from 'node:url';
35
+
36
+ import { zodToJsonSchema } from 'zod-to-json-schema';
37
+
38
+ import { settingsSchema } from '../server/schemas/settings.js';
39
+ import {
40
+ buildSettingsClassIndex,
41
+ parseSettingsClassRows,
42
+ type SettingsClass,
43
+ } from '../shared/settingsClasses.js';
44
+ import { flattenSurface, type SurfaceEntry } from '../shared/settingsSurface.js';
45
+
46
+ const _HERE = path.dirname(fileURLToPath(import.meta.url));
47
+ export const ROOT = path.resolve(_HERE, '..', '..');
48
+
49
+ export const CONTRACT_REL = path.join('docs', 'contracts', 'settings-classes.md');
50
+ export const OUTPUT_REL = path.join('docs', 'settings-reference.md');
51
+
52
+ /** What each class means for the reader. Mirrors the contract's own table. */
53
+ const CLASS_BLURB: Record<SettingsClass, string> = {
54
+ A: 'preference — the agent may set it; you are never asked',
55
+ B: 'consent — the agent asks once, then persists your answer',
56
+ C: 'guarded — only you (GUI or hand-edit) may change it',
57
+ };
58
+
59
+ /** A key with no row in the contract. The lint fails on these separately. */
60
+ const UNCLASSIFIED = '—';
61
+
62
+ export interface ReferenceRow {
63
+ key: string;
64
+ cls: SettingsClass | null;
65
+ entry: SurfaceEntry;
66
+ }
67
+
68
+ /**
69
+ * Group by top-level segment so the page reads like the template did.
70
+ * Leaves with no dot (e.g. `discipline_profile`) group under `(root)`.
71
+ */
72
+ export function groupRows(rows: readonly ReferenceRow[]): Map<string, ReferenceRow[]> {
73
+ const groups = new Map<string, ReferenceRow[]>();
74
+ for (const row of rows) {
75
+ const dot = row.key.indexOf('.');
76
+ const section = dot === -1 ? '(root)' : row.key.slice(0, dot);
77
+ const bucket = groups.get(section);
78
+ if (bucket === undefined) groups.set(section, [row]);
79
+ else bucket.push(row);
80
+ }
81
+ return groups;
82
+ }
83
+
84
+ /** Render a value for a markdown table cell — never let it break the row. */
85
+ export function renderCell(value: unknown): string {
86
+ if (value === undefined) return '';
87
+ const json = JSON.stringify(value);
88
+ if (json === undefined) return '';
89
+ return `\`${json.replace(/\|/g, '\\|')}\``;
90
+ }
91
+
92
+ /** Descriptions are free prose; pipes and newlines would break the table. */
93
+ export function renderProse(value: string | undefined): string {
94
+ if (value === undefined || value === '') return '';
95
+ return value.replace(/\s+/g, ' ').replace(/\|/g, '\\|').trim();
96
+ }
97
+
98
+ export function buildRows(contractMarkdown: string): ReferenceRow[] {
99
+ const jsonSchema = zodToJsonSchema(settingsSchema, {
100
+ name: 'AgentSettings',
101
+ $refStrategy: 'none',
102
+ target: 'jsonSchema7',
103
+ }) as Parameters<typeof flattenSurface>[0];
104
+ // The version is a required parameter of the shared flattener and is
105
+ // discarded here on purpose — see the module header.
106
+ const surface = flattenSurface(jsonSchema, 'unversioned');
107
+ const classIndex = buildSettingsClassIndex(parseSettingsClassRows(contractMarkdown));
108
+
109
+ return Object.keys(surface.entries)
110
+ .sort()
111
+ .map((key) => ({
112
+ key,
113
+ cls: classIndex.get(key) ?? null,
114
+ entry: surface.entries[key] as SurfaceEntry,
115
+ }));
116
+ }
117
+
118
+ export function renderPage(rows: readonly ReferenceRow[]): string {
119
+ const out: string[] = [];
120
+ out.push('# Settings reference — every key, its class, and its default');
121
+ out.push('');
122
+ out.push('> **Generated** by `src/scripts/generate_settings_reference.ts` — do NOT');
123
+ out.push('> hand-edit. Derived from the zod settings schema plus the A/B/C class');
124
+ out.push('> table in [`settings-classes`](contracts/settings-classes.md).');
125
+ out.push('> Drift-checked in CI (`--check`).');
126
+ out.push('');
127
+ out.push('Your global settings file is **sparse**: it records the decisions you');
128
+ out.push('actually made, not every key that exists. A key absent from the file');
129
+ out.push('resolves to the default listed here. This page is where the long-form');
130
+ out.push('explanation lives now that the file no longer carries it as comments.');
131
+ out.push('');
132
+ out.push('**Class** answers one question — *who may write this key.*');
133
+ out.push('');
134
+ for (const cls of ['A', 'B', 'C'] as const) {
135
+ out.push(`- **${cls}** — ${CLASS_BLURB[cls]}`);
136
+ }
137
+ out.push('');
138
+
139
+ const groups = groupRows(rows);
140
+ for (const section of [...groups.keys()].sort()) {
141
+ const bucket = groups.get(section) as ReferenceRow[];
142
+ out.push(`## ${section}`);
143
+ out.push('');
144
+ out.push('| Key | Class | Type | Default | Allowed values | What it does |');
145
+ out.push('|---|---|---|---|---|---|');
146
+ for (const row of bucket) {
147
+ const allowed = row.entry.enum === undefined
148
+ ? ''
149
+ : row.entry.enum.map((v) => `\`${String(v)}\``).join(' · ');
150
+ out.push(
151
+ `| \`${row.key}\` | ${row.cls ?? UNCLASSIFIED} | ${row.entry.type} | `
152
+ + `${renderCell(row.entry.default)} | ${allowed} | ${renderProse(row.entry.description)} |`,
153
+ );
154
+ }
155
+ out.push('');
156
+ }
157
+
158
+ out.push('## See also');
159
+ out.push('');
160
+ out.push('- [`settings-classes`](contracts/settings-classes.md) — the class contract this page reads.');
161
+ out.push('- [`settings-api`](contracts/settings-api.md) — the read/write surface.');
162
+ out.push('');
163
+ return out.join('\n');
164
+ }
165
+
166
+ export function generate(root: string): string {
167
+ const contract = fs.readFileSync(path.join(root, CONTRACT_REL), 'utf-8');
168
+ return renderPage(buildRows(contract));
169
+ }
170
+
171
+ interface Args {
172
+ check: boolean;
173
+ }
174
+
175
+ export function parse_args(argv: readonly string[]): Args {
176
+ const args: Args = { check: false };
177
+ for (const a of argv) {
178
+ if (a === '--check') {
179
+ args.check = true;
180
+ continue;
181
+ }
182
+ process.stderr.write(`generate_settings_reference: unknown argument: ${a}\n`);
183
+ process.exit(2);
184
+ }
185
+ return args;
186
+ }
187
+
188
+ export function main(argv?: readonly string[]): number {
189
+ const args = parse_args(argv ?? process.argv.slice(2));
190
+ const out_path = path.join(ROOT, OUTPUT_REL);
191
+
192
+ let rendered: string;
193
+ try {
194
+ rendered = generate(ROOT);
195
+ } catch (err) {
196
+ process.stderr.write(`generate_settings_reference: ${String(err)}\n`);
197
+ return 3;
198
+ }
199
+
200
+ if (args.check) {
201
+ let committed: string | null = null;
202
+ try {
203
+ committed = fs.readFileSync(out_path, 'utf-8');
204
+ } catch {
205
+ committed = null;
206
+ }
207
+ if (committed !== rendered) {
208
+ process.stderr.write(
209
+ `❌ ${OUTPUT_REL} is out of date.\n`
210
+ + ' Regenerate: ./scripts-run src/scripts/generate_settings_reference\n',
211
+ );
212
+ return 1;
213
+ }
214
+ process.stdout.write(`✅ ${OUTPUT_REL} is up to date.\n`);
215
+ return 0;
216
+ }
217
+
218
+ fs.mkdirSync(path.dirname(out_path), { recursive: true });
219
+ fs.writeFileSync(out_path, rendered, 'utf-8');
220
+ process.stdout.write(`✅ wrote ${OUTPUT_REL}\n`);
221
+ return 0;
222
+ }
223
+
224
+ function _invoked_directly(): boolean {
225
+ if (process.argv[1] === undefined) return false;
226
+ const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
227
+ return import.meta.url === argvUrl;
228
+ }
229
+
230
+ if (_invoked_directly()) {
231
+ process.exitCode = main();
232
+ }