@event4u/agent-config 16.0.0 → 16.2.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 (382) hide show
  1. package/.augment-plugin/marketplace.json +3 -3
  2. package/.augment-plugin/plugin.json +1 -1
  3. package/.claude-plugin/marketplace.json +1 -1
  4. package/CHANGELOG.md +289 -140
  5. package/CONTRIBUTING.md +9 -0
  6. package/README.md +2 -2
  7. package/dist/agent-src/commands/analyze/inbox.md +26 -0
  8. package/dist/agent-src/commands/analyze/roadmap-repos.md +27 -0
  9. package/dist/agent-src/commands/challenge-me/closure.md +178 -0
  10. package/dist/agent-src/commands/challenge-me.md +9 -2
  11. package/dist/agent-src/commands/council/default.md +25 -5
  12. package/dist/agent-src/commands/feature/plan.md +28 -1
  13. package/dist/agent-src/commands/feature/roadmap.md +28 -1
  14. package/dist/agent-src/commands/implement-ticket.md +27 -0
  15. package/dist/agent-src/commands/jira-ticket.md +27 -0
  16. package/dist/agent-src/commands/pr/create.md +29 -0
  17. package/dist/agent-src/commands/pr/merge.md +60 -9
  18. package/dist/agent-src/commands/roadmap/ai-council.md +7 -1
  19. package/dist/agent-src/commands/roadmap/create.md +74 -27
  20. package/dist/agent-src/commands/roadmap/materialize.md +27 -0
  21. package/dist/agent-src/commands/roadmap/process-full.md +123 -18
  22. package/dist/agent-src/commands/team.md +22 -0
  23. package/dist/agent-src/commands/work.md +4 -0
  24. package/dist/agent-src/contexts/authority/scope-growth-ownership.md +80 -0
  25. package/dist/agent-src/contexts/communication/rules-auto/user-interaction-mechanics.md +47 -0
  26. package/dist/agent-src/contexts/execution/auto-orchestration-activation.md +44 -3
  27. package/dist/agent-src/contexts/execution/autonomy-mechanics.md +19 -23
  28. package/dist/agent-src/contexts/execution/decision-closure-execution.md +93 -0
  29. package/dist/agent-src/contexts/execution/fix-loop-ladder.md +108 -0
  30. package/dist/agent-src/contexts/execution/plan-confidence-gate.md +4 -2
  31. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +75 -6
  32. package/dist/agent-src/guidelines/agent-infra/active-remediation-mechanics.md +57 -0
  33. package/dist/agent-src/guidelines/agent-infra/evaluator-independence-mechanics.md +102 -0
  34. package/dist/agent-src/guidelines/design-fidelity-routing.md +29 -0
  35. package/dist/agent-src/rules/autonomous-execution.md +15 -8
  36. package/dist/agent-src/rules/delegation-policy.md +23 -45
  37. package/dist/agent-src/rules/design-fidelity.md +32 -37
  38. package/dist/agent-src/rules/evaluator-independence.md +43 -49
  39. package/dist/agent-src/rules/test-first.md +41 -0
  40. package/dist/agent-src/rules/ui-audit-gate.md +1 -2
  41. package/dist/agent-src/skills/accessibility-auditor/data/aria-patterns.csv +1 -1
  42. package/dist/agent-src/skills/accessibility-auditor/data/manifest.json +3 -3
  43. package/dist/agent-src/skills/ai-council/SKILL.md +11 -3
  44. package/dist/agent-src/skills/api-design/data/manifest.json +3 -3
  45. package/dist/agent-src/skills/database/data/manifest.json +4 -4
  46. package/dist/agent-src/skills/database/data/query-tuning.csv +3 -3
  47. package/dist/agent-src/skills/design-review/SKILL.md +80 -4
  48. package/dist/agent-src/skills/design-review/references/verification-automation.md +32 -0
  49. package/dist/agent-src/skills/existing-ui-audit/SKILL.md +30 -5
  50. package/dist/agent-src/skills/existing-ui-audit/references/component-taxonomy-detection.md +56 -0
  51. package/dist/agent-src/skills/experiment-loop/SKILL.md +31 -1
  52. package/dist/agent-src/skills/markitdown/SKILL.md +4 -0
  53. package/dist/agent-src/skills/playwright-testing/SKILL.md +30 -6
  54. package/dist/agent-src/skills/roadmap-writing/SKILL.md +1 -1
  55. package/dist/agent-src/skills/storybook-workshop/SKILL.md +42 -3
  56. package/dist/agent-src/skills/threat-modeling/SKILL.md +9 -4
  57. package/dist/agent-src/skills/threat-modeling/data/manifest.json +4 -4
  58. package/dist/agent-src/skills/ui-component-architect/SKILL.md +23 -0
  59. package/dist/agent-src/skills/verify-repair-loop/SKILL.md +37 -3
  60. package/dist/agent-src/templates/agent-settings.md +1 -1
  61. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  62. package/dist/agent-src/templates/roadmaps.md +218 -0
  63. package/dist/agent-src/templates/scripts/work_engine/directives/backend/_self_fix.ts +62 -2
  64. package/dist/agent-src/templates/scripts/work_engine/directives/backend/test.ts +1 -0
  65. package/dist/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +1 -0
  66. package/dist/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +10 -4
  67. package/dist/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +260 -21
  68. package/dist/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +22 -3
  69. package/dist/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +6 -1
  70. package/dist/agent-src/templates/scripts/work_engine/state.ts +57 -7
  71. package/dist/agent-src/templates/scripts/work_engine/taxonomy/detect.ts +500 -0
  72. package/dist/agent-src/templates/scripts/work_engine/taxonomy/place.ts +183 -0
  73. package/dist/cli/python/knowledge_ingest.js +1 -1
  74. package/dist/cli/python/workspace_drive.js +1 -1
  75. package/dist/cli-delegate/check_update_banner.js +40 -2
  76. package/dist/cli-delegate/chunk-4E2W63DA.js +335 -0
  77. package/dist/cli-delegate/{chunk-S3VFXMTM.js → chunk-4XFP2Y3E.js} +1 -1
  78. package/dist/cli-delegate/chunk-5R7HOLAF.js +99 -0
  79. package/dist/cli-delegate/{chunk-IVBPRCQO.js → chunk-6KFEC3G7.js} +1 -1
  80. package/dist/cli-delegate/{chunk-2TAQ2K4V.js → chunk-6KM6ORRY.js} +55 -386
  81. package/dist/cli-delegate/{chunk-AK6HODNQ.js → chunk-7XGBX6ZH.js} +973 -555
  82. package/dist/cli-delegate/{chunk-2XY7ILBT.js → chunk-FC75PGJE.js} +42 -2
  83. package/dist/cli-delegate/{chunk-55ACLDUE.js → chunk-FP36SWJD.js} +587 -141
  84. package/dist/cli-delegate/{chunk-3FKR64R5.js → chunk-IGD6ERX6.js} +62 -7
  85. package/dist/cli-delegate/{chunk-GYC3VRYN.js → chunk-JNMVLWLR.js} +99 -14
  86. package/dist/cli-delegate/{chunk-H32DGUJH.js → chunk-P6ZHP5W2.js} +1 -1
  87. package/dist/cli-delegate/{chunk-57PN4GYJ.js → chunk-RGNIWTEA.js} +1 -1
  88. package/dist/cli-delegate/chunk-ROYYTEZU.js +39 -0
  89. package/dist/cli-delegate/{chunk-2DK2VJWG.js → chunk-WGXTVKCQ.js} +20 -6
  90. package/dist/cli-delegate/chunk-YDPILCOL.js +152 -0
  91. package/dist/cli-delegate/{chunk-QJWAUMJW.js → chunk-YJ72UIAK.js} +730 -15
  92. package/dist/cli-delegate/cmd_analyze_session.js +1 -1
  93. package/dist/cli-delegate/cmd_brand_status.js +1 -1
  94. package/dist/cli-delegate/cmd_conformance.js +985 -831
  95. package/dist/cli-delegate/cmd_doctor.js +7 -5
  96. package/dist/cli-delegate/cmd_explain.js +2 -2
  97. package/dist/cli-delegate/cmd_export.js +9 -7
  98. package/dist/cli-delegate/cmd_fleet.js +4 -2
  99. package/dist/cli-delegate/cmd_handoff.js +7 -7
  100. package/dist/cli-delegate/cmd_mcp_available.js +1 -1
  101. package/dist/cli-delegate/cmd_memory_get.js +1 -1
  102. package/dist/cli-delegate/cmd_migrate.js +2 -2
  103. package/dist/cli-delegate/cmd_packs_active.js +2 -2
  104. package/dist/cli-delegate/cmd_preflight.js +4 -2
  105. package/dist/cli-delegate/cmd_prune.js +10 -8
  106. package/dist/cli-delegate/cmd_route_audit.js +3 -3
  107. package/dist/cli-delegate/cmd_route_explain.js +1 -1
  108. package/dist/cli-delegate/cmd_session_recycle.js +4 -4
  109. package/dist/cli-delegate/cmd_settings_get.js +28 -2
  110. package/dist/cli-delegate/cmd_settings_migrate.js +9 -7
  111. package/dist/cli-delegate/cmd_settings_set.js +1 -1
  112. package/dist/cli-delegate/cmd_sync.js +10 -8
  113. package/dist/cli-delegate/cmd_uninstall.js +16 -13
  114. package/dist/cli-delegate/cmd_update.js +1 -1
  115. package/dist/cli-delegate/cmd_upgrade.js +1 -1
  116. package/dist/cli-delegate/cmd_validate.js +12 -10
  117. package/dist/cli-delegate/cmd_versions.js +1 -1
  118. package/dist/discovery/deprecation-report.md +1 -1
  119. package/dist/discovery/discovery-manifest.json +90 -43
  120. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  121. package/dist/discovery/discovery-manifest.summary.md +4 -4
  122. package/dist/discovery/dormancy-report.md +1 -1
  123. package/dist/discovery/orphan-report.md +1 -1
  124. package/dist/discovery/packs.json +10 -8
  125. package/dist/discovery/trust-report.md +3 -3
  126. package/dist/discovery/workspaces.json +8 -6
  127. package/dist/hooks/dispatch.js +7682 -2772
  128. package/dist/hooks/dispatch.sha256 +1 -0
  129. package/dist/install/claudeRuleRewrite.js +14 -3
  130. package/dist/install/conflict.js +60 -10
  131. package/dist/install/conflictTracker.js +218 -0
  132. package/dist/install/fsPrimitives.js +54 -0
  133. package/dist/install/install.mjs +2200 -1559
  134. package/dist/install/manifestFiles.js +259 -0
  135. package/dist/install/preflight.js +14 -2
  136. package/dist/install/preserve.js +157 -0
  137. package/dist/install/recordedOwnership.js +132 -0
  138. package/dist/install/txlog.js +11 -2
  139. package/dist/mcp/registry-manifest.json +3 -3
  140. package/dist/mcp/server.json +2 -2
  141. package/dist/mcp/server.mjs +3066 -1470
  142. package/dist/router.json +1 -1
  143. package/dist/scripts/_lib/agent_settings.js +3 -2
  144. package/dist/scripts/_lib/fs_atomic.js +167 -0
  145. package/dist/scripts/_lib/installed_tools.js +460 -0
  146. package/dist/scripts/_lib/lean_projection_mode.js +3 -2
  147. package/dist/scripts/_lib/settings_renamed_keys.js +78 -0
  148. package/dist/server/routes/install.js +8 -0
  149. package/dist/server/routes/wizard.js +9 -3
  150. package/dist/server/schemas/settings.js +40 -5
  151. package/dist/shared/missionExecution.js +97 -0
  152. package/dist/ui/assets/index-Cwq4d0hX.js +23 -0
  153. package/dist/ui/assets/index-Cyjy8bWO.css +1 -0
  154. package/dist/ui/index.html +2 -2
  155. package/docs/MIGRATION.md +103 -0
  156. package/docs/contracts/settings-classes.md +28 -7
  157. package/docs/guidelines/agent-infra/active-remediation-mechanics.md +57 -0
  158. package/docs/guidelines/agent-infra/evaluator-independence-mechanics.md +102 -0
  159. package/docs/guidelines/design-fidelity-routing.md +29 -0
  160. package/package.json +4 -4
  161. package/src/agent-src/templates/scripts/work_engine/directives/backend/_self_fix.ts +62 -2
  162. package/src/agent-src/templates/scripts/work_engine/directives/backend/test.ts +1 -0
  163. package/src/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +1 -0
  164. package/src/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +10 -4
  165. package/src/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +260 -21
  166. package/src/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +22 -3
  167. package/src/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +6 -1
  168. package/src/agent-src/templates/scripts/work_engine/state.ts +57 -7
  169. package/src/agent-src/templates/scripts/work_engine/taxonomy/detect.ts +500 -0
  170. package/src/agent-src/templates/scripts/work_engine/taxonomy/place.ts +183 -0
  171. package/src/config/agent-settings.template.yml +168 -27
  172. package/src/config/auto-rule-budget.json +16 -0
  173. package/src/config/ci-local-parity.yml +36 -5
  174. package/src/config/continuity-surface.json +23 -2
  175. package/src/config/gate-coverage.yml +377 -13
  176. package/src/config/gate-violation-baselines.json +10 -6
  177. package/src/config/hook-latency-budget.json +21 -0
  178. package/src/config/hook-token-budget.json +4 -2
  179. package/src/config/loop-surfaces.yaml +215 -0
  180. package/src/config/metric-registry.yml +114 -0
  181. package/src/config/placeholder-drift-budget.json +10 -0
  182. package/src/config/platform-anchor.json +3 -2
  183. package/src/config/release-gate-locality.yml +11 -0
  184. package/src/config/routing-coverage-seed.json +33 -5
  185. package/src/config/rule-enforcement-baseline.json +0 -1
  186. package/src/install/claudeRuleRewrite.ts +16 -2
  187. package/src/install/conflict.ts +65 -9
  188. package/src/install/conflictTracker.ts +299 -0
  189. package/src/install/fsPrimitives.ts +54 -0
  190. package/src/install/manifestFiles.ts +312 -0
  191. package/src/install/preflight.ts +16 -2
  192. package/src/install/preserve.ts +205 -0
  193. package/src/install/recordedOwnership.ts +149 -0
  194. package/src/install/txlog.ts +11 -2
  195. package/src/install/types.ts +19 -4
  196. package/src/scripts/_cli/cmd_conformance.ts +88 -15
  197. package/src/scripts/_cli/cmd_doctor.ts +7 -20
  198. package/src/scripts/_cli/cmd_route_explain.ts +9 -2
  199. package/src/scripts/_cli/cmd_settings_get.ts +44 -0
  200. package/src/scripts/_cli/doctor_execution.ts +173 -0
  201. package/src/scripts/_cli/doctor_network_posture.ts +226 -0
  202. package/src/scripts/_lib/activation_payload.ts +247 -0
  203. package/src/scripts/_lib/agent_settings.ts +4 -2
  204. package/src/scripts/_lib/authority_path.ts +102 -0
  205. package/src/scripts/_lib/body_portable.ts +102 -0
  206. package/src/scripts/_lib/cascade_base.ts +94 -0
  207. package/src/scripts/_lib/cc_transcript.ts +132 -1
  208. package/src/scripts/_lib/changelog_eras.ts +7 -2
  209. package/src/scripts/_lib/concern_sla_window.ts +200 -0
  210. package/src/scripts/_lib/continuation_ladder.ts +149 -2
  211. package/src/scripts/_lib/council_transport.ts +129 -0
  212. package/src/scripts/_lib/delivery_ready.ts +112 -0
  213. package/src/scripts/_lib/forge_protection.ts +381 -0
  214. package/src/scripts/_lib/forge_reader.ts +558 -0
  215. package/src/scripts/_lib/hook_platform_keys.ts +27 -0
  216. package/src/scripts/_lib/host_env_write.ts +111 -0
  217. package/src/scripts/_lib/json_python_parity.ts +127 -0
  218. package/src/scripts/_lib/lean_projection_mode.ts +14 -3
  219. package/src/scripts/_lib/llm_proposer_transport.ts +14 -4
  220. package/src/scripts/_lib/loop_surfaces.ts +382 -0
  221. package/src/scripts/_lib/mission_record.ts +190 -0
  222. package/src/scripts/_lib/obligation_frequency.ts +217 -0
  223. package/src/scripts/_lib/obligations.ts +513 -0
  224. package/src/scripts/_lib/orchestration_record.ts +11 -20
  225. package/src/scripts/_lib/probe_inputs.ts +271 -0
  226. package/src/scripts/_lib/read_surface_scan.ts +322 -0
  227. package/src/scripts/_lib/release_highlights.ts +201 -11
  228. package/src/scripts/_lib/release_holds.ts +493 -0
  229. package/src/scripts/_lib/release_migration_gate.ts +184 -0
  230. package/src/scripts/_lib/retrieval_sanitize.ts +77 -16
  231. package/src/scripts/_lib/rides_along.ts +102 -0
  232. package/src/scripts/_lib/settings_renamed_keys.ts +82 -0
  233. package/src/scripts/_lib/shell_write_shapes.ts +108 -0
  234. package/src/scripts/_lib/structural_hiding.ts +622 -0
  235. package/src/scripts/_lib/test_delta.ts +166 -0
  236. package/src/scripts/_lib/test_provenance.ts +171 -0
  237. package/src/scripts/_lib/turn_end_refusals.ts +525 -4
  238. package/src/scripts/_lib/turn_end_transcript.ts +216 -0
  239. package/src/scripts/_lib/typed_op_grant.ts +246 -0
  240. package/src/scripts/_lib/typed_op_watch.ts +185 -0
  241. package/src/scripts/_lib/verification_command.ts +324 -0
  242. package/src/scripts/_lib/verification_evidence.ts +647 -0
  243. package/src/scripts/_lib/verify_clause.ts +280 -0
  244. package/src/scripts/ai_council/config.ts +8 -11
  245. package/src/scripts/ai_council/fallback_config.ts +5 -3
  246. package/src/scripts/ai_council/ownership_classes.ts +118 -0
  247. package/src/scripts/ai_team/team_dispatch.ts +4 -0
  248. package/src/scripts/ask_block_census.ts +285 -15
  249. package/src/scripts/before_complete_hook.ts +340 -23
  250. package/src/scripts/bench_hook_latency.ts +412 -5
  251. package/src/scripts/cache_realization_report.ts +61 -1
  252. package/src/scripts/check_argument_hint.ts +287 -0
  253. package/src/scripts/check_claims.ts +125 -1
  254. package/src/scripts/check_enforcement_coverage.ts +38 -9
  255. package/src/scripts/check_enforcement_matrix.ts +866 -0
  256. package/src/scripts/check_finding_dispositions.ts +22 -3
  257. package/src/scripts/check_gate_reachability.ts +173 -10
  258. package/src/scripts/check_generator_sync.ts +1025 -0
  259. package/src/scripts/check_host_docs_digest.ts +786 -0
  260. package/src/scripts/check_host_format_column.ts +559 -0
  261. package/src/scripts/check_input_references.ts +228 -0
  262. package/src/scripts/check_inputs_parity.ts +235 -0
  263. package/src/scripts/check_kernel_edit_ratified.ts +47 -5
  264. package/src/scripts/check_kill_switch_table.ts +294 -0
  265. package/src/scripts/check_placeholder_drift.ts +221 -0
  266. package/src/scripts/check_preamble_payload_budget.ts +75 -2
  267. package/src/scripts/check_read_surface_coverage.ts +515 -0
  268. package/src/scripts/check_release_highlights.ts +33 -1
  269. package/src/scripts/check_release_holds.ts +568 -0
  270. package/src/scripts/check_routing_coverage.ts +378 -11
  271. package/src/scripts/check_rule_projection_integrity.ts +22 -12
  272. package/src/scripts/check_static_parity.ts +17 -0
  273. package/src/scripts/check_test_delta.ts +294 -0
  274. package/src/scripts/check_test_weakening.ts +299 -0
  275. package/src/scripts/ci_settle.ts +145 -19
  276. package/src/scripts/closure_scan.ts +447 -0
  277. package/src/scripts/compile_hook_manifest.ts +102 -18
  278. package/src/scripts/council_record_shape.ts +209 -0
  279. package/src/scripts/external_sources_denylist.json +29 -4
  280. package/src/scripts/hook_manifest.json +1 -1
  281. package/src/scripts/hook_manifest.yaml +127 -11
  282. package/src/scripts/hooks/block_config_weakening.ts +229 -4
  283. package/src/scripts/hooks/block_kernel_rule_writes.ts +9 -78
  284. package/src/scripts/hooks/block_plumbing_writes.ts +314 -0
  285. package/src/scripts/hooks/bundle_integrity.ts +291 -0
  286. package/src/scripts/hooks/chain_nudge_hook.ts +1 -2
  287. package/src/scripts/hooks/code_graph_context_hook.ts +1 -2
  288. package/src/scripts/hooks/comment_discipline_hook.ts +1 -3
  289. package/src/scripts/hooks/concern_registry.ts +6 -0
  290. package/src/scripts/hooks/concern_timings.ts +94 -0
  291. package/src/scripts/hooks/delegation_nudge_hook.ts +1 -2
  292. package/src/scripts/hooks/design_pass_hook.ts +221 -5
  293. package/src/scripts/hooks/design_slop_hook.ts +1 -2
  294. package/src/scripts/hooks/dispatch_hook.ts +25 -19
  295. package/src/scripts/hooks/edit_shape_hook.ts +1 -2
  296. package/src/scripts/hooks/end_review_nudge_hook.ts +1 -1
  297. package/src/scripts/hooks/evidence_independence.ts +97 -2
  298. package/src/scripts/hooks/exit_codes.ts +128 -0
  299. package/src/scripts/hooks/host_lowering.json +1 -0
  300. package/src/scripts/hooks/host_lowering.ts +128 -3
  301. package/src/scripts/hooks/host_lowering.yaml +262 -46
  302. package/src/scripts/hooks/host_semantics.ts +30 -2
  303. package/src/scripts/hooks/injection_budget.ts +9 -2
  304. package/src/scripts/hooks/interruption_ledger_hook.ts +1 -2
  305. package/src/scripts/hooks/journal_record_hook.ts +1 -2
  306. package/src/scripts/hooks/memory_recall_hook.ts +539 -0
  307. package/src/scripts/hooks/obligation_settle_hook.ts +328 -0
  308. package/src/scripts/hooks/one_question_per_ask_hook.ts +25 -4
  309. package/src/scripts/hooks/orchestration_record_hook.ts +1 -2
  310. package/src/scripts/hooks/permission_gate.ts +1 -2
  311. package/src/scripts/hooks/push_settle_hook.ts +1 -3
  312. package/src/scripts/hooks/replay_hook.ts +2 -1
  313. package/src/scripts/hooks/reread_guard_hook.ts +1 -2
  314. package/src/scripts/hooks/review_baseline_hook.ts +1 -2
  315. package/src/scripts/hooks/rtk_wrap_hook.ts +1 -2
  316. package/src/scripts/hooks/rule_inject_hook.ts +55 -4
  317. package/src/scripts/hooks/run_continuation_hook.ts +30 -35
  318. package/src/scripts/hooks/skill_route_hook.ts +1 -2
  319. package/src/scripts/hooks/source_first_gate_hook.ts +1 -2
  320. package/src/scripts/hooks/spawn_guard_shadow_hook.ts +1 -2
  321. package/src/scripts/hooks/subagent_ledger_hook.ts +1 -2
  322. package/src/scripts/hooks/suggestion_capture_hook.ts +1 -1
  323. package/src/scripts/hooks/table_fingerprint.ts +49 -0
  324. package/src/scripts/hooks/telemetry_flush_hook.ts +1 -2
  325. package/src/scripts/hooks/telemetry_usage_hook.ts +1 -2
  326. package/src/scripts/hooks/tool_result_bytes_hook.ts +93 -9
  327. package/src/scripts/hooks/turn_end_gate_hook.ts +337 -324
  328. package/src/scripts/hooks/ui_route_nudge_hook.ts +1 -2
  329. package/src/scripts/hooks_doctor.ts +85 -0
  330. package/src/scripts/hooks_status.ts +19 -0
  331. package/src/scripts/install.ts +109 -210
  332. package/src/scripts/janitor.ts +7 -0
  333. package/src/scripts/lint_decision_classes.ts +464 -0
  334. package/src/scripts/lint_deny_text.ts +419 -0
  335. package/src/scripts/lint_exit_codes.ts +262 -0
  336. package/src/scripts/lint_hook_manifest.ts +66 -0
  337. package/src/scripts/lint_major_migration_sections.ts +450 -0
  338. package/src/scripts/lint_plan_risk_register.ts +47 -3
  339. package/src/scripts/lint_roadmap_blockers.ts +139 -12
  340. package/src/scripts/lint_roadmap_producers.ts +202 -0
  341. package/src/scripts/lint_skill_descriptions.ts +1 -1
  342. package/src/scripts/lint_skill_scripts_readonly.ts +1 -1
  343. package/src/scripts/mcp_server/prompts.ts +142 -1
  344. package/src/scripts/measure_rule_budget.ts +101 -5
  345. package/src/scripts/measure_skill_ranker_baseline.ts +305 -51
  346. package/src/scripts/measure_turn_end_gate.ts +193 -0
  347. package/src/scripts/memory_lookup.ts +1 -1
  348. package/src/scripts/new_roadmap.ts +37 -0
  349. package/src/scripts/orchestration_record.ts +0 -5
  350. package/src/scripts/prepack-check.mjs +53 -0
  351. package/src/scripts/release.ts +14 -98
  352. package/src/scripts/release_drill.ts +267 -1
  353. package/src/scripts/release_publication.ts +64 -1
  354. package/src/scripts/report_enforcement_drift.ts +566 -0
  355. package/src/scripts/report_host_injection_effect.ts +3 -11
  356. package/src/scripts/report_invocation_surface.ts +383 -0
  357. package/src/scripts/report_obligation_carriers.ts +699 -0
  358. package/src/scripts/report_obligation_writer_split.ts +236 -0
  359. package/src/scripts/report_skill_menu_census.ts +208 -0
  360. package/src/scripts/report_standing_payload_by_host.ts +26 -152
  361. package/src/scripts/roadmap_verify_share.ts +213 -0
  362. package/src/scripts/schemas/agent-settings.schema.json +23 -0
  363. package/src/scripts/schemas/command.schema.json +48 -0
  364. package/src/scripts/schemas/review-findings.schema.json +4 -0
  365. package/src/scripts/schemas/skill.schema.json +44 -0
  366. package/src/scripts/scope_growth.ts +219 -0
  367. package/src/scripts/self_review_gate.ts +359 -10
  368. package/src/scripts/semantic_parity_census.ts +314 -0
  369. package/src/scripts/session_eol_report.ts +54 -1
  370. package/src/scripts/sweep_dead_scan_roots.ts +1 -1
  371. package/src/scripts/trigger_eval_grandfather.json +0 -3
  372. package/src/scripts/trigger_eval_rotation.ts +122 -27
  373. package/src/scripts/ui_conformance_probe.ts +774 -0
  374. package/src/scripts/update_prices.ts +85 -3
  375. package/src/scripts/write_bundle_digest.mjs +74 -0
  376. package/src/server/schemas/settings.ts +62 -7
  377. package/src/shared/missionExecution.ts +126 -0
  378. package/src/templates/consumer-settings/ONBOARDING.md +1 -0
  379. package/dist/cli-delegate/chunk-523HCFYO.js +0 -90
  380. package/dist/ui/assets/index-D0jaCRPM.js +0 -29
  381. package/dist/ui/assets/index-jSfMW8_t.css +0 -1
  382. /package/dist/cli-delegate/{chunk-YUM5KURG.js → chunk-7RQP4UDH.js} +0 -0
@@ -0,0 +1,236 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * report_obligation_writer_split — which kind of tree wrote the obligation rows.
4
+ *
5
+ * WHAT QUESTION THIS ANSWERS, AND WHY IT NEEDED A TOOL.
6
+ * `obligation-settle-shadow-bar` is pre-registered against the shadow rows under
7
+ * `agents/runtime/state/obligations/`, and its own clause (7) says that corpus is
8
+ * one machine's runtime state. Until the rows carried a writer, that sentence was
9
+ * the ONLY place the fact lived: a reader holding the ledgers could count rows and
10
+ * could not separate them, so a reading that cleared the floor would have been
11
+ * quotable as evidence about a population nobody had measured. This prints the
12
+ * separation, from the rows themselves.
13
+ *
14
+ * IT REACHES NO VERDICT, AND THAT IS THE DESIGN. A split is an input to the corpus
15
+ * decision held in `road-to-an-obligation-row-that-names-its-writer`, not the
16
+ * decision — the three options there (accept the one-machine corpus and amend the
17
+ * bar, widen it deliberately, or file the window `resolved-null`) are owner-
18
+ * reserved, and all three name arming as owner-reserved too. So this exits 0 on
19
+ * every path, prints counts, and says nothing about whether anything may be armed.
20
+ * A reporter that graded the corpus would be handing the arming discussion a
21
+ * verdict wearing a number's clothes.
22
+ *
23
+ * `absent` IS REPORTED APART FROM `unknown`, and the distinction is load-bearing.
24
+ * `unknown` is a row whose writer WAS resolved and came back unresolvable; `absent`
25
+ * is a row written before the field existed. Folding them together would let the
26
+ * whole pre-field corpus read as "we looked and could not tell", which is a
27
+ * stronger claim than "we had not started looking". The ledger's own `parseRows`
28
+ * maps both to `unknown` — correct for a consumer that only needs a role — so this
29
+ * reads the JSON directly rather than through it, and uses `isWriterRole` for the
30
+ * vocabulary so the two cannot drift.
31
+ *
32
+ * PRIVACY, at the precision the code holds. Output is counts, plus the SCAN ROOT,
33
+ * which the caller supplied and which is the one thing a reader needs in order to
34
+ * know what was measured. Nothing else crosses: no session id, no ledger filename,
35
+ * no rule id, no timestamp. The scan reads those and keeps none of them.
36
+ *
37
+ * WRITES NOTHING. It opens files for reading and creates no directory, not even
38
+ * the ledger directory it looks for. A missing directory is a reported state, not
39
+ * a thing to fix.
40
+ *
41
+ * Exit: 0 always, except a usage error (1). Advisory — it gates nothing.
42
+ */
43
+
44
+ import * as fs from 'node:fs';
45
+ import * as path from 'node:path';
46
+ import process from 'node:process';
47
+ import { fileURLToPath } from 'node:url';
48
+
49
+ import { OBLIGATION_STATE_DIR, WRITER_ROLES, isWriterRole } from './_lib/obligations.js';
50
+
51
+ /** The reported classes: the closed vocabulary, plus the pre-field case. */
52
+ export const REPORTED_CLASSES = [...WRITER_ROLES, 'absent'] as const;
53
+ export type ReportedClass = (typeof REPORTED_CLASSES)[number];
54
+
55
+ /** The three row arrays a ledger carries, named as the file names them. */
56
+ export const ROW_ARRAYS = ['delivered', 'discharged', 'shadow'] as const;
57
+ export type RowArray = (typeof ROW_ARRAYS)[number];
58
+
59
+ export type Tally = Record<ReportedClass, number>;
60
+
61
+ export interface WriterSplit {
62
+ /** The directory scanned, echoed so a reader knows what was measured. */
63
+ readonly store: string;
64
+ /** `false` when the ledger directory does not exist at all. */
65
+ readonly storeExists: boolean;
66
+ /** Ledger files opened and parsed. */
67
+ readonly ledgers: number;
68
+ /** Files present but unreadable or unparseable — reported, never silently dropped. */
69
+ readonly unreadable: number;
70
+ /** Per row array, the split by writer class. */
71
+ readonly byArray: Record<RowArray, Tally>;
72
+ /** Every array folded together. */
73
+ readonly total: Tally;
74
+ }
75
+
76
+ function emptyTally(): Tally {
77
+ return { package: 0, consumer: 0, unknown: 0, absent: 0 };
78
+ }
79
+
80
+ /**
81
+ * Classify one row's writer field.
82
+ *
83
+ * A field that is missing entirely is `absent`. A field that is present but
84
+ * outside the closed vocabulary is `unknown`, matching `parseRows`: a corrupt
85
+ * provenance label degrades, it never deletes the record of a real row.
86
+ */
87
+ export function classifyRow(row: unknown): ReportedClass {
88
+ if (typeof row !== 'object' || row === null) return 'absent';
89
+ if (!('writer' in row)) return 'absent';
90
+ const writer = (row as { writer: unknown }).writer;
91
+ return isWriterRole(writer) ? writer : 'unknown';
92
+ }
93
+
94
+ /** Tally one ledger's arrays into an accumulator. */
95
+ function tallyLedger(doc: unknown, into: Record<RowArray, Tally>): void {
96
+ if (typeof doc !== 'object' || doc === null) return;
97
+ for (const name of ROW_ARRAYS) {
98
+ const rows = (doc as Record<string, unknown>)[name];
99
+ if (!Array.isArray(rows)) continue;
100
+ for (const row of rows) into[name][classifyRow(row)] += 1;
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Read every ledger under `root` and split its rows by writer class.
106
+ *
107
+ * Opens files for reading only. A file that will not parse increments
108
+ * `unreadable` rather than being skipped in silence — a corpus with unreadable
109
+ * members is a different corpus from a clean one, and a reader deciding whether
110
+ * a sample floor is met needs to know which they have.
111
+ */
112
+ export function readWriterSplit(root: string): WriterSplit {
113
+ const store = path.join(root, OBLIGATION_STATE_DIR);
114
+ const byArray: Record<RowArray, Tally> = {
115
+ delivered: emptyTally(),
116
+ discharged: emptyTally(),
117
+ shadow: emptyTally(),
118
+ };
119
+
120
+ let entries: string[];
121
+ try {
122
+ entries = fs.readdirSync(store).filter((f) => f.endsWith('.json'));
123
+ } catch {
124
+ return { store, storeExists: false, ledgers: 0, unreadable: 0, byArray, total: emptyTally() };
125
+ }
126
+
127
+ let ledgers = 0;
128
+ let unreadable = 0;
129
+ for (const entry of entries) {
130
+ let doc: unknown;
131
+ try {
132
+ doc = JSON.parse(fs.readFileSync(path.join(store, entry), 'utf-8'));
133
+ } catch {
134
+ unreadable += 1;
135
+ continue;
136
+ }
137
+ ledgers += 1;
138
+ tallyLedger(doc, byArray);
139
+ }
140
+
141
+ const total = emptyTally();
142
+ for (const name of ROW_ARRAYS) {
143
+ for (const cls of REPORTED_CLASSES) total[cls] += byArray[name][cls];
144
+ }
145
+ return { store, storeExists: true, ledgers, unreadable, byArray, total };
146
+ }
147
+
148
+ const sum = (t: Tally): number => REPORTED_CLASSES.reduce((n, c) => n + t[c], 0);
149
+
150
+ /**
151
+ * Render the split as text.
152
+ *
153
+ * The line a reader is most likely to quote is the shadow row, so it is named
154
+ * rather than left to be inferred from a table: the pre-registered bar counts
155
+ * shadow rows and nothing else, and a split over `delivered` answers a question
156
+ * the bar did not ask.
157
+ */
158
+ export function render(split: WriterSplit): string {
159
+ const out: string[] = [];
160
+ out.push(`scanned: ${split.store}`);
161
+ if (!split.storeExists) {
162
+ out.push(' the ledger directory does not exist — nothing was measured.');
163
+ out.push(' This is not a reading of zero. It is the absence of a corpus.');
164
+ return `${out.join('\n')}\n`;
165
+ }
166
+ out.push(` ledgers: ${split.ledgers}${split.unreadable > 0 ? `, unreadable: ${split.unreadable}` : ''}`);
167
+ if (split.ledgers === 0) {
168
+ out.push(' no ledger files — the directory exists and holds nothing.');
169
+ return `${out.join('\n')}\n`;
170
+ }
171
+
172
+ const width = Math.max(...ROW_ARRAYS.map((n) => n.length));
173
+ out.push('');
174
+ out.push(`${'rows'.padEnd(width)} ${REPORTED_CLASSES.map((c) => c.padStart(9)).join('')} total`);
175
+ for (const name of ROW_ARRAYS) {
176
+ const t = split.byArray[name];
177
+ const cells = REPORTED_CLASSES.map((c) => String(t[c]).padStart(9)).join('');
178
+ out.push(`${name.padEnd(width)} ${cells} ${String(sum(t)).padStart(7)}`);
179
+ }
180
+ const cells = REPORTED_CLASSES.map((c) => String(split.total[c]).padStart(9)).join('');
181
+ out.push(`${'all'.padEnd(width)} ${cells} ${String(sum(split.total)).padStart(7)}`);
182
+
183
+ out.push('');
184
+ out.push(
185
+ `shadow rows — the only array the pre-registered bar counts: ${sum(split.byArray.shadow)}`,
186
+ );
187
+ out.push(
188
+ '`absent` is a row written before the writer field existed; `unknown` is a row whose',
189
+ );
190
+ out.push('writer was resolved and came back unresolvable. They are not the same reading.');
191
+ out.push('');
192
+ out.push('This is a split, not a verdict. Whether the corpus is fit to read the bar off,');
193
+ out.push('and what follows for the detector, are owner decisions this tool does not make.');
194
+ return `${out.join('\n')}\n`;
195
+ }
196
+
197
+ /**
198
+ * Read one flag's value, refusing a flag whose value is missing.
199
+ *
200
+ * A flag present as the last argv element must be a usage error and never a
201
+ * silent fall back to the default. The same slip in a sibling reporter measured
202
+ * the DEFAULT corpus while printing what read as a settled number — a confident
203
+ * figure from a tree nobody asked for is worse than no figure at all.
204
+ */
205
+ function argValue(argv: string[], flag: string, fallback: string): string {
206
+ const i = argv.indexOf(flag);
207
+ if (i === -1) return fallback;
208
+ const value = argv[i + 1];
209
+ if (value === undefined || value.startsWith('--')) {
210
+ process.stderr.write(`report_obligation_writer_split: ${flag} needs a value\n`);
211
+ process.exit(1);
212
+ }
213
+ return value;
214
+ }
215
+
216
+ export function main(argv: string[]): number {
217
+ if (argv.includes('--help') || argv.includes('-h')) {
218
+ process.stdout.write(
219
+ 'usage: report_obligation_writer_split [--root <dir>] [--json]\n\n' +
220
+ ' --root <dir> tree whose agents/runtime/state/obligations/ to read (default: cwd)\n' +
221
+ ' --json emit the split as JSON instead of text\n\n' +
222
+ 'Read-only. Writes nothing, gates nothing, exits 0.\n',
223
+ );
224
+ return 0;
225
+ }
226
+ const root = argValue(argv, '--root', process.cwd());
227
+ const split = readWriterSplit(root);
228
+ process.stdout.write(argv.includes('--json') ? `${JSON.stringify(split, null, 2)}\n` : render(split));
229
+ return 0;
230
+ }
231
+
232
+ const _HERE = fileURLToPath(import.meta.url);
233
+
234
+ if (process.argv[1] && path.resolve(process.argv[1]) === path.resolve(_HERE)) {
235
+ process.exit(main(process.argv.slice(2)));
236
+ }
@@ -53,8 +53,21 @@
53
53
  * `command-only` and `flow-only` name a CANDIDATE for `user-invocable: false`,
54
54
  * never a decision — see the sole-entry limit above.
55
55
  *
56
+ * THE PER-PROFILE MENU, added by `road-to-a-menu-whose-precision-is-measured`
57
+ * 4.1. `--profile minimal|balanced|full|all` prints the menu byte count each
58
+ * shipped install profile delivers. Each profile's preset is read from its own
59
+ * file, and the answer is that all three are equal: no shipped preset declares
60
+ * a key that selects skills, so each one ships the whole menu.
61
+ *
62
+ * The equality note is WITHHELD when any profile carries a key listed in
63
+ * {@link SKILL_SELECTING_INI_KEYS}, and the key is named instead. That is the
64
+ * whole falsifiability claim, stated no wider than it holds: the byte count
65
+ * would not move — nothing here knows what such a key means — but the report
66
+ * stops asserting a conclusion whose premise has changed.
67
+ *
56
68
  * Usage:
57
69
  * ./scripts-run src/scripts/report_skill_menu_census # stdout summary
70
+ * ./scripts-run src/scripts/report_skill_menu_census --profile all
58
71
  * ./scripts-run src/scripts/report_skill_menu_census --emit # write the artifact
59
72
  * ./scripts-run src/scripts/report_skill_menu_census --emit --pin <sha>
60
73
  *
@@ -313,8 +326,203 @@ function headSha(root: string): string {
313
326
  return execFileSync('git', ['-C', root, 'rev-parse', 'HEAD'], { encoding: 'utf-8' }).trim();
314
327
  }
315
328
 
329
+ // ── per-profile menu bytes (road-to-a-menu-whose-precision-is-measured 4.1) ──
330
+
331
+ /** The shipped install presets, mirroring `install.ts` SUPPORTED_PROFILES. */
332
+ export const PROFILES = ['minimal', 'balanced', 'full'] as const;
333
+ export type Profile = (typeof PROFILES)[number];
334
+
335
+ /**
336
+ * Preset keys that would narrow the skill menu.
337
+ *
338
+ * EMPTY, and the emptiness is the finding rather than a stub. A preset selects
339
+ * `rule_loading_tier`, `discipline_profile` and `chat_history_frequency`; which
340
+ * SKILLS are written is decided by `packs:` frontmatter against the active pack
341
+ * set, which no preset names.
342
+ *
343
+ * It is a TRIPWIRE, not a selector, and the distinction is worth stating
344
+ * plainly because the first version of this comment overclaimed it. Nothing
345
+ * here knows what such a key would MEAN, so nothing here can apply one: the
346
+ * byte count would not move. What the list buys is that a preset carrying a
347
+ * listed key is NAMED in the output and the equality note is withheld — so the
348
+ * report stops asserting a conclusion whose premise has changed, instead of
349
+ * printing the same prose over a different tree.
350
+ */
351
+ export const SKILL_SELECTING_INI_KEYS: readonly string[] = [];
352
+
353
+ /** Flat `key=value` read of a profile preset; `;` comments and blanks dropped. */
354
+ export function readProfileIni(root: string, profile: Profile): Record<string, string> {
355
+ const p = path.join(root, 'src', 'config', 'profiles', `${profile}.ini`);
356
+ const out: Record<string, string> = {};
357
+ for (const raw of fs.readFileSync(p, 'utf-8').split('\n')) {
358
+ const line = raw.trim();
359
+ if (line === '' || line.startsWith(';') || line.startsWith('#') || line.startsWith('[')) continue;
360
+ const eq = line.indexOf('=');
361
+ if (eq === -1) continue;
362
+ out[line.slice(0, eq).trim()] = line.slice(eq + 1).trim();
363
+ }
364
+ return out;
365
+ }
366
+
367
+ export interface ProfileMenu {
368
+ readonly profile: Profile;
369
+ /** Skills this profile writes onto the model menu. */
370
+ readonly menuSkills: number;
371
+ /** Bytes of `- <name>: <description>\n`, the shape `censusSkillsCatalog` sums. */
372
+ readonly menuBytes: number;
373
+ /** chars/4, the estimator the payload budget uses. */
374
+ readonly tokensEstimate: number;
375
+ /** `declared_skill_catalogue_tokens` from the preset, or null when absent. */
376
+ readonly declaredCatalogueTokens: number | null;
377
+ /** Preset keys found that would narrow the menu. Empty today, by measurement. */
378
+ readonly selectingKeys: readonly string[];
379
+ }
380
+
381
+ /**
382
+ * One profile's menu, computed from its preset plus the skill tree.
383
+ *
384
+ * The catalogue LINE is the same shape `preamble_byte_census.censusSkillsCatalog`
385
+ * sums — `- <name>: <description>\n`. The POPULATION deliberately is not, and the
386
+ * difference is stated rather than smoothed over: that census sums every
387
+ * `SKILL.md`, because the payload bucket it feeds is about what the host lists;
388
+ * this one drops a skill carrying `user-invocable: false` or
389
+ * `disable-model-invocation: true`, because a menu is what the MODEL may pick
390
+ * from. Two skills carry such a flag today, so over the same tree and the same
391
+ * line shape this report reads 311 B below the full-population sum. Expect the
392
+ * two to differ; a reader who finds them equal has found a defect in one.
393
+ */
394
+ export function menuForProfile(root: string, profile: Profile): ProfileMenu {
395
+ const ini = readProfileIni(root, profile);
396
+ const selectingKeys = SKILL_SELECTING_INI_KEYS.filter((k) => k in ini);
397
+ const skillsDir = path.join(root, 'src', 'skills');
398
+ let menuSkills = 0;
399
+ let menuBytes = 0;
400
+ for (const entry of fs.readdirSync(skillsDir, { withFileTypes: true })) {
401
+ if (!entry.isDirectory()) continue;
402
+ const skillMd = path.join(skillsDir, entry.name, 'SKILL.md');
403
+ if (!fs.existsSync(skillMd)) continue;
404
+ const text = fs.readFileSync(skillMd, 'utf-8');
405
+ const flags = menuFlags(text);
406
+ if (flags.userInvocable === false || flags.disableModel === true) continue;
407
+ const name = /^name:\s*(\S.*?)\s*$/mu.exec(text.slice(0, 4000))?.[1] ?? entry.name;
408
+ const description = frontmatterDescription(text);
409
+ menuSkills += 1;
410
+ menuBytes += `- ${name}: ${description}\n`.length;
411
+ }
412
+ const declared = ini['declared_skill_catalogue_tokens'];
413
+ return {
414
+ profile,
415
+ menuSkills,
416
+ menuBytes,
417
+ tokensEstimate: Math.round(menuBytes / 4),
418
+ declaredCatalogueTokens: declared === undefined ? null : Number(declared),
419
+ selectingKeys,
420
+ };
421
+ }
422
+
423
+ /**
424
+ * The `description:` value, folded lines included.
425
+ *
426
+ * A `[^\n]+` capture was measured 17.1 % low elsewhere in this tree because it
427
+ * truncates every folded multi-line description; this reads to the next
428
+ * top-level key instead.
429
+ *
430
+ * A double-quoted value has its escapes DECODED. Leaving them encoded counted
431
+ * `\"` as two characters and over-reported the menu by 56 B across 13 skills —
432
+ * small, but the same class of defect as the 17.1 % one above, and a function
433
+ * whose docstring cites that fix should not carry a second instance of it.
434
+ */
435
+ export function frontmatterDescription(text: string): string {
436
+ if (!text.startsWith('---')) return '';
437
+ const end = text.indexOf('\n---', 3);
438
+ if (end === -1) return '';
439
+ const lines = text.slice(3, end).split('\n');
440
+ const at = lines.findIndex((l) => /^description:/u.test(l));
441
+ if (at === -1) return '';
442
+ const parts = [(lines[at] as string).replace(/^description:\s*/u, '')];
443
+ for (let i = at + 1; i < lines.length; i += 1) {
444
+ const l = lines[i] as string;
445
+ if (/^\S/u.test(l)) break;
446
+ parts.push(l.trim());
447
+ }
448
+ const joined = parts.join(' ').trim();
449
+ if (joined.length >= 2 && joined.startsWith('"') && joined.endsWith('"')) {
450
+ return joined.slice(1, -1).replace(/\\(["\\])/gu, '$1');
451
+ }
452
+ if (joined.length >= 2 && joined.startsWith("'") && joined.endsWith("'")) {
453
+ return joined.slice(1, -1).replace(/''/gu, "'");
454
+ }
455
+ return joined;
456
+ }
457
+
458
+ /** Render the per-profile menu table. Returns the lines, so tests read them. */
459
+ export function renderProfileMenus(menus: readonly ProfileMenu[]): string[] {
460
+ const L: string[] = [];
461
+ L.push('menu bytes per install profile (name + description, the catalogue shape):');
462
+ L.push(' profile skills menu_bytes ~tokens declared');
463
+ for (const m of menus) {
464
+ L.push(
465
+ ` ${m.profile.padEnd(10)} ${String(m.menuSkills).padStart(6)} ` +
466
+ `${String(m.menuBytes).padStart(12)} ${String(m.tokensEstimate).padStart(9)} ` +
467
+ `${(m.declaredCatalogueTokens === null ? '—' : String(m.declaredCatalogueTokens)).padStart(10)}`,
468
+ );
469
+ }
470
+ const flagged = menus.filter((m) => m.selectingKeys.length > 0);
471
+ if (flagged.length > 0) {
472
+ L.push('');
473
+ L.push(' A preset declares a key that could select skills, so the equality below is NOT');
474
+ L.push(' asserted — the premise it rested on has changed. Keys found:');
475
+ for (const m of flagged) L.push(` ${m.profile}: ${m.selectingKeys.join(', ')}`);
476
+ return L;
477
+ }
478
+ const distinct = new Set(menus.map((m) => m.menuBytes));
479
+ if (menus.length > 1 && distinct.size === 1) {
480
+ L.push('');
481
+ L.push(
482
+ ' All profiles report the SAME menu_bytes, and that is a measurement, not a',
483
+ );
484
+ L.push(
485
+ ' placeholder: no preset declares a key that selects skills, so each ships the',
486
+ );
487
+ L.push(
488
+ ' whole menu. The preset chooses which RULES activate; which SKILLS are written',
489
+ );
490
+ L.push(
491
+ ' is decided by `packs:` frontmatter against the active pack set. A menu-bytes',
492
+ );
493
+ L.push(
494
+ ' lever therefore has to move packs or the menu flags — changing profile does',
495
+ );
496
+ L.push(' nothing. The three presets already record this in their own comments.');
497
+ }
498
+ return L;
499
+ }
500
+
316
501
  export function main(argv: readonly string[]): number {
317
502
  const root = REPO_ROOT;
503
+ const profIdx = argv.indexOf('--profile');
504
+ if (profIdx !== -1) {
505
+ const want = argv[profIdx + 1] ?? 'all';
506
+ const chosen: Profile[] =
507
+ want === 'all' ? [...PROFILES] : PROFILES.includes(want as Profile) ? [want as Profile] : [];
508
+ if (chosen.length === 0) {
509
+ process.stderr.write(
510
+ `report_skill_menu_census: --profile expects ${PROFILES.join(' | ')} | all, got ${want}\n`,
511
+ );
512
+ return 2;
513
+ }
514
+ if (argv.includes('--emit')) {
515
+ // Refused rather than ignored: `--emit` writes the census artifact,
516
+ // `--profile` prints a table and writes nothing, and silently doing
517
+ // the second when the caller asked for both is how an artifact goes
518
+ // un-refreshed without anyone noticing.
519
+ process.stderr.write('report_skill_menu_census: --profile and --emit are separate runs\n');
520
+ return 2;
521
+ }
522
+ const menus = chosen.map((p) => menuForProfile(root, p));
523
+ process.stdout.write(`${renderProfileMenus(menus).join('\n')}\n`);
524
+ return 0;
525
+ }
318
526
  const emit = argv.includes('--emit');
319
527
  const pinIdx = argv.indexOf('--pin');
320
528
  const pin = pinIdx !== -1 && pinIdx + 1 < argv.length ? (argv[pinIdx + 1] as string) : headSha(root);
@@ -49,13 +49,20 @@ import * as path from 'node:path';
49
49
  import { fileURLToPath, pathToFileURL } from 'node:url';
50
50
 
51
51
  import {
52
- loadRouter,
53
- matchTierRules,
54
- pathOnlyRuleIds,
55
- selectForInjection,
56
- } from './_lib/rule_injection.js';
52
+ corpusShapeProblems,
53
+ slotFireSizes as sampleSlotFireSizes,
54
+ type SlotFireSizes,
55
+ } from './_lib/activation_payload.js';
56
+ import { loadRouter, pathOnlyRuleIds } from './_lib/rule_injection.js';
57
57
  import { CAP_BYTES } from './hooks/rule_inject_hook.js';
58
58
 
59
+ // Re-exported rather than redefined: this module was their only home until the
60
+ // payload gate needed the same sample, and two copies is how two numbers start
61
+ // disagreeing. `corpusShapeProblems` and `SlotFireSizes` are imported above
62
+ // because this module's own code uses them; these two it only passes through.
63
+ export { percentile, readCorpusPositives } from './_lib/activation_payload.js';
64
+ export { corpusShapeProblems, type SlotFireSizes };
65
+
59
66
  const _HERE = fileURLToPath(import.meta.url);
60
67
  export const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
61
68
  export const ARTIFACT_REL = path.join(
@@ -149,8 +156,13 @@ export const HOST_SURFACES: readonly HostSurface[] = [
149
156
  },
150
157
  {
151
158
  host: 'codex', surface: '.codex/agent-config.md', perRuleTree: false,
152
- writer: 'src/scripts/install.ts:1541',
153
- anchor: '\'agent-config.md\'',
159
+ writer: 'src/scripts/install.ts:1431',
160
+ // Scoped to `.codex` rather than the bare filename: nine other lines in
161
+ // `install.ts` write an `agent-config.md` for some other host, so the
162
+ // unscoped anchor would have accepted a drift onto any of them — the
163
+ // bounds-test-wearing-a-provenance-test's-name failure this field exists
164
+ // to close, one level down.
165
+ anchor: '\'.codex\', \'agent-config.md\'',
154
166
  note: 'written by the installer, not by `condense`; absent in this checkout',
155
167
  },
156
168
  ];
@@ -431,155 +443,17 @@ export function renderArtifact(root: string, pin: string): string {
431
443
  return L.join('\n');
432
444
  }
433
445
 
434
- /** One positive prompt of the frozen corpus, with the open files it declares. */
435
- interface CorpusPrompt {
436
- prompt: string;
437
- openFiles: string[] | null;
438
- }
439
-
440
446
  /**
441
- * Every YAML shape inside `positives:` this hand parser cannot read (R2 finding 10).
447
+ * The activation-charge sample at the concern's own cap.
442
448
  *
443
- * `readCorpusPositives` accepts only `- prompt: "…"` with a double-quoted
444
- * same-line scalar and an inline `open_files: [...]`. Any other legal shape —
445
- * single quotes, an unquoted scalar, a folded block, a block sequence for
446
- * `open_files` — was SILENTLY SKIPPED, which drops fires out of the
447
- * distribution the 16,384-byte `user_prompt_submit` row is derived from. A
448
- * measurement that governs a budget must fail loudly on a shape it cannot read
449
- * rather than under-count, so the CLI refuses on a non-empty result here.
450
- *
451
- * Reported as a shape problem rather than fixed by widening the parser: this
452
- * script must not grow a second YAML implementation, and the corpus is frozen,
453
- * so the honest answer to an unreadable shape is to say which file carries it.
454
- */
455
- export function corpusShapeProblems(root: string): string[] {
456
- const dir = path.join(root, 'tests', 'eval', 'routing-matrix');
457
- const problems: string[] = [];
458
- if (!fs.existsSync(dir)) return problems;
459
- for (const f of fs.readdirSync(dir).filter((n) => n.endsWith('.yaml')).sort()) {
460
- let inPositives = false;
461
- let lineNo = 0;
462
- for (const raw of fs.readFileSync(path.join(dir, f), 'utf-8').split('\n')) {
463
- lineNo += 1;
464
- if (/^positives:/u.test(raw)) { inPositives = true; continue; }
465
- if (/^near_misses:/u.test(raw)) { inPositives = false; continue; }
466
- if (!inPositives) continue;
467
- if (/^\s*-\s*prompt:/u.test(raw) && !/^\s*-\s*prompt:\s*"(.*)"\s*$/u.test(raw)) {
468
- problems.push(
469
- `${f}:${String(lineNo)}: a \`- prompt:\` this parser cannot read — only a ` +
470
- `double-quoted same-line scalar is accepted: ${JSON.stringify(raw.trim())}`,
471
- );
472
- continue;
473
- }
474
- if (/^\s*-\s*/u.test(raw) && !/^\s*-\s*(prompt|command|open_files):/u.test(raw)
475
- && raw.trim() !== '-' && raw.trim() !== '') {
476
- // A bare sequence item under `positives:` is a shape with no
477
- // `prompt:` key on its first line — folded or nested.
478
- if (!/^\s{4,}/u.test(raw)) {
479
- problems.push(
480
- `${f}:${String(lineNo)}: a positives entry whose first line carries no ` +
481
- `\`prompt:\` key: ${JSON.stringify(raw.trim())}`,
482
- );
483
- }
484
- }
485
- if (/^\s*open_files:\s*$/u.test(raw)) {
486
- problems.push(
487
- `${f}:${String(lineNo)}: a BLOCK-sequence \`open_files:\` — only the inline ` +
488
- '`[...]` form is read, so this entry\'s files would be dropped',
489
- );
490
- }
491
- }
492
- }
493
- return problems;
494
- }
495
-
496
- /** Every positive prompt in the frozen routing-matrix corpus. Near-misses are not fires. */
497
- export function readCorpusPositives(root: string): CorpusPrompt[] {
498
- const dir = path.join(root, 'tests', 'eval', 'routing-matrix');
499
- const out: CorpusPrompt[] = [];
500
- if (!fs.existsSync(dir)) return out;
501
- for (const f of fs.readdirSync(dir).filter((n) => n.endsWith('.yaml')).sort()) {
502
- let inPositives = false;
503
- let cur: CorpusPrompt | null = null;
504
- for (const raw of fs.readFileSync(path.join(dir, f), 'utf-8').split('\n')) {
505
- if (/^positives:/u.test(raw)) { inPositives = true; continue; }
506
- if (/^near_misses:/u.test(raw)) { inPositives = false; continue; }
507
- if (!inPositives) continue;
508
- const p = /^\s*-\s*prompt:\s*"(.*)"\s*$/u.exec(raw);
509
- if (p !== null) {
510
- cur = { prompt: p[1] as string, openFiles: null };
511
- out.push(cur);
512
- continue;
513
- }
514
- const of = /^\s*open_files:\s*\[(.*)\]\s*$/u.exec(raw);
515
- if (of !== null && cur !== null) {
516
- cur.openFiles = (of[1] as string)
517
- .split(',')
518
- .map((x) => x.trim().replace(/^["']|["']$/gu, ''))
519
- .filter((x) => x !== '');
520
- }
521
- }
522
- }
523
- return out;
524
- }
525
-
526
- /** Nearest-rank percentile over an unsorted sample. Empty sample → 0. */
527
- export function percentile(xs: readonly number[], q: number): number {
528
- if (xs.length === 0) return 0;
529
- const s = [...xs].sort((a, b) => a - b);
530
- return s[Math.min(s.length - 1, Math.floor(q * (s.length - 1)))] as number;
531
- }
532
-
533
- export interface SlotFireSizes {
534
- readonly slot: string;
535
- readonly fires: number;
536
- readonly p50: number;
537
- readonly p90: number;
538
- readonly max: number;
539
- }
540
-
541
- /**
542
- * Gate-open per-fire emission sizes for the delivery concern, per slot
543
- * (road-to-delivery-for-every-host 3.1).
544
- *
545
- * Measured through the SAME selection the runtime concern uses —
546
- * `matchTierRules` + `selectForInjection` at the concern's own `CAP_BYTES` —
547
- * rather than through a second model of it. An offline figure computed by a
548
- * different matcher would price a set the concern does not deliver.
549
- *
550
- * `pre_compact` is 0 by construction rather than by measurement: that branch of
551
- * `rule_inject_hook` clears the seen-set and returns allow without writing to
552
- * stdout, so there is no emission to sample.
449
+ * The sampling itself moved to `_lib/activation_payload.ts` when the payload
450
+ * gate needed the same figure (`road-to-the-delivery-flip-that-tells-the-truth`
451
+ * 3.1). This wrapper supplies `CAP_BYTES` so the report's own callers keep the
452
+ * one-argument form, and the helpers below are re-exported so there is still
453
+ * exactly one implementation of each.
553
454
  */
554
455
  export function slotFireSizes(root: string): SlotFireSizes[] {
555
- const router = loadRouter(root);
556
- const prompts = readCorpusPositives(root);
557
- const sample = (mode: 'prompt' | 'file'): number[] => {
558
- const out: number[] = [];
559
- for (const r of prompts) {
560
- if (mode === 'file' && (r.openFiles === null || r.openFiles.length === 0)) continue;
561
- const matches =
562
- mode === 'prompt'
563
- ? matchTierRules(router, r.prompt, null, null)
564
- : matchTierRules(router, '', r.openFiles, null);
565
- if (matches.length === 0) continue;
566
- const sel = selectForInjection(root, matches, CAP_BYTES);
567
- if (sel.selected.length > 0) out.push(sel.bytes);
568
- }
569
- return out;
570
- };
571
- const row = (slot: string, xs: number[]): SlotFireSizes => ({
572
- slot,
573
- fires: xs.length,
574
- p50: percentile(xs, 0.5),
575
- p90: percentile(xs, 0.9),
576
- max: xs.length === 0 ? 0 : Math.max(...xs),
577
- });
578
- return [
579
- row('user_prompt_submit', sample('prompt')),
580
- row('pre_tool_use', sample('file')),
581
- row('pre_compact', []),
582
- ];
456
+ return sampleSlotFireSizes(root, CAP_BYTES);
583
457
  }
584
458
 
585
459
  function headSha(root: string): string {