@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
@@ -53,7 +53,7 @@ import {
53
53
  import * as hooks_status from "./hooks_status.js";
54
54
  import { load_agent_settings } from "./_lib/agent_settings.js";
55
55
  import {
56
- normalizeHostManifest,
56
+ resolveHostCapabilities,
57
57
  type HostCapabilityManifest,
58
58
  } from "./_lib/host_capability.js";
59
59
  import {
@@ -106,6 +106,19 @@ export interface OrchestrationReport {
106
106
  /** cost.budgets.per_tier ceilings + live cool-down state per tier. */
107
107
  tier_budgets: Record<string, { ceiling_usd: number | null; cooldown_until_ms: number }>;
108
108
  host_manifest: HostCapabilityManifest;
109
+ /**
110
+ * The host identifier the capability registry was keyed on, and whether it
111
+ * was OBSERVED (given via `--platform`) or merely ASSUMED (the CLI default).
112
+ *
113
+ * The doctor has no host detection: `main()` defaults to `claude`. Once
114
+ * capability resolution keys on that string, an assumed platform produces a
115
+ * confident-looking `subagent_spawn` for a host the user may not be on —
116
+ * so the assumption is reported next to the value it decided, and a reader
117
+ * can falsify it. Without this pair the misdiagnosis is invisible in the
118
+ * output, which is worse than the pre-registry bug it replaced.
119
+ */
120
+ host_platform: string;
121
+ host_platform_assumed: boolean;
109
122
  /** Verdict of the real activation gate for a canonical delegable probe. */
110
123
  activation: { action: string; reason: string };
111
124
  cost_budgets: Record<string, unknown>;
@@ -194,18 +207,40 @@ export interface DoctorReport {
194
207
  /**
195
208
  * Orchestration-routing state: settings gates, host capability, budget
196
209
  * inputs, and (optionally) a dry-run classification for a sample prompt.
197
- * Read-only — mirrors exactly what the delegation layer would resolve.
210
+ * Read-only — it mirrors what the delegation layer resolves FOR `platform`.
211
+ * That qualifier is load-bearing: the hook reads its platform off a real
212
+ * envelope, this function is told one, and the two agree only when the
213
+ * caller was told the truth (see `platform_assumed`).
214
+ *
215
+ * `platform` is the host identifier the committed capability registry is
216
+ * keyed on — the same string a hook envelope's `platform` field carries.
217
+ * It is REQUIRED, not optional: resolving through `normalizeHostManifest`
218
+ * alone (as this function did until the registry landed) skips the registry
219
+ * row entirely, so on a fresh clone with no `subagents.host_capabilities`
220
+ * override the doctor reported `subagent_spawn: false` while
221
+ * `delegation_nudge_hook` — which does call `resolveHostCapabilities` —
222
+ * reported `true`. Two readers of one fact disagreeing is worse than either
223
+ * answer, and the diagnostic is the one a user runs precisely to check the
224
+ * other. A required parameter makes that regression uncompilable; an
225
+ * optional one only discourages it in a comment, and a comment has never
226
+ * failed a build.
227
+ *
228
+ * `platform_assumed` says whether the caller OBSERVED the host or guessed
229
+ * it. It is not decoration: keying the registry on a guessed host is how
230
+ * this function would trade the bug it fixed for a mirrored one.
198
231
  */
199
232
  export function collect_orchestration(
200
233
  workspace_root: string,
201
234
  sample_prompt: string | null,
235
+ platform: string,
236
+ platform_assumed: boolean,
202
237
  ): OrchestrationReport {
203
238
  const settings = load_agent_settings({ cwd: workspace_root });
204
239
  const sub = (settings["subagents"] ?? {}) as Record<string, unknown>;
205
240
  const enabled = sub["enabled"] !== false;
206
241
  const auto = typeof sub["auto"] === "string" ? (sub["auto"] as string) : "ask";
207
242
  const downshift = sub["downshift"] !== false;
208
- const host_manifest = normalizeHostManifest(sub["host_capabilities"]);
243
+ const host_manifest = resolveHostCapabilities(platform, sub["host_capabilities"]);
209
244
  const activationInputs: ActivationInputs = {
210
245
  enabled,
211
246
  auto: (auto === "on" || auto === "off" ? auto : "ask") as ActivationInputs["auto"],
@@ -260,6 +295,8 @@ export function collect_orchestration(
260
295
  budget_routing,
261
296
  tier_budgets,
262
297
  host_manifest,
298
+ host_platform: platform,
299
+ host_platform_assumed: platform_assumed,
263
300
  activation: { action: probe.action, reason: probe.reason },
264
301
  cost_budgets,
265
302
  ledger_present,
@@ -410,6 +447,15 @@ function _freshness(no_freshness: boolean): FreshnessReport {
410
447
 
411
448
  export function collect_report(options: {
412
449
  platform: string;
450
+ /**
451
+ * True when `platform` is the CLI's fallback rather than a value the caller
452
+ * observed or passed. Optional so existing callers keep compiling, and
453
+ * defaulting to `true` on purpose: a caller that did not say where the
454
+ * platform came from has not established that it observed one, and the
455
+ * conservative reading is the one that shows the caveat rather than the one
456
+ * that hides it.
457
+ */
458
+ platform_assumed?: boolean;
413
459
  workspace_root: string;
414
460
  no_freshness: boolean;
415
461
  classify?: string | null;
@@ -440,7 +486,12 @@ export function collect_report(options: {
440
486
  chain: chain.map((c) => c.name),
441
487
  gates,
442
488
  freshness: _freshness(options.no_freshness),
443
- orchestration: collect_orchestration(options.workspace_root, options.classify ?? null),
489
+ orchestration: collect_orchestration(
490
+ options.workspace_root,
491
+ options.classify ?? null,
492
+ options.platform,
493
+ options.platform_assumed ?? true,
494
+ ),
444
495
  bridge_table,
445
496
  };
446
497
  }
@@ -466,9 +517,18 @@ function _render(report: DoctorReport): string {
466
517
  const o = report.orchestration;
467
518
  lines.push(
468
519
  `Orchestration: enabled=${o.enabled} · auto=${o.auto} · downshift=${o.downshift} · ` +
469
- `budget_routing=${o.budget_routing} · subagent_spawn=${o.host_manifest.subagent_spawn} · ` +
520
+ `budget_routing=${o.budget_routing} · subagent_spawn=${o.host_manifest.subagent_spawn} ` +
521
+ `(host=${o.host_platform}${o.host_platform_assumed ? ", ASSUMED" : ", observed"}) · ` +
470
522
  `ledger=${o.ledger_present ? "present" : "absent"}`,
471
523
  );
524
+ if (o.host_platform_assumed) {
525
+ // Without this line the reader cannot tell a measured capability from one
526
+ // the registry produced for a host nobody checked they were on.
527
+ lines.push(
528
+ ` ⚠️ host not detected — '${o.host_platform}' is this command's default, ` +
529
+ `and the capability registry is keyed on it. Pass --platform <host> to check another.`,
530
+ );
531
+ }
472
532
  const tierBits = Object.entries(o.tier_budgets).map(([t, s]) => {
473
533
  const cap = s.ceiling_usd === null ? "no cap" : `$${s.ceiling_usd}`;
474
534
  const cool = s.cooldown_until_ms > 0 ? " COOLING" : "";
@@ -499,7 +559,11 @@ function _render(report: DoctorReport): string {
499
559
 
500
560
  export function main(argv?: string[]): number {
501
561
  const args = argv ?? process.argv.slice(2);
562
+ // The default is a guess, and since the capability registry is keyed on it
563
+ // the guess now decides `subagent_spawn`. Track where the value came from so
564
+ // the report can say so — there is no host detection here to replace it with.
502
565
  let platform = "claude";
566
+ let platform_assumed = true;
503
567
  let workspace_root = process.cwd();
504
568
  let json = false;
505
569
  let strict = false;
@@ -509,6 +573,7 @@ export function main(argv?: string[]): number {
509
573
  const a = args[i];
510
574
  if (a === "--platform" && args[i + 1]) {
511
575
  platform = String(args[(i += 1)]);
576
+ platform_assumed = false;
512
577
  } else if (a === "--workspace" && args[i + 1]) {
513
578
  workspace_root = path.resolve(String(args[(i += 1)]));
514
579
  } else if (a === "--classify" && args[i + 1]) {
@@ -521,7 +586,13 @@ export function main(argv?: string[]): number {
521
586
  no_freshness = true;
522
587
  }
523
588
  }
524
- const report = collect_report({ platform, workspace_root, no_freshness, classify });
589
+ const report = collect_report({
590
+ platform,
591
+ platform_assumed,
592
+ workspace_root,
593
+ no_freshness,
594
+ classify,
595
+ });
525
596
  if (json) {
526
597
  process.stdout.write(`${JSON.stringify(report as unknown as JsonValue, null, 2)}\n`);
527
598
  } else {
@@ -154,6 +154,11 @@
154
154
  },
155
155
  "description": "What mechanically enforces this rule. `validator:<script>` and `test:<path>` must resolve on disk AND be wired into a taskfile, workflow, or the hook manifest — a script that exists but runs nowhere resolves to unwired, not covered. `hook:<name>` must be registered in hook_manifest.yaml with fail_closed: true; a hook that never blocks is `observer:`, not `validator:`. `none` is legal and counted — an honest gap beats a false claim. Resolved, not trusted, by check_enforcement_coverage.ts."
156
156
  },
157
+ "obligation_frequency": {
158
+ "type": "string",
159
+ "enum": ["per-edit", "per-turn", "per-task", "per-session", "per-event", "per-commit", "per-file-write", "none"],
160
+ "description": "How often this rule's obligation comes due — the question `enforced_by` does not answer. `enforced_by` asks *does something carry this*; a carrier can be wired, reachable and firing and still fire in the wrong slot (session-canary: a per-task obligation carried by a session_start hook, measured dropped on ~13 of 15 task starts while counting as enforced). Joined against the carrier's per-slot-and-platform firing frequency by check_enforcement_coverage.ts. The value set is a FOREST, not a chain — three roots that never cover one another: lifecycle (`per-turn` > `per-task` > `per-session`), repository-write (`per-commit`, `per-file-write`), and external-event (`per-event`). `per-edit` is a fourth, incomparable-to-`per-turn` case: pre/post_tool_use fire per tool call, so a conversational turn with no tool call fires them zero times. `none` = no recurring obligation (a pointer or reference rule). Required on every non-kernel rule by validate_frontmatter.ts; the nine kernel rules are exempt because block_kernel_rule_writes.ts denies agent writes to them, and that exemption is derived from _lib/kernel_rules.ts so it closes itself the moment a rule leaves the kernel."
161
+ },
157
162
  "trust": {
158
163
  "type": "object",
159
164
  "additionalProperties": false,
@@ -0,0 +1,240 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Self-repair CLI — the queue reader and the single gated outward step.
4
+ *
5
+ * self-repair:status list open records
6
+ * self-repair:release <fp> [--dry-run] [--repo <owner/name>]
7
+ *
8
+ * `release` is the ONE keystroke the loop is designed around: everything up to
9
+ * it — detection, dedup, analysis, the authored fix, the commit — happens
10
+ * without the user. Publishing does not, because a push and a PR/issue are
11
+ * both Hard-Floor actions under `non-destructive-by-default`, and no standing
12
+ * instruction lifts that floor. Running this command IS the user's word.
13
+ *
14
+ * Route selection follows the spec exactly: a pull request when the fix can be
15
+ * authored and pushed, an issue when it cannot, and a local record when nothing
16
+ * can leave the machine. `--dry-run` prints the resolved route and the rendered
17
+ * body and touches no network — that is the path the tests drive.
18
+ *
19
+ * Fail-closed on privacy: the record's own text goes through the audited
20
+ * privacy floor before anything leaves the machine, and a refusal downgrades
21
+ * the record to local-only rather than scrubbing it into publishable shape.
22
+ */
23
+ import { spawnSync } from 'node:child_process';
24
+ import * as fs from 'node:fs';
25
+ import * as path from 'node:path';
26
+ import { fileURLToPath, pathToFileURL } from 'node:url';
27
+
28
+ import {
29
+ chooseEgressRoute,
30
+ type DefectRecord,
31
+ type EgressCapability,
32
+ type EgressRoute,
33
+ egressBlockedReason,
34
+ renderReport,
35
+ } from './_lib/self_repair.js';
36
+ import { listRecords, markReleased, openRecords, readRecord } from './_lib/self_repair_store.js';
37
+
38
+ export const UPSTREAM_REPO = 'event4u-app/agent-config';
39
+
40
+ const EXIT_OK = 0;
41
+ const EXIT_FAIL = 1;
42
+
43
+ /** Probe result for one machine — injectable so tests never shell out. */
44
+ export interface Probe {
45
+ agentConfigCheckout: string | null;
46
+ ghAuthenticated: boolean;
47
+ canPush: boolean;
48
+ }
49
+
50
+ function run(cmd: string, args: string[], cwd?: string): { ok: boolean; out: string } {
51
+ const r = spawnSync(cmd, args, { cwd, encoding: 'utf-8' });
52
+ return { ok: r.status === 0, out: `${r.stdout ?? ''}${r.stderr ?? ''}` };
53
+ }
54
+
55
+ /**
56
+ * Where the fix can be authored. `AGENT_CONFIG_SOURCE` wins; otherwise the CWD
57
+ * when it IS the package checkout. A consumer project has neither — that is the
58
+ * normal case, and the reason the issue fallback exists.
59
+ */
60
+ export function findAgentConfigCheckout(env: NodeJS.ProcessEnv, cwd: string): string | null {
61
+ const explicit = env['AGENT_CONFIG_SOURCE'];
62
+ if (explicit && fs.existsSync(path.join(explicit, 'src', 'rules'))) {
63
+ return explicit;
64
+ }
65
+ if (
66
+ fs.existsSync(path.join(cwd, 'src', 'rules')) &&
67
+ fs.existsSync(path.join(cwd, 'src', 'scripts', 'hook_manifest.yaml'))
68
+ ) {
69
+ return cwd;
70
+ }
71
+ return null;
72
+ }
73
+
74
+ export function probeMachine(env: NodeJS.ProcessEnv, cwd: string): Probe {
75
+ const checkout = findAgentConfigCheckout(env, cwd);
76
+ const gh = run('gh', ['auth', 'status']);
77
+ const canPush = checkout !== null && run('git', ['remote'], checkout).out.trim().length > 0;
78
+ return { agentConfigCheckout: checkout, ghAuthenticated: gh.ok, canPush };
79
+ }
80
+
81
+ export function capabilityOf(probe: Probe): EgressCapability {
82
+ return {
83
+ hasAgentConfigCheckout: probe.agentConfigCheckout !== null,
84
+ ghAuthenticated: probe.ghAuthenticated,
85
+ canPush: probe.canPush,
86
+ };
87
+ }
88
+
89
+ /** Title line for the PR / issue — one per defect class, stable across reruns. */
90
+ export function titleFor(record: DefectRecord): string {
91
+ return `fix(self-repair): ${record.defect_class} — ${record.occurrences}x observed`;
92
+ }
93
+
94
+ export interface ReleasePlan {
95
+ route: EgressRoute;
96
+ blocked: string | null;
97
+ title: string;
98
+ body: string;
99
+ }
100
+
101
+ /** Pure: everything `release` decides before it touches the network. */
102
+ export function planRelease(record: DefectRecord, probe: Probe, repoRoot: string): ReleasePlan {
103
+ const blocked = egressBlockedReason(record, repoRoot);
104
+ const route: EgressRoute =
105
+ blocked !== null ? 'local-only' : chooseEgressRoute(capabilityOf(probe));
106
+ return { route, blocked, title: titleFor(record), body: renderReport(record, route) };
107
+ }
108
+
109
+ function statusCmd(root: string): number {
110
+ const open = openRecords(root);
111
+ const all = listRecords(root);
112
+ if (all.length === 0) {
113
+ process.stdout.write('self-repair: no records.\n');
114
+ return EXIT_OK;
115
+ }
116
+ process.stdout.write(`self-repair: ${open.length} open of ${all.length} record(s)\n\n`);
117
+ for (const r of all) {
118
+ const mark = r.status === 'open' ? 'o' : '.';
119
+ process.stdout.write(
120
+ ` ${mark} ${r.fingerprint} ${r.defect_class} ${r.occurrences}x ${r.last_seen}\n` +
121
+ ` ${r.evidence}\n`,
122
+ );
123
+ }
124
+ process.stdout.write('\nRelease one: agent-config self-repair:release <fingerprint>\n');
125
+ return EXIT_OK;
126
+ }
127
+
128
+ function releaseCmd(root: string, argv: string[]): number {
129
+ const fp = argv.find((a) => !a.startsWith('-'));
130
+ if (!fp) {
131
+ process.stderr.write('self-repair:release needs a fingerprint (see self-repair:status).\n');
132
+ return EXIT_FAIL;
133
+ }
134
+ const record = readRecord(root, fp);
135
+ if (record === null) {
136
+ process.stderr.write(`self-repair:release: no record ${fp}.\n`);
137
+ return EXIT_FAIL;
138
+ }
139
+ const dryRun = argv.includes('--dry-run');
140
+ const repoArg = argv.indexOf('--repo');
141
+ const repo = repoArg >= 0 ? (argv[repoArg + 1] ?? UPSTREAM_REPO) : UPSTREAM_REPO;
142
+ const probe = probeMachine(process.env, process.cwd());
143
+ const plan = planRelease(record, probe, root);
144
+
145
+ process.stdout.write(`self-repair:release ${fp} -> route=${plan.route}\n`);
146
+ if (plan.blocked !== null) {
147
+ process.stdout.write(` WARN ${plan.blocked}\n`);
148
+ }
149
+ if (dryRun) {
150
+ process.stdout.write(`\n--- ${plan.title} ---\n${plan.body}\n`);
151
+ return EXIT_OK;
152
+ }
153
+ if (plan.route === 'local-only') {
154
+ process.stdout.write(
155
+ ' Nothing published. The record stays in agents/runtime/self-repair/.\n',
156
+ );
157
+ return EXIT_OK;
158
+ }
159
+ if (plan.route === 'issue') {
160
+ const r = run('gh', [
161
+ 'issue',
162
+ 'create',
163
+ '--repo',
164
+ repo,
165
+ '--title',
166
+ plan.title,
167
+ '--body',
168
+ plan.body,
169
+ ]);
170
+ process.stdout.write(`${r.out}\n`);
171
+ if (!r.ok) {
172
+ return EXIT_FAIL;
173
+ }
174
+ } else {
175
+ const checkout = probe.agentConfigCheckout!;
176
+ const branch = run('git', ['branch', '--show-current'], checkout).out.trim();
177
+ if (!branch || branch === 'main') {
178
+ process.stderr.write(
179
+ ' The fix must sit on its own branch in the agent-config checkout ' +
180
+ `(currently: ${branch || 'detached'}).\n`,
181
+ );
182
+ return EXIT_FAIL;
183
+ }
184
+ const pushed = run('git', ['push', '-u', 'origin', branch], checkout);
185
+ if (!pushed.ok) {
186
+ process.stderr.write(`${pushed.out}\n`);
187
+ return EXIT_FAIL;
188
+ }
189
+ const pr = run(
190
+ 'gh',
191
+ ['pr', 'create', '--repo', repo, '--title', plan.title, '--body', plan.body],
192
+ checkout,
193
+ );
194
+ process.stdout.write(`${pr.out}\n`);
195
+ if (!pr.ok) {
196
+ return EXIT_FAIL;
197
+ }
198
+ }
199
+ markReleased(root, fp, new Date().toISOString());
200
+ return EXIT_OK;
201
+ }
202
+
203
+ export function main(argv: readonly string[]): number {
204
+ const root = process.cwd();
205
+ const sub = argv[0] ?? 'status';
206
+ if (sub === 'status') {
207
+ return statusCmd(root);
208
+ }
209
+ if (sub === 'release') {
210
+ return releaseCmd(root, [...argv.slice(1)]);
211
+ }
212
+ process.stderr.write(`self-repair: unknown subcommand '${sub}' (status | release).\n`);
213
+ return EXIT_FAIL;
214
+ }
215
+
216
+ declare const __AGENT_CONFIG_BUNDLE__: boolean | undefined;
217
+ function isCliEntry(): boolean {
218
+ if (typeof __AGENT_CONFIG_BUNDLE__ !== 'undefined' && __AGENT_CONFIG_BUNDLE__) {
219
+ return false;
220
+ }
221
+ if (process.argv[1] === undefined) {
222
+ return false;
223
+ }
224
+ const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
225
+ if (import.meta.url === argvUrl) {
226
+ return true;
227
+ }
228
+ try {
229
+ return (
230
+ fs.realpathSync(fileURLToPath(import.meta.url)) ===
231
+ fs.realpathSync(path.resolve(process.argv[1]))
232
+ );
233
+ } catch {
234
+ return false;
235
+ }
236
+ }
237
+
238
+ if (isCliEntry()) {
239
+ process.exit(main(process.argv.slice(2)));
240
+ }
@@ -0,0 +1,241 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Self-repair hook — turns an observed agent defect into a queued, fixable
4
+ * record without the user having to file anything.
5
+ *
6
+ * Three slots, one record shape:
7
+ *
8
+ * - `user_prompt_submit` — the user states the agent worked wrongly. Opens a
9
+ * `user-reported` record and injects the open-queue line so the agent picks
10
+ * the work up in the same turn.
11
+ * - `stop` — the finished turn is run past the deterministic detectors. A hit
12
+ * opens a `self-detected` record. Nothing is injected here: a reminder that
13
+ * fires after the reply cannot shape it, and the queue line at the next
14
+ * prompt reaches the same agent one turn later at a fraction of the tokens.
15
+ * - every other slot — clean no-op.
16
+ *
17
+ * ADVISORY BY CONSTRUCTION (`fail_closed: false`, always exit 0). Blocking a
18
+ * turn-end on a heuristic is the failure mode the enforcement-projection and
19
+ * recursive-verification nulls both warn about; this hook records, it does not
20
+ * gate. The one outward step (push + PR, or an issue) stays behind the
21
+ * `non-destructive-by-default` Hard Floor and lives in `self_repair_cli.ts`.
22
+ *
23
+ * Honest scope: the detector set is small and deterministic on purpose. It
24
+ * covers the two defect classes the conformance audits actually measured, plus
25
+ * the user-report path. It does NOT "check all rules were followed" — most
26
+ * obligations are model-carried and unobservable from a transcript, and a
27
+ * detector that guessed at them would manufacture defects and flood the queue.
28
+ */
29
+ import * as fs from 'node:fs';
30
+ import * as path from 'node:path';
31
+ import { fileURLToPath, pathToFileURL } from 'node:url';
32
+
33
+ import {
34
+ type DefectFinding,
35
+ detectUserReport,
36
+ runDetectors,
37
+ type TurnSnapshot,
38
+ } from './_lib/self_repair.js';
39
+ import { openRecords, upsertFinding } from './_lib/self_repair_store.js';
40
+ import { readHookStdin } from './hooks/hook_stdin.js';
41
+
42
+ const EXIT_ALLOW = 0;
43
+
44
+ type JsonValue = string | number | boolean | null | JsonValue[] | { [k: string]: JsonValue };
45
+ type JsonObject = { [k: string]: JsonValue };
46
+
47
+ function isObject(v: unknown): v is JsonObject {
48
+ return typeof v === 'object' && v !== null && !Array.isArray(v);
49
+ }
50
+
51
+ function str(v: JsonValue | undefined): string {
52
+ return typeof v === 'string' ? v : '';
53
+ }
54
+
55
+ function workspaceRoot(env: JsonObject): string {
56
+ const v = env['workspace_root'];
57
+ return typeof v === 'string' && v ? v : process.cwd();
58
+ }
59
+
60
+ /**
61
+ * Last assistant reply + the tool commands of the turn, read from the host's
62
+ * JSONL transcript. Absent or unreadable transcript → an empty snapshot, which
63
+ * every detector declines.
64
+ */
65
+ export function readTurn(transcriptPath: string, pinned: 'de' | 'en' | null): TurnSnapshot {
66
+ const empty: TurnSnapshot = { prompt: '', reply: '', toolCommands: [], pinnedLanguage: pinned };
67
+ if (!transcriptPath) {
68
+ return empty;
69
+ }
70
+ let lines: string[];
71
+ try {
72
+ lines = fs.readFileSync(transcriptPath, 'utf-8').split('\n');
73
+ } catch {
74
+ return empty;
75
+ }
76
+ let reply = '';
77
+ let prompt = '';
78
+ const toolCommands: string[] = [];
79
+ for (const raw of lines) {
80
+ const line = raw.trim();
81
+ if (!line) {
82
+ continue;
83
+ }
84
+ let obj: unknown;
85
+ try {
86
+ obj = JSON.parse(line);
87
+ } catch {
88
+ continue;
89
+ }
90
+ if (!isObject(obj)) {
91
+ continue;
92
+ }
93
+ const msg = obj['message'];
94
+ if (!isObject(msg)) {
95
+ continue;
96
+ }
97
+ const content = msg['content'];
98
+ if (obj['type'] === 'user' && typeof content === 'string') {
99
+ prompt = content;
100
+ }
101
+ if (obj['type'] !== 'assistant') {
102
+ continue;
103
+ }
104
+ if (typeof content === 'string') {
105
+ reply = content;
106
+ continue;
107
+ }
108
+ if (!Array.isArray(content)) {
109
+ continue;
110
+ }
111
+ const parts: string[] = [];
112
+ for (const blk of content) {
113
+ if (!isObject(blk)) {
114
+ continue;
115
+ }
116
+ if (blk['type'] === 'text' && typeof blk['text'] === 'string') {
117
+ parts.push(blk['text']);
118
+ }
119
+ if (blk['type'] === 'tool_use') {
120
+ const input = blk['input'];
121
+ const name = str(blk['name']);
122
+ const cmd = isObject(input) ? str(input['command']) : '';
123
+ toolCommands.push(`${name} ${cmd}`.trim());
124
+ }
125
+ }
126
+ if (parts.length > 0) {
127
+ reply = parts.join('\n');
128
+ }
129
+ }
130
+ return { prompt, reply, toolCommands, pinnedLanguage: pinned };
131
+ }
132
+
133
+ /** The queue line injected at prompt time when records are waiting. */
134
+ export function buildQueueLine(count: number, newest: string): string {
135
+ return (
136
+ `<self-repair-queue>${count} open agent-config defect record(s) ` +
137
+ `(newest: ${newest}). These are defects in the AGENT CONFIG, not in this ` +
138
+ `project. Per the self-repair rule: analyse the newest record, author the ` +
139
+ `fix against agent-config, and prepare the release — the outward step ` +
140
+ `(push + PR, or an issue) still needs the user's word this turn. ` +
141
+ `Records: agents/runtime/self-repair/</self-repair-queue>`
142
+ );
143
+ }
144
+
145
+ function pinnedLanguage(env: JsonObject): 'de' | 'en' | null {
146
+ const v = str(env['reply_language'] ?? env['language']).toLowerCase();
147
+ if (v.startsWith('de')) {
148
+ return 'de';
149
+ }
150
+ if (v.startsWith('en')) {
151
+ return 'en';
152
+ }
153
+ return null;
154
+ }
155
+
156
+ export function main(): number {
157
+ let env: JsonObject = {};
158
+ try {
159
+ const raw = readHookStdin();
160
+ const parsed: unknown = raw.trim() ? JSON.parse(raw) : {};
161
+ env = isObject(parsed) ? parsed : {};
162
+ } catch {
163
+ // malformed stdin — never block, just no-op below
164
+ }
165
+
166
+ const slot = str(env['event']) || 'session_start';
167
+ if (slot !== 'user_prompt_submit' && slot !== 'stop') {
168
+ return EXIT_ALLOW;
169
+ }
170
+
171
+ const root = workspaceRoot(env);
172
+ const now = new Date().toISOString();
173
+ const findings: DefectFinding[] = [];
174
+
175
+ if (slot === 'user_prompt_submit') {
176
+ const prompt = str(env['prompt'] ?? env['user_prompt'] ?? env['text']);
177
+ const f = detectUserReport(prompt);
178
+ if (f !== null) {
179
+ findings.push(f);
180
+ }
181
+ } else {
182
+ const tp = str(env['transcript_path'] ?? env['transcriptPath']);
183
+ findings.push(...runDetectors(readTurn(tp, pinnedLanguage(env))));
184
+ }
185
+
186
+ for (const f of findings) {
187
+ try {
188
+ upsertFinding(root, f, now);
189
+ } catch {
190
+ // an unwritable store must never cost the user their turn
191
+ }
192
+ }
193
+
194
+ // Injection happens at prompt time only — see the header note on `stop`.
195
+ if (slot !== 'user_prompt_submit') {
196
+ return EXIT_ALLOW;
197
+ }
198
+ let open: ReturnType<typeof openRecords>;
199
+ try {
200
+ open = openRecords(root);
201
+ } catch {
202
+ return EXIT_ALLOW;
203
+ }
204
+ if (open.length === 0) {
205
+ return EXIT_ALLOW;
206
+ }
207
+ process.stdout.write(
208
+ `${JSON.stringify({
209
+ decision: 'allow',
210
+ reason: `self-repair: ${open.length} open record(s)`,
211
+ context: buildQueueLine(open.length, open[0]!.defect_class),
212
+ })}\n`,
213
+ );
214
+ return EXIT_ALLOW;
215
+ }
216
+
217
+ declare const __AGENT_CONFIG_BUNDLE__: boolean | undefined;
218
+ function isCliEntry(): boolean {
219
+ if (typeof __AGENT_CONFIG_BUNDLE__ !== 'undefined' && __AGENT_CONFIG_BUNDLE__) {
220
+ return false;
221
+ }
222
+ if (process.argv[1] === undefined) {
223
+ return false;
224
+ }
225
+ const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
226
+ if (import.meta.url === argvUrl) {
227
+ return true;
228
+ }
229
+ try {
230
+ return (
231
+ fs.realpathSync(fileURLToPath(import.meta.url)) ===
232
+ fs.realpathSync(path.resolve(process.argv[1]))
233
+ );
234
+ } catch {
235
+ return false;
236
+ }
237
+ }
238
+
239
+ if (isCliEntry()) {
240
+ process.exit(main());
241
+ }