@event4u/agent-config 9.27.0 → 9.29.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 (220) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/AGENTS.md +13 -12
  3. package/CHANGELOG.md +165 -0
  4. package/README.md +1 -1
  5. package/agents/templates/.ai-council.yml.example +55 -24
  6. package/dist/agent-src/commands/analyze/conformance.md +24 -0
  7. package/dist/agent-src/commands/cost/report.md +2 -2
  8. package/dist/agent-src/commands/team/adversarial.md +8 -7
  9. package/dist/agent-src/commands/team/delegate.md +13 -12
  10. package/dist/agent-src/commands/team/review.md +9 -8
  11. package/dist/agent-src/commands/team/status.md +9 -8
  12. package/dist/agent-src/commands/team.md +29 -19
  13. package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +88 -2
  14. package/dist/agent-src/contexts/execution/auto-orchestration-activation.md +29 -21
  15. package/dist/agent-src/contexts/execution/host-capability-manifest.md +26 -15
  16. package/dist/agent-src/contexts/execution/orchestration-telemetry.md +28 -3
  17. package/dist/agent-src/contexts/execution/subagent-response-contract.md +54 -0
  18. package/dist/agent-src/contexts/execution/subagent-routing.md +13 -11
  19. package/dist/agent-src/contexts/execution/subagent-spawn-contract.md +25 -0
  20. package/dist/agent-src/contexts/execution/subagent-steering.md +6 -3
  21. package/dist/agent-src/rules/artifact-engagement-recording.md +4 -1
  22. package/dist/agent-src/rules/autonomous-execution.md +1 -1
  23. package/dist/agent-src/rules/brand-source-of-truth.md +33 -6
  24. package/dist/agent-src/rules/context-hygiene.md +3 -1
  25. package/dist/agent-src/rules/council-availability.md +93 -0
  26. package/dist/agent-src/rules/cross-source-consistency.md +3 -0
  27. package/dist/agent-src/rules/delegation-policy.md +38 -10
  28. package/dist/agent-src/rules/design-fidelity.md +3 -0
  29. package/dist/agent-src/rules/evaluator-independence.md +2 -1
  30. package/dist/agent-src/rules/external-code-graph-interop.md +2 -1
  31. package/dist/agent-src/rules/finance-safety-floor.md +1 -1
  32. package/dist/agent-src/rules/git-history-discipline.md +1 -1
  33. package/dist/agent-src/rules/history-discipline.md +3 -1
  34. package/dist/agent-src/rules/legal-safety-floor.md +1 -1
  35. package/dist/agent-src/rules/no-pr-progress-comments.md +3 -1
  36. package/dist/agent-src/rules/onboarding-gate.md +3 -1
  37. package/dist/agent-src/rules/roadmap-ci-steps-policy.md +4 -1
  38. package/dist/agent-src/rules/role-mode-adherence.md +1 -1
  39. package/dist/agent-src/rules/scale-discipline.md +3 -1
  40. package/dist/agent-src/rules/self-repair-loop.md +103 -0
  41. package/dist/agent-src/rules/session-canary.md +2 -1
  42. package/dist/agent-src/rules/settings-ask-protocol.md +8 -7
  43. package/dist/agent-src/rules/strategy-safety-floor.md +1 -1
  44. package/dist/agent-src/rules/token-budget-discipline.md +4 -2
  45. package/dist/agent-src/rules/tool-safety.md +5 -1
  46. package/dist/agent-src/skills/adversarial-review/SKILL.md +1 -1
  47. package/dist/agent-src/skills/analysis-autonomous-mode/SKILL.md +1 -1
  48. package/dist/agent-src/skills/forensics-report/SKILL.md +114 -0
  49. package/dist/agent-src/skills/forensics-report/evals/triggers.json +17 -0
  50. package/dist/agent-src/skills/performance-analysis/SKILL.md +1 -1
  51. package/dist/agent-src/skills/persona-improvement/SKILL.md +1 -1
  52. package/dist/agent-src/skills/premortem/SKILL.md +30 -1
  53. package/dist/agent-src/skills/project-analyzer/SKILL.md +1 -1
  54. package/dist/agent-src/skills/reasoning-orchestrator/SKILL.md +2 -1
  55. package/dist/agent-src/skills/security-audit/SKILL.md +1 -1
  56. package/dist/agent-src/skills/sequential-thinking/SKILL.md +1 -1
  57. package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +1 -1
  58. package/dist/agent-src/skills/subagent-orchestration/SKILL.md +7 -5
  59. package/dist/agent-src/skills/subagent-orchestration/schemas/subagent-status.json +61 -3
  60. package/dist/agent-src/skills/test-case-discovery/SKILL.md +1 -1
  61. package/dist/agent-src/skills/universal-project-analysis/SKILL.md +1 -1
  62. package/dist/agent-src/templates/AGENTS.md +8 -12
  63. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  64. package/dist/agent-src/templates/roadmaps.md +23 -0
  65. package/dist/cli/registry.js +7 -0
  66. package/dist/cli/registry.js.map +1 -1
  67. package/dist/cli-delegate/chunk-66OVWGB5.js +270 -0
  68. package/dist/cli-delegate/{chunk-HJ6DAJHR.js → chunk-LCFJIQ4J.js} +89 -7
  69. package/dist/cli-delegate/{chunk-VSYT7OHO.js → chunk-LVRYBKTI.js} +31 -2
  70. package/dist/cli-delegate/{chunk-DVRS2KVR.js → chunk-MMXA5GY7.js} +10 -17
  71. package/dist/cli-delegate/{chunk-WR6GTRHJ.js → chunk-MP3JT3IU.js} +3 -3
  72. package/dist/cli-delegate/{chunk-3KJIMFTB.js → chunk-NZ5N2XQL.js} +467 -305
  73. package/dist/cli-delegate/{chunk-AIIUU73N.js → chunk-OJM3MBJM.js} +1 -1
  74. package/dist/cli-delegate/{chunk-BCPIOAGV.js → chunk-RCIA67XN.js} +117 -96
  75. package/dist/cli-delegate/chunk-REKCMYOU.js +2991 -0
  76. package/dist/cli-delegate/cmd_analyze_session.js +1 -1
  77. package/dist/cli-delegate/cmd_brand_status.js +134 -0
  78. package/dist/cli-delegate/cmd_conformance.js +10 -47
  79. package/dist/cli-delegate/cmd_converge.js +3 -3
  80. package/dist/cli-delegate/cmd_doctor.js +10 -6
  81. package/dist/cli-delegate/cmd_explain.js +80 -330
  82. package/dist/cli-delegate/cmd_export.js +6 -6
  83. package/dist/cli-delegate/cmd_fleet.js +3 -3
  84. package/dist/cli-delegate/cmd_handoff.js +3 -3
  85. package/dist/cli-delegate/cmd_mcp_available.js +433 -0
  86. package/dist/cli-delegate/cmd_migrate.js +3 -3
  87. package/dist/cli-delegate/cmd_packs_active.js +153 -0
  88. package/dist/cli-delegate/cmd_preflight.js +1 -1
  89. package/dist/cli-delegate/cmd_prune.js +6 -6
  90. package/dist/cli-delegate/cmd_route_audit.js +8 -8
  91. package/dist/cli-delegate/cmd_route_explain.js +4 -4
  92. package/dist/cli-delegate/cmd_settings_get.js +266 -0
  93. package/dist/cli-delegate/cmd_settings_migrate.js +6 -6
  94. package/dist/cli-delegate/cmd_settings_set.js +8 -2968
  95. package/dist/cli-delegate/cmd_sync.js +6 -6
  96. package/dist/cli-delegate/cmd_uninstall.js +6 -6
  97. package/dist/cli-delegate/cmd_update.js +4 -4
  98. package/dist/cli-delegate/cmd_upgrade.js +4 -4
  99. package/dist/cli-delegate/cmd_validate.js +6 -6
  100. package/dist/cli-delegate/cmd_versions.js +4 -4
  101. package/dist/discovery/deprecation-report.md +1 -1
  102. package/dist/discovery/discovery-manifest.json +164 -55
  103. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  104. package/dist/discovery/discovery-manifest.summary.md +4 -3
  105. package/dist/discovery/orphan-report.md +1 -1
  106. package/dist/discovery/packs.json +44 -5
  107. package/dist/discovery/trust-report.md +11 -11
  108. package/dist/discovery/workspaces.json +16 -6
  109. package/dist/hooks/dispatch.js +9341 -6131
  110. package/dist/install/install.mjs +739 -524
  111. package/dist/mcp/registry-manifest.json +3 -3
  112. package/dist/mcp/server.json +2 -2
  113. package/dist/mcp/server.mjs +777 -281
  114. package/dist/router.json +1 -1
  115. package/dist/scripts/_lib/agent_settings.js +134 -15
  116. package/dist/scripts/_lib/agent_settings.js.map +1 -1
  117. package/dist/scripts/condense.js +147 -2
  118. package/dist/scripts/condense.js.map +1 -1
  119. package/dist/server/schemas/settings.js +6 -7
  120. package/dist/server/schemas/settings.js.map +1 -1
  121. package/dist/shared/settingsCarveOut.js +0 -9
  122. package/dist/shared/settingsCarveOut.js.map +1 -1
  123. package/dist/shared/settingsClasses.js +11 -9
  124. package/dist/shared/settingsClasses.js.map +1 -1
  125. package/dist/ui/assets/{index-DmR0mbk4.js → index-JDAF00bC.js} +2 -2
  126. package/dist/ui/assets/{index-DmR0mbk4.js.map → index-JDAF00bC.js.map} +1 -1
  127. package/dist/ui/index.html +1 -1
  128. package/docs/contracts/settings-classes.md +68 -17
  129. package/docs/guidelines/agent-infra/memory-access.md +14 -3
  130. package/package.json +1 -1
  131. package/src/agent-src/templates/AGENTS.md +8 -12
  132. package/src/config/agent-settings.template.yml +52 -66
  133. package/src/config/budgets.yml +29 -0
  134. package/src/config/discovery/packs.yml +11 -0
  135. package/src/config/discovery/workspaces.yml +1 -1
  136. package/src/config/evaluator-budgets.json +4 -4
  137. package/src/config/gate-coverage.yml +26 -0
  138. package/src/config/gate-violation-baselines.json +3 -3
  139. package/src/scripts/_cli/cmd_brand_status.ts +174 -0
  140. package/src/scripts/_cli/cmd_conformance.ts +14 -42
  141. package/src/scripts/_cli/cmd_doctor.ts +65 -48
  142. package/src/scripts/_cli/cmd_mcp_available.ts +266 -0
  143. package/src/scripts/_cli/cmd_packs_active.ts +225 -0
  144. package/src/scripts/_cli/cmd_settings_get.ts +324 -0
  145. package/src/scripts/_dispatch.bash +57 -0
  146. package/src/scripts/_lib/agent_settings.ts +140 -16
  147. package/src/scripts/_lib/auto_dispatch.corpus.test.ts +1 -1
  148. package/src/scripts/_lib/auto_dispatch.ts +23 -18
  149. package/src/scripts/_lib/capsule_trigger.ts +124 -0
  150. package/src/scripts/_lib/carrier_divergence.ts +135 -0
  151. package/src/scripts/_lib/host_capability.ts +144 -4
  152. package/src/scripts/_lib/judgment_ladder.ts +382 -0
  153. package/src/scripts/_lib/orchestration_gate.ts +8 -0
  154. package/src/scripts/_lib/orchestration_record.ts +37 -0
  155. package/src/scripts/_lib/orchestration_savings.ts +1 -1
  156. package/src/scripts/_lib/prompt_shape.ts +88 -0
  157. package/src/scripts/_lib/review_skipped_record.ts +102 -0
  158. package/src/scripts/_lib/rule_layer_overlap.ts +254 -0
  159. package/src/scripts/_lib/self_repair.ts +489 -0
  160. package/src/scripts/_lib/self_repair_store.ts +128 -0
  161. package/src/scripts/_lib/subagent_capsule.ts +182 -0
  162. package/src/scripts/_lib/subagent_response.ts +19 -0
  163. package/src/scripts/_lib/subagent_spawn.ts +13 -0
  164. package/src/scripts/_lib/subagent_steering.ts +9 -4
  165. package/src/scripts/_lib/tier_budget_routing.ts +6 -1
  166. package/src/scripts/_lib/transcript_entry.ts +80 -0
  167. package/src/scripts/_lib/worker_budget.ts +37 -2
  168. package/src/scripts/ai_council/clients.ts +76 -54
  169. package/src/scripts/ai_council/config.ts +94 -6
  170. package/src/scripts/ai_council/handoff.ts +113 -0
  171. package/src/scripts/ai_council/modes.ts +19 -4
  172. package/src/scripts/ai_council/orchestrator.ts +105 -0
  173. package/src/scripts/ai_council/quorum.ts +84 -0
  174. package/src/scripts/ai_council/session.ts +44 -0
  175. package/src/scripts/ai_council/transport_resolver.ts +127 -1
  176. package/src/scripts/ai_team/availability.ts +110 -0
  177. package/src/scripts/ai_team/config.ts +34 -14
  178. package/src/scripts/ai_team/team_dispatch.ts +71 -29
  179. package/src/scripts/bench_critic_protocol.ts +396 -0
  180. package/src/scripts/check_branch_freshness.ts +255 -0
  181. package/src/scripts/check_council_config_location.ts +125 -1
  182. package/src/scripts/check_detector_corpus.ts +202 -0
  183. package/src/scripts/check_hook_bundle_freshness.ts +4 -1
  184. package/src/scripts/check_standing_rule_delivery.ts +314 -0
  185. package/src/scripts/condense.ts +162 -2
  186. package/src/scripts/config/profiles.ts +13 -3
  187. package/src/scripts/conformance_scan.ts +272 -24
  188. package/src/scripts/council_availability_hook.ts +188 -0
  189. package/src/scripts/council_cli.ts +527 -32
  190. package/src/scripts/forensics_report.ts +333 -0
  191. package/src/scripts/hook_manifest.yaml +107 -26
  192. package/src/scripts/hooks/block_no_verify.ts +39 -2
  193. package/src/scripts/hooks/block_unauthorized_git.ts +156 -7
  194. package/src/scripts/hooks/concern_registry.ts +10 -0
  195. package/src/scripts/hooks/delegation_nudge_hook.ts +484 -0
  196. package/src/scripts/hooks/end_review_nudge_hook.ts +726 -0
  197. package/src/scripts/hooks/orchestration_record_hook.ts +342 -0
  198. package/src/scripts/install.ts +151 -0
  199. package/src/scripts/language_mirror_hook.ts +30 -37
  200. package/src/scripts/lint_discovery_vocabulary.ts +2 -0
  201. package/src/scripts/lint_never_silent.ts +276 -0
  202. package/src/scripts/lint_no_activation_gates.ts +271 -0
  203. package/src/scripts/measure_scope_dedup.ts +19 -13
  204. package/src/scripts/onboarding_gate_hook.ts +40 -1
  205. package/src/scripts/orchestration_record.ts +12 -1
  206. package/src/scripts/preamble_byte_census.ts +9 -20
  207. package/src/scripts/rebuild_hook_bundle.ts +177 -0
  208. package/src/scripts/release.ts +74 -2
  209. package/src/scripts/report_carrier_divergence.ts +364 -0
  210. package/src/scripts/report_skill_activation.ts +257 -0
  211. package/src/scripts/report_skill_obligation_violations.ts +579 -0
  212. package/src/scripts/routing_doctor.ts +95 -39
  213. package/src/scripts/score_skill_selection.ts +19 -5
  214. package/src/scripts/self_repair_cli.ts +470 -0
  215. package/src/scripts/self_repair_hook.ts +241 -0
  216. package/src/scripts/team_events_spike.ts +271 -0
  217. package/src/scripts/team_review_gate_hook.ts +31 -9
  218. package/src/server/schemas/settings.ts +10 -17
  219. package/src/shared/settingsCarveOut.ts +0 -10
  220. package/src/shared/settingsClasses.ts +11 -9
@@ -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
+ }
@@ -0,0 +1,271 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * Team-event payload shape spike (Phase 5.1, road-to-always-on-orchestration).
4
+ *
5
+ * `TaskCreated` / `TaskCompleted` / `TeammateIdle` are documented Claude Code
6
+ * hook events (agent teams, behind `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS`),
7
+ * but this repo has never observed a real payload for any of them — the
8
+ * shapes are documented, not verified. This script is manually startable
9
+ * ONLY: it is never wired into `hook_manifest.yaml` / `hooks.json`. A
10
+ * maintainer points a hook command at it (or pipes a captured payload) to
11
+ * record a SHAPE — field names + JS/JSON types — and never a value.
12
+ * Privacy by construction: the recursive walk below inspects `typeof` /
13
+ * `Array.isArray`, nothing else, so there is no code path that could copy a
14
+ * value into the written report.
15
+ *
16
+ * SCOPE OF THE PRIVACY CLAIM (stated honestly, per independent review): the
17
+ * claim above holds for a payload whose OWN keys are static field names —
18
+ * true of the three documented envelopes this script targets
19
+ * (`TaskCreated` / `TaskCompleted` / `TeammateIdle`), where a key is always
20
+ * a schema name like `task_id` or `teammate_name`. It does NOT hold for a
21
+ * payload with dynamically-named keys — a map keyed by a file path, a
22
+ * person's name, or any other runtime value — because `extractShape` walks
23
+ * `Object.keys()` and records each key verbatim as a "field name". A
24
+ * dynamic key is not a field name; it is a value wearing a field name's
25
+ * clothes, and this script would write it into the tracked report exactly
26
+ * as it would refuse to write a value. Point this script only at the three
27
+ * documented shapes (or another envelope you have separately confirmed has
28
+ * static keys) — never at an undocumented payload that might carry a
29
+ * dynamically-keyed map.
30
+ *
31
+ * Usage (only meaningful with the experimental flag set):
32
+ *
33
+ * echo '<payload-json>' | \
34
+ * CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 npx tsx src/scripts/team_events_spike.ts [EventName]
35
+ *
36
+ * `EventName` is optional; if omitted, the payload's own `hook_event_name` /
37
+ * `event` / `eventName` / `event_name` / `type` field is read and matched
38
+ * against the three known `TEAM_EVENT_NAMES` — an unrecognised value falls
39
+ * back to `unlabeled-event` rather than copying the raw (payload-supplied)
40
+ * string into the report, which would itself be writing a value under the
41
+ * cover of a "name".
42
+ *
43
+ * Without the flag: clean one-line skip, exit 0, stdin is never touched.
44
+ *
45
+ * Output: appends one dated section per capture to
46
+ * `agents/evidence/analysis/team-event-shapes.md`.
47
+ */
48
+ import * as fs from 'node:fs';
49
+ import * as path from 'node:path';
50
+ import { fileURLToPath, pathToFileURL } from 'node:url';
51
+
52
+ const _HERE = fileURLToPath(import.meta.url);
53
+ const ROOT = path.resolve(path.dirname(_HERE), '..', '..');
54
+ const OUT_FILE = path.join(ROOT, 'agents', 'evidence', 'analysis', 'team-event-shapes.md');
55
+
56
+ export const FLAG = 'CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS';
57
+ export const TEAM_EVENT_NAMES = ['TaskCreated', 'TaskCompleted', 'TeammateIdle'] as const;
58
+
59
+ export interface ShapeEntry {
60
+ path: string;
61
+ type: string;
62
+ }
63
+
64
+ /** JS/JSON type of a value. Reads only `typeof` / `Array.isArray` — never the value. */
65
+ function _typeOf(value: unknown): string {
66
+ if (value === null) return 'null';
67
+ if (Array.isArray(value)) return 'array';
68
+ return typeof value; // 'string' | 'number' | 'boolean' | 'object' | 'undefined'
69
+ }
70
+
71
+ /**
72
+ * Walk a parsed JSON payload and record field-name + type only.
73
+ *
74
+ * This never returns, prints, or embeds a value — recursion is driven
75
+ * entirely by structural type and object keys.
76
+ */
77
+ export function extractShape(value: unknown, prefix = ''): ShapeEntry[] {
78
+ const entries: ShapeEntry[] = [];
79
+ const t = _typeOf(value);
80
+ if (prefix !== '') {
81
+ entries.push({ path: prefix, type: t });
82
+ }
83
+ if (t === 'array') {
84
+ // An event payload's arrays are homogeneous lists, not fixed tuples —
85
+ // merge every element's shape under one `[]` path (deduped below).
86
+ for (const item of value as unknown[]) {
87
+ entries.push(...extractShape(item, `${prefix}[]`));
88
+ }
89
+ return entries;
90
+ }
91
+ if (t === 'object') {
92
+ const obj = value as Record<string, unknown>;
93
+ for (const key of Object.keys(obj).sort()) {
94
+ entries.push(...extractShape(obj[key], prefix === '' ? key : `${prefix}.${key}`));
95
+ }
96
+ }
97
+ return entries;
98
+ }
99
+
100
+ /** De-duplicate by path+type — an array's elements repeat the same shape. */
101
+ function _dedupe(entries: ShapeEntry[]): ShapeEntry[] {
102
+ const seen = new Set<string>();
103
+ const out: ShapeEntry[] = [];
104
+ for (const e of entries) {
105
+ const key = `${e.path}\u0000${e.type}`;
106
+ if (seen.has(key)) continue;
107
+ seen.add(key);
108
+ out.push(e);
109
+ }
110
+ return out;
111
+ }
112
+
113
+ /**
114
+ * Best-effort event-name inference from the payload's own envelope.
115
+ *
116
+ * m4 fix (independent-review finding): the envelope field this reads
117
+ * (`hook_event_name` / `event` / `type` / …) carries a payload-SUPPLIED
118
+ * string — copying it verbatim into the recorded report is exactly the
119
+ * "copy a value into the report" this script otherwise refuses to do
120
+ * (see the module header's privacy claim). Matched against the known
121
+ * `TEAM_EVENT_NAMES` allowlist instead: a recognised name is safe to
122
+ * record because it is drawn from three publicly documented, stable
123
+ * literals, never from the untrusted payload string itself; anything
124
+ * else returns `null` and the caller falls back to the fixed literal
125
+ * `'unlabeled-event'`.
126
+ */
127
+ export function inferEventName(payload: unknown): string | null {
128
+ if (payload === null || typeof payload !== 'object' || Array.isArray(payload)) return null;
129
+ const obj = payload as Record<string, unknown>;
130
+ const known = new Set<string>(TEAM_EVENT_NAMES);
131
+ for (const key of ['hook_event_name', 'event', 'eventName', 'event_name', 'type']) {
132
+ const v = obj[key];
133
+ if (typeof v === 'string' && known.has(v)) return v;
134
+ }
135
+ return null;
136
+ }
137
+
138
+ /** Render one capture as a Markdown section — field names + types only. */
139
+ export function renderShapeReport(eventName: string, entries: ShapeEntry[], observedAt: string): string {
140
+ const deduped = _dedupe(entries);
141
+ const rows = deduped.map((e) => `| \`${e.path}\` | ${e.type} |`).join('\n');
142
+ const table = rows === '' ? '_(empty payload — no fields observed)_' : `| Field | Type |\n|---|---|\n${rows}`;
143
+ return [`## ${eventName} — observed ${observedAt}`, '', table, ''].join('\n');
144
+ }
145
+
146
+ function _printGuide(): void {
147
+ process.stdout.write(
148
+ [
149
+ 'team-event shape spike (manual, not hook-bound)',
150
+ '',
151
+ `${FLAG} is set — recording.`,
152
+ '',
153
+ `Observed events: ${TEAM_EVENT_NAMES.join(', ')}`,
154
+ '',
155
+ 'Pipe one captured JSON payload on stdin — values are never written,',
156
+ 'only field names + types:',
157
+ '',
158
+ ` echo '<payload-json>' | ${FLAG}=1 npx tsx src/scripts/team_events_spike.ts [EventName]`,
159
+ '',
160
+ `Shapes append to ${path.relative(ROOT, OUT_FILE)}.`,
161
+ '',
162
+ ].join('\n'),
163
+ );
164
+ }
165
+
166
+ async function _readStdin(): Promise<string> {
167
+ const chunks: Buffer[] = [];
168
+ // `for await` streams stdin — a synchronous `readFileSync(0)` EAGAINs
169
+ // past the pipe buffer on a large payload; this has no such ceiling.
170
+ for await (const chunk of process.stdin) {
171
+ chunks.push(chunk as Buffer);
172
+ }
173
+ return Buffer.concat(chunks).toString('utf-8');
174
+ }
175
+
176
+ export interface RunResult {
177
+ skipped: boolean;
178
+ reason?: string;
179
+ eventName?: string;
180
+ entryCount?: number;
181
+ outFile?: string;
182
+ }
183
+
184
+ export interface RunOptions {
185
+ argv?: string[];
186
+ env?: NodeJS.ProcessEnv;
187
+ readStdin?: () => Promise<string>;
188
+ writeReport?: (section: string) => void;
189
+ now?: () => string;
190
+ }
191
+
192
+ /**
193
+ * Orchestrates the spike. Every side effect is injectable so tests never
194
+ * touch the real environment, stdin, or the tracked report file.
195
+ */
196
+ export async function run(opts?: RunOptions): Promise<RunResult> {
197
+ const env = opts?.env ?? process.env;
198
+ const argv = opts?.argv ?? process.argv.slice(2);
199
+ const readStdin = opts?.readStdin ?? _readStdin;
200
+ const now = opts?.now ?? (() => new Date().toISOString());
201
+ const writeReport =
202
+ opts?.writeReport ??
203
+ ((section: string) => {
204
+ fs.mkdirSync(path.dirname(OUT_FILE), { recursive: true });
205
+ fs.appendFileSync(OUT_FILE, section, 'utf-8');
206
+ });
207
+
208
+ if (!env[FLAG]) {
209
+ const reason = `${FLAG} is not set — teams are experimental and unavailable; skipping.`;
210
+ process.stdout.write(`${reason}\n`);
211
+ return { skipped: true, reason };
212
+ }
213
+
214
+ _printGuide();
215
+ const raw = (await readStdin()).trim();
216
+ if (raw === '') {
217
+ const reason = 'no payload received on stdin — nothing recorded.';
218
+ process.stdout.write(`${reason}\n`);
219
+ return { skipped: true, reason };
220
+ }
221
+
222
+ let payload: unknown;
223
+ try {
224
+ payload = JSON.parse(raw);
225
+ } catch (e) {
226
+ const reason = `invalid JSON on stdin: ${(e as Error).message}`;
227
+ process.stderr.write(`team_events_spike: ${reason}\n`);
228
+ return { skipped: true, reason };
229
+ }
230
+
231
+ const eventName = argv[0] ?? inferEventName(payload) ?? 'unlabeled-event';
232
+ const entries = extractShape(payload);
233
+ const section = renderShapeReport(eventName, entries, now());
234
+ writeReport(`\n${section}`);
235
+ const count = _dedupe(entries).length;
236
+ process.stdout.write(`recorded ${count} field shape(s) for ${eventName} -> ${path.relative(ROOT, OUT_FILE)}\n`);
237
+ return { skipped: false, eventName, entryCount: count, outFile: OUT_FILE };
238
+ }
239
+
240
+ function _isCliEntry(): boolean {
241
+ if (process.argv[1] === undefined) {
242
+ return false;
243
+ }
244
+ const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
245
+ if (import.meta.url === argvUrl) {
246
+ return true;
247
+ }
248
+ // A symlinked invocation (e.g. via an installed `.augment/` projection)
249
+ // makes the raw URLs differ; compare realpaths so the entry guard still
250
+ // fires when run through a symlink.
251
+ try {
252
+ const here = fs.realpathSync(fileURLToPath(import.meta.url));
253
+ const argv = fs.realpathSync(path.resolve(process.argv[1]));
254
+ return here === argv;
255
+ } catch {
256
+ return false;
257
+ }
258
+ }
259
+
260
+ if (_isCliEntry() || process.argv[1] === _HERE) {
261
+ run()
262
+ .then(() => {
263
+ // Observational spike, never a gate — always exit 0 on a
264
+ // completed run (skip or record alike).
265
+ process.exit(0);
266
+ })
267
+ .catch((e: unknown) => {
268
+ process.stderr.write(`team_events_spike: ${(e as Error).message}\n`);
269
+ process.exit(1);
270
+ });
271
+ }
@@ -3,14 +3,36 @@
3
3
  * Team Review-Gate governance — `stop` concern (road-to-team-mode Phase 4).
4
4
  *
5
5
  * Minimal call-site for `src/scripts/ai_team/review_gate.ts`. On Stop it
6
- * reads the dispatcher envelope, and — ONLY when `ai_team.enabled` AND
7
- * `ai_team.review_gate.managed` are true — locates the newest stop-gate
8
- * review job the codex plugin persisted for this session, parses its
9
- * first-line ALLOW/BLOCK contract, applies the consecutive-BLOCK counter,
10
- * and prints the circuit-breaker notice (exactly once per trip) to stdout
11
- * for the dispatch layer to render. With `managed: false` (default) the
12
- * hook exits 0 without touching anything — byte-identical pre-Phase-4
13
- * Stop behavior.
6
+ * reads the dispatcher envelope, and — ONLY when `ai_team.review_gate.managed`
7
+ * is true — locates the newest stop-gate review job the codex plugin
8
+ * persisted for this session, parses its first-line ALLOW/BLOCK contract,
9
+ * applies the consecutive-BLOCK counter, and prints the circuit-breaker
10
+ * notice (exactly once per trip) to stdout for the dispatch layer to
11
+ * render. With `managed: false` (default) the hook exits 0 without
12
+ * touching anything — byte-identical pre-Phase-4 Stop behavior.
13
+ *
14
+ * `ai_team.enabled` was DELETED (road-to-always-on-orchestration Phase 1,
15
+ * Step 1.3) — `managed` was previously double-gated behind it, but the two
16
+ * keys govern independent things: `managed` governs the codex PLUGIN's own
17
+ * Stop-hook loop (upstream's mechanism, which does not depend on our codex
18
+ * CLI availability), while `/team`'s own availability is a separate,
19
+ * CLI-resolved fact (`src/scripts/ai_team/availability.ts`) that the
20
+ * conversational `/team` wrappers check, not this passive Stop hook. So the
21
+ * two keys are decoupled: `managed` stands alone.
22
+ *
23
+ * DELIBERATELY NOT GATED ON `emergency.orchestration_halt` (independent
24
+ * review, road-to-always-on-orchestration): unlike `/team`'s own
25
+ * conversational surfaces (`assert_delegate_allowed` / `run_team_review` in
26
+ * `ai_team/team_dispatch.ts`, both of which check `isOrchestrationHalted`
27
+ * before doing anything), this hook never reads that switch. This is
28
+ * intentional, not an oversight — `managed` governs the UPSTREAM codex
29
+ * plugin's OWN Stop-hook loop, a third-party mechanism this hook merely
30
+ * observes and reports on (parsing an already-persisted job record); it
31
+ * never itself dispatches, spends, or calls a provider. The always-on-
32
+ * orchestration incident switch exists to pause OUR dispatch surfaces —
33
+ * there is no dispatch here to pause. Tripping the halt does not, and
34
+ * should not, silence this hook's circuit-breaker notice about the
35
+ * plugin's own loop.
14
36
  *
15
37
  * Composition point (wired): registered as the `team-review-gate` concern
16
38
  * in `src/scripts/hook_manifest.yaml` on the claude `stop` slot, after
@@ -56,7 +78,7 @@ export function main(): number {
56
78
  // owns reporting it. Defaults = managed off = strict no-op.
57
79
  config = AI_TEAM_DEFAULTS;
58
80
  }
59
- if (!config.enabled || !config.review_gate.managed) {
81
+ if (!config.review_gate.managed) {
60
82
  return 0;
61
83
  }
62
84
 
@@ -331,18 +331,9 @@ export const settingsSchema = z.object({
331
331
  }).default({}),
332
332
  }).default({}),
333
333
  subagents: z.object({
334
- enabled: z.boolean().default(true).describe(
335
- 'Global master switch for the subagent layer. true (default) = available; false = fully disabled (the canonical kill-switch — no auto-dispatch, no routing, everything runs in-session).',
336
- ),
337
- auto: z.enum(['off', 'ask', 'on']).default('on').describe(
338
- 'Automatic-dispatch mode. off = subagents only via explicit command. ask = classify the task and ask once before dispatching. on = auto-dispatch delegable tasks (surface the choice in one line). Shipped default is on on subagent-capable hosts (flipped from ask 2026-07-09, ADR-117), off elsewhere; no-op where the host has no subagent primitive.',
339
- ),
340
334
  downshift: z.boolean().default(true).describe(
341
335
  'Route delegable sub-tasks to the lowest-capable model tier (cost + speed via model downshift). false = every subagent runs on the session tier.',
342
336
  ),
343
- budget_routing: z.enum(['ask', 'auto', 'off']).default('ask').describe(
344
- 'Budget-aware cheap-request delegation (docs/contracts/budget-routing.md): pick the cheapest classifier-adequate tier WITH available budget (cost.budgets.per_tier); cheap exhausted but strong funded → strong tier; all exhausted → session model + notice. Work is never blocked to save money; the session model is never switched (delegation with a model override). ask = confirm the first budget-motivated downshift per session (default). auto = apply silently with telemetry. off = today\'s behavior (single-flip rollback). Distinct from downshift: that is the quality/cost knob, this is the resource-availability gate.',
345
- ),
346
337
  quota_arbitrage: z.boolean().default(true).describe(
347
338
  'Prefer a separate quota-pool model for delegable sub-tasks where the host manifest reports one. Optional bonus only — identical behaviour (minus the quota win) where unsupported. Never load-bearing.',
348
339
  ),
@@ -353,9 +344,6 @@ export const settingsSchema = z.object({
353
344
  }).default({}).describe(
354
345
  'Per-tier model map for downshift routing. Each empty value uses the tier runtime default (no vendor model baked in).',
355
346
  ),
356
- host_capabilities: z.object({}).passthrough().default({}).describe(
357
- 'Optional override of the host-capability manifest (subagent_spawn / parallel_spawn / status_polling / separate_quota_pool). Any field set wins; omitted fields fall back to the all-false safe default. Empty = the agent resolves the manifest from host knowledge.',
358
- ),
359
347
  implementer_model: z.string().default('').describe(
360
348
  'Override the model the orchestrator dispatches to subagents that write code (e.g. claude-sonnet-4, gpt-5). Empty (default) = inherit the session\'s primary model — cheapest and usually right.',
361
349
  ),
@@ -375,14 +363,11 @@ export const settingsSchema = z.object({
375
363
  ),
376
364
  }),
377
365
  ai_team: z.object({
378
- enabled: z.boolean().default(false).describe(
379
- 'Master switch for the /team cross-model review family (docs/contracts/ai-team-config.md). false (default) = commands are never suggested and every invocation refuses with an enable pointer. true = the family is live; individual gates (allow_delegate) still apply.',
380
- ),
381
366
  model: z.string().default('auto').describe(
382
367
  "Model handed to the codex CLI. 'auto' (default) = pass no --model flag so the CLI's own default applies — tracks the subscription's current strongest model instead of pinning a stale ID. Any other value passes through verbatim as `--model <value>`.",
383
368
  ),
384
369
  allow_delegate: z.boolean().default(false).describe(
385
- 'Second opt-in for the only wrapper that delegates write access (/team:delegate). false (default) = delegate refuses even when ai_team.enabled is true. Both keys must be true before delegation is reachable.',
370
+ 'Second opt-in for the only wrapper that delegates write access (/team:delegate). false (default) = delegate refuses even when /team itself is available. Availability (codex CLI installed + authenticated) is necessary but not sufficient — this key must ALSO be true before delegation is reachable.',
386
371
  ),
387
372
  max_calls_per_day: z.number().int().min(0).default(50).describe(
388
373
  'Per-day cap on team calls, read against the EXISTING cli_call_budget openai bucket (~/.event4u/agent-config/cli-calls.json, daily UTC reset) — one subscription, one counter, never a parallel count. 0 = block all team calls.',
@@ -398,7 +383,15 @@ export const settingsSchema = z.object({
398
383
  'Circuit-breaker bound: after this many CONSECUTIVE BLOCK verdicts in one session, a visible notice is injected exactly once and the managed layer stops re-blocking — the user decides, never an infinite Claude↔Codex loop. An ALLOW verdict resets the counter. Positive integer.',
399
384
  ),
400
385
  }).default({ managed: false, max_consecutive_blocks: 3 }),
401
- }).default({ enabled: false, model: 'auto', allow_delegate: false, max_calls_per_day: 50, suppress_setup_hint: false, review_gate: { managed: false, max_consecutive_blocks: 3 } }),
386
+ }).default({ model: 'auto', allow_delegate: false, max_calls_per_day: 50, suppress_setup_hint: false, review_gate: { managed: false, max_consecutive_blocks: 3 } }),
387
+ emergency: z.object({
388
+ orchestration_halt: z.boolean().default(false).describe(
389
+ 'The one audited incident switch over the always-on orchestration stack (subagents, council, team). NOT an activation gate: false (default) = the stack runs normally. true = halted; arming requires no ceremony. Disarming (returning to false) requires orchestration_halt_justification to be a non-empty string. Both transitions emit one telemetry line.',
390
+ ),
391
+ orchestration_halt_justification: z.string().default('').describe(
392
+ 'Required non-empty before orchestration_halt may return to false. Ignored while arming the halt.',
393
+ ),
394
+ }).default({ orchestration_halt: false, orchestration_halt_justification: '' }),
402
395
  onboarding: z.object({
403
396
  onboarded: z.boolean().default(false).describe(
404
397
  'Set to true once the developer has completed `agent-config setup`. The onboarding-gate rule blocks the first turn of every chat until this is true. Toggle back to false to re-trigger the wizard.',
@@ -57,16 +57,6 @@ export const SETTINGS_CARVE_OUT: readonly CarveOutKey[] = [
57
57
  'Same upgrade contract as projection.mode, and the failure is louder: '
58
58
  + 'an absent list widens the projection instead of narrowing it.',
59
59
  },
60
- {
61
- key: 'subagents.auto',
62
- reader: 'src/scripts/routing_doctor.ts:206',
63
- absentResolvesTo: 'ask',
64
- reason:
65
- 'The reader falls back to `ask` while the template ships `on`. Carved out '
66
- + 'rather than fixed at the reader because the doctor reports what the '
67
- + 'RESOLVED configuration is; changing its fallback would make the report '
68
- + 'disagree with what the other readers actually do.',
69
- },
70
60
  {
71
61
  key: 'discipline_profile',
72
62
  reader: 'work_engine/_lib/agent_settings.ts:1263',