@event4u/agent-config 9.28.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 (147) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/AGENTS.md +6 -6
  3. package/CHANGELOG.md +66 -0
  4. package/README.md +1 -1
  5. package/agents/templates/.ai-council.yml.example +55 -24
  6. package/dist/agent-src/commands/cost/report.md +2 -2
  7. package/dist/agent-src/commands/team/adversarial.md +8 -7
  8. package/dist/agent-src/commands/team/delegate.md +13 -12
  9. package/dist/agent-src/commands/team/review.md +9 -8
  10. package/dist/agent-src/commands/team/status.md +9 -8
  11. package/dist/agent-src/commands/team.md +29 -19
  12. package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +88 -2
  13. package/dist/agent-src/contexts/execution/auto-orchestration-activation.md +29 -21
  14. package/dist/agent-src/contexts/execution/host-capability-manifest.md +26 -15
  15. package/dist/agent-src/contexts/execution/orchestration-telemetry.md +18 -0
  16. package/dist/agent-src/contexts/execution/subagent-response-contract.md +54 -0
  17. package/dist/agent-src/contexts/execution/subagent-routing.md +13 -11
  18. package/dist/agent-src/contexts/execution/subagent-spawn-contract.md +25 -0
  19. package/dist/agent-src/contexts/execution/subagent-steering.md +6 -3
  20. package/dist/agent-src/rules/delegation-policy.md +11 -7
  21. package/dist/agent-src/rules/settings-ask-protocol.md +8 -7
  22. package/dist/agent-src/rules/token-budget-discipline.md +1 -1
  23. package/dist/agent-src/skills/forensics-report/SKILL.md +114 -0
  24. package/dist/agent-src/skills/forensics-report/evals/triggers.json +17 -0
  25. package/dist/agent-src/skills/premortem/SKILL.md +30 -1
  26. package/dist/agent-src/skills/reasoning-orchestrator/SKILL.md +2 -1
  27. package/dist/agent-src/skills/subagent-orchestration/SKILL.md +7 -5
  28. package/dist/agent-src/skills/subagent-orchestration/schemas/subagent-status.json +61 -3
  29. package/dist/agent-src/skills/test-case-discovery/SKILL.md +1 -1
  30. package/dist/agent-src/templates/AGENTS.md +4 -4
  31. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  32. package/dist/agent-src/templates/roadmaps.md +23 -0
  33. package/dist/cli-delegate/{chunk-22GCIJ2O.js → chunk-66OVWGB5.js} +1 -1
  34. package/dist/cli-delegate/{chunk-GB2FSUEE.js → chunk-LCFJIQ4J.js} +32 -0
  35. package/dist/cli-delegate/{chunk-VSYT7OHO.js → chunk-LVRYBKTI.js} +31 -2
  36. package/dist/cli-delegate/{chunk-DVRS2KVR.js → chunk-MMXA5GY7.js} +10 -17
  37. package/dist/cli-delegate/{chunk-JXARCGVN.js → chunk-MP3JT3IU.js} +1 -1
  38. package/dist/cli-delegate/{chunk-PQSAQKHW.js → chunk-NZ5N2XQL.js} +3 -3
  39. package/dist/cli-delegate/{chunk-7OMQCH4V.js → chunk-OJM3MBJM.js} +1 -1
  40. package/dist/cli-delegate/{chunk-2BE5I2MN.js → chunk-RCIA67XN.js} +111 -90
  41. package/dist/cli-delegate/cmd_analyze_session.js +1 -1
  42. package/dist/cli-delegate/cmd_brand_status.js +1 -1
  43. package/dist/cli-delegate/cmd_conformance.js +6 -43
  44. package/dist/cli-delegate/cmd_doctor.js +9 -5
  45. package/dist/cli-delegate/cmd_explain.js +2 -2
  46. package/dist/cli-delegate/cmd_export.js +4 -4
  47. package/dist/cli-delegate/cmd_handoff.js +3 -3
  48. package/dist/cli-delegate/cmd_mcp_available.js +1 -1
  49. package/dist/cli-delegate/cmd_migrate.js +2 -2
  50. package/dist/cli-delegate/cmd_packs_active.js +2 -2
  51. package/dist/cli-delegate/cmd_prune.js +4 -4
  52. package/dist/cli-delegate/cmd_route_audit.js +2 -2
  53. package/dist/cli-delegate/cmd_route_explain.js +1 -1
  54. package/dist/cli-delegate/cmd_settings_get.js +1 -7
  55. package/dist/cli-delegate/cmd_settings_migrate.js +4 -4
  56. package/dist/cli-delegate/cmd_settings_set.js +1 -1
  57. package/dist/cli-delegate/cmd_sync.js +4 -4
  58. package/dist/cli-delegate/cmd_uninstall.js +4 -4
  59. package/dist/cli-delegate/cmd_update.js +1 -1
  60. package/dist/cli-delegate/cmd_upgrade.js +1 -1
  61. package/dist/cli-delegate/cmd_validate.js +4 -4
  62. package/dist/cli-delegate/cmd_versions.js +1 -1
  63. package/dist/discovery/deprecation-report.md +1 -1
  64. package/dist/discovery/discovery-manifest.json +69 -20
  65. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  66. package/dist/discovery/discovery-manifest.summary.md +3 -2
  67. package/dist/discovery/orphan-report.md +1 -1
  68. package/dist/discovery/packs.json +39 -2
  69. package/dist/discovery/trust-report.md +2 -2
  70. package/dist/discovery/workspaces.json +12 -4
  71. package/dist/hooks/dispatch.js +373 -35
  72. package/dist/install/install.mjs +42 -17
  73. package/dist/mcp/registry-manifest.json +2 -2
  74. package/dist/mcp/server.json +2 -2
  75. package/dist/mcp/server.mjs +651 -262
  76. package/dist/router.json +1 -1
  77. package/dist/scripts/_lib/agent_settings.js +54 -0
  78. package/dist/scripts/_lib/agent_settings.js.map +1 -1
  79. package/dist/scripts/condense.js +147 -2
  80. package/dist/scripts/condense.js.map +1 -1
  81. package/dist/server/schemas/settings.js +6 -7
  82. package/dist/server/schemas/settings.js.map +1 -1
  83. package/dist/shared/settingsCarveOut.js +0 -9
  84. package/dist/shared/settingsCarveOut.js.map +1 -1
  85. package/dist/shared/settingsClasses.js +11 -9
  86. package/dist/shared/settingsClasses.js.map +1 -1
  87. package/dist/ui/assets/{index-DmR0mbk4.js → index-JDAF00bC.js} +2 -2
  88. package/dist/ui/assets/{index-DmR0mbk4.js.map → index-JDAF00bC.js.map} +1 -1
  89. package/dist/ui/index.html +1 -1
  90. package/docs/contracts/settings-classes.md +39 -9
  91. package/package.json +1 -1
  92. package/src/agent-src/templates/AGENTS.md +4 -4
  93. package/src/config/agent-settings.template.yml +52 -66
  94. package/src/config/discovery/packs.yml +11 -0
  95. package/src/config/discovery/workspaces.yml +1 -1
  96. package/src/config/gate-violation-baselines.json +3 -3
  97. package/src/scripts/_cli/cmd_conformance.ts +14 -42
  98. package/src/scripts/_cli/cmd_doctor.ts +65 -48
  99. package/src/scripts/_lib/agent_settings.ts +58 -0
  100. package/src/scripts/_lib/auto_dispatch.corpus.test.ts +1 -1
  101. package/src/scripts/_lib/auto_dispatch.ts +23 -18
  102. package/src/scripts/_lib/capsule_trigger.ts +124 -0
  103. package/src/scripts/_lib/host_capability.ts +63 -6
  104. package/src/scripts/_lib/judgment_ladder.ts +382 -0
  105. package/src/scripts/_lib/orchestration_gate.ts +8 -0
  106. package/src/scripts/_lib/orchestration_record.ts +37 -0
  107. package/src/scripts/_lib/orchestration_savings.ts +1 -1
  108. package/src/scripts/_lib/self_repair.ts +160 -13
  109. package/src/scripts/_lib/self_repair_store.ts +28 -0
  110. package/src/scripts/_lib/subagent_capsule.ts +182 -0
  111. package/src/scripts/_lib/subagent_response.ts +19 -0
  112. package/src/scripts/_lib/subagent_spawn.ts +13 -0
  113. package/src/scripts/_lib/subagent_steering.ts +9 -4
  114. package/src/scripts/_lib/tier_budget_routing.ts +6 -1
  115. package/src/scripts/_lib/worker_budget.ts +37 -2
  116. package/src/scripts/ai_council/clients.ts +76 -54
  117. package/src/scripts/ai_council/config.ts +94 -6
  118. package/src/scripts/ai_council/handoff.ts +113 -0
  119. package/src/scripts/ai_council/modes.ts +19 -4
  120. package/src/scripts/ai_council/orchestrator.ts +105 -0
  121. package/src/scripts/ai_council/quorum.ts +84 -0
  122. package/src/scripts/ai_council/session.ts +44 -0
  123. package/src/scripts/ai_council/transport_resolver.ts +127 -1
  124. package/src/scripts/ai_team/availability.ts +110 -0
  125. package/src/scripts/ai_team/config.ts +34 -14
  126. package/src/scripts/ai_team/team_dispatch.ts +71 -29
  127. package/src/scripts/bench_critic_protocol.ts +396 -0
  128. package/src/scripts/check_detector_corpus.ts +202 -0
  129. package/src/scripts/check_hook_bundle_freshness.ts +4 -1
  130. package/src/scripts/condense.ts +162 -2
  131. package/src/scripts/council_cli.ts +401 -25
  132. package/src/scripts/forensics_report.ts +333 -0
  133. package/src/scripts/hooks/delegation_nudge_hook.ts +69 -35
  134. package/src/scripts/lint_discovery_vocabulary.ts +2 -0
  135. package/src/scripts/lint_never_silent.ts +276 -0
  136. package/src/scripts/lint_no_activation_gates.ts +271 -0
  137. package/src/scripts/orchestration_record.ts +8 -0
  138. package/src/scripts/rebuild_hook_bundle.ts +177 -0
  139. package/src/scripts/release.ts +74 -2
  140. package/src/scripts/routing_doctor.ts +21 -36
  141. package/src/scripts/score_skill_selection.ts +19 -5
  142. package/src/scripts/self_repair_cli.ts +287 -57
  143. package/src/scripts/team_events_spike.ts +271 -0
  144. package/src/scripts/team_review_gate_hook.ts +31 -9
  145. package/src/server/schemas/settings.ts +10 -17
  146. package/src/shared/settingsCarveOut.ts +0 -10
  147. package/src/shared/settingsClasses.ts +11 -9
@@ -0,0 +1,382 @@
1
+ /**
2
+ * Judgment ladder (Phase 2, road-to-always-on-orchestration) — the ONE
3
+ * resolver that decides which of the five dispatch rungs (0-4), or the
4
+ * silent ∅, a task resolves to. Committed table + rationale:
5
+ * `src/agent-src/contexts/execution/auto-dispatch-classification.md`
6
+ * § Judgment ladder.
7
+ *
8
+ * This WRAPS `classifyTask` rather than replacing it — rungs 1 and 2 ARE
9
+ * `classifyTask`'s existing dispatch verdict (single-slice vs multi-slice);
10
+ * rungs 0, 3, and 4 are new signal layers checked around it, in a FIXED
11
+ * priority order (documented on {@link classifyLadder} itself, because the
12
+ * order IS the contract: two signals matching the same text must resolve
13
+ * deterministically, never on evaluation-order accident).
14
+ *
15
+ * Three previously-scattered classification surfaces (the delegable-signal
16
+ * rules in `auto_dispatch.ts`, ad-hoc "should this be a team" judgment
17
+ * calls, and the absence of any council-routing signal on the TASK side)
18
+ * get ONE committed name and ONE resolver here — never a fourth parallel
19
+ * classifier bolted on beside it.
20
+ *
21
+ * Deliberately independent of `ai_council/necessity.ts`: that module is the
22
+ * council's OWN necessity gate (its `NECESSARY_TRIGGERS` vocabulary and its
23
+ * `off|educate|block|warn-only` modes stay the council-side surface, per the
24
+ * roadmap's 2.2 note). This resolver's rung-4 signals are a NEW, narrower
25
+ * set — the three shapes the roadmap names explicitly (design decision,
26
+ * security downgrade, release-gate escalation) — kept as separate,
27
+ * regex-only constants rather than an import so this resolver carries no
28
+ * runtime dependency on the council module.
29
+ */
30
+ import {
31
+ classifyLookup,
32
+ classifyTask,
33
+ SIZE_FLOOR,
34
+ type ActivationInputs,
35
+ type DispatchMode,
36
+ type TaskSignals,
37
+ } from './auto_dispatch.js';
38
+
39
+ /** The five dispatch rungs, or `null` for the silent ∅ (in-session / ask). */
40
+ export type LadderRung = 0 | 1 | 2 | 3 | 4 | null;
41
+
42
+ export type LadderVerdict =
43
+ | 'script' // rung 0 — deterministic transform/primitive, no spawn at all
44
+ | 'subagent' // rung 1/2 — one, or several downshifted, subagent dispatch(es)
45
+ | 'team' // rung 3 — communicating slices, dispatched via the host teams primitive
46
+ | 'council' // rung 4 — contested judgment
47
+ | 'ask' // ∅ — ambiguous; a VERDICT to the user, never a speculative spawn
48
+ | 'in-session'; // ∅ — halted, no host primitive, trivial, approval-required, or recursive guard
49
+
50
+ export interface LadderResult {
51
+ rung: LadderRung;
52
+ verdict: LadderVerdict;
53
+ reason: string;
54
+ /**
55
+ * Present only when a HIGHER rung's signal fired but its precondition
56
+ * failed (today: rung 3's `agentTeams` host-capability check) and
57
+ * resolution fell back to a lower rung/verdict. Names the rung the
58
+ * signal originally pointed at, never the resolved one.
59
+ */
60
+ degraded_from?: LadderRung;
61
+ /**
62
+ * Carried through from `classifyTask` when the resolved rung is 1 or 2
63
+ * — the concrete `subagent-orchestration` mode a caller dispatches on.
64
+ * `null` for rung 1 (a single bounded slice has no do-in-steps /
65
+ * do-in-parallel shape of its own) and for every non-subagent verdict.
66
+ * Not part of the roadmap's minimal `{rung, verdict, reason,
67
+ * degraded_from?}` shape — added because the delegation-nudge hook
68
+ * (2.4) needs the concrete mode to build its injected line.
69
+ */
70
+ mode?: DispatchMode | null;
71
+ }
72
+
73
+ export interface LadderInputs {
74
+ /**
75
+ * Raw task/prompt text — regex-only signal extraction for rungs 0, 1, 3,
76
+ * and 4, in the same discipline as `delegation_nudge_hook.ts`'s
77
+ * extractors (cheap, no LLM call; false-positive/negative trade-offs
78
+ * documented per pattern below).
79
+ */
80
+ taskText: string;
81
+ /** Structural signals `classifyTask` already consumes — rungs 1/2. */
82
+ signals: TaskSignals;
83
+ /** Emergency halt + subagent_spawn primitive — gates every rung above 0. */
84
+ activation: ActivationInputs;
85
+ /**
86
+ * Host-capability manifest's `agent_teams` field
87
+ * (`host_capability.ts::probeHostCapabilities`) — rung 3's precondition.
88
+ * Absent/`false` degrades a matched rung-3 signal to rung 2.
89
+ */
90
+ agentTeams: boolean;
91
+ /**
92
+ * Caller states this step needs an explicit human decision before
93
+ * anything may run (an approval gate, a Hard-Floor action, …) — NEVER
94
+ * inferred from `taskText`. Checked first; overrides every other signal.
95
+ */
96
+ interactiveApprovalRequired?: boolean;
97
+ /**
98
+ * Recursive-dispatch guard (2.3). See {@link classifyLadder}'s doc
99
+ * comment for the honest scope of what this suite can and cannot
100
+ * detect on its own — this field is a CALLER-SUPPLIED fact, never
101
+ * something this resolver probes for itself.
102
+ */
103
+ insideSubagentSession?: boolean;
104
+ }
105
+
106
+ // ── Rung 0 — mechanical-transform signals (regex-only) ──────────────────
107
+ //
108
+ // `classifyLookup` (auto_dispatch.ts, road-to-lean-agent-init L0) already
109
+ // covers the READ-ONLY half of "deterministic script, no spawn" (definition/
110
+ // references/string-existence/report-run lookups). This adds the WRITE half
111
+ // the roadmap names explicitly: mechanical transforms with no semantics to
112
+ // judge — a rename, a codemod, a formatter run, a bulk search-and-replace.
113
+ // Each pattern requires the verb AND its object shape (e.g. "rename X to Y",
114
+ // not bare "rename") so a sentence that merely MENTIONS renaming in passing
115
+ // ("we renamed this last week") does not false-positive into rung 0.
116
+ const RENAME_RE = /\brename\s+\S+\s+to\s+\S+\b/i;
117
+ const CODEMOD_RE = /\bcodemod\b/i;
118
+ const FORMATTER_RUN_RE =
119
+ /\bauto-?format(?:ting)?\b|\breformat(?:ting)?\b|\brun(?:ning)?\s+prettier\b|\brun(?:ning)?\s+eslint\s*--fix\b|\bapply\s+eslint\s*--fix\b/i;
120
+ const SEARCH_REPLACE_RE = /\bsearch-and-replace\b|\bfind-and-replace\b|\bbulk\s+rename\b/i;
121
+
122
+ export function detectMechanicalTransform(text: string): { matched: boolean; reason: string } {
123
+ const rename = RENAME_RE.exec(text);
124
+ if (rename) {
125
+ return { matched: true, reason: `rename signal ("${rename[0]}") — deterministic script, no spawn` };
126
+ }
127
+ const codemod = CODEMOD_RE.exec(text);
128
+ if (codemod) {
129
+ return { matched: true, reason: `codemod signal ("${codemod[0]}") — deterministic script, no spawn` };
130
+ }
131
+ const formatter = FORMATTER_RUN_RE.exec(text);
132
+ if (formatter) {
133
+ return { matched: true, reason: `formatter-run signal ("${formatter[0]}") — deterministic script, no spawn` };
134
+ }
135
+ const searchReplace = SEARCH_REPLACE_RE.exec(text);
136
+ if (searchReplace) {
137
+ return {
138
+ matched: true,
139
+ reason: `search-and-replace/bulk-rename signal ("${searchReplace[0]}") — deterministic script, no spawn`,
140
+ };
141
+ }
142
+ return { matched: false, reason: 'no mechanical-transform signal' };
143
+ }
144
+
145
+ // ── Rung 1 — single bounded read-heavy slice (regex-only) ───────────────
146
+ //
147
+ // The one rung `classifyTask` structurally cannot see: its delegable rules
148
+ // all require >=2 slices, an ordered plan, or a declared `parallelizable`
149
+ // frontmatter value. A single "read this whole file and summarize it" task
150
+ // is real, non-trivial work that still benefits from a lite-tier subagent
151
+ // (session-context savings) but has no multi-slice shape at all. Requires a
152
+ // read-heavy VERB plus a bounded, singular TARGET phrase — a bare "read the
153
+ // code" without a stated target is too weak a signal to spawn on.
154
+ const SINGLE_SLICE_READHEAVY_RE =
155
+ /\b(?:read|review|summarize|summarise|inventory|audit|investigate|research|inspect|dig into|look into)\b[^.!?\n]{0,80}\b(?:this file|the file|this codebase|the codebase|this transcript|the transcript|this log|the log|this document|the document|this diff|the diff)\b/i;
156
+
157
+ export function detectBoundedReadHeavySlice(text: string): { matched: boolean; reason: string } {
158
+ const m = SINGLE_SLICE_READHEAVY_RE.exec(text);
159
+ return m
160
+ ? {
161
+ matched: true,
162
+ reason: `single bounded read-heavy slice ("${m[0]}") — one lite-tier subagent instead of session-context burn`,
163
+ }
164
+ : { matched: false, reason: 'no single-slice read-heavy signal' };
165
+ }
166
+
167
+ // ── Rung 3 — communication-need signals (team) ───────────────────────────
168
+ //
169
+ // "Slices that must communicate" per the committed table: cross-layer work,
170
+ // an in-flight review-with-challenge shape, or an explicit shared work
171
+ // queue — signals a single implementer-then-judge handoff, or a set of
172
+ // independent parallel slices, cannot express, because the workers need to
173
+ // see each other's state DURING the run, not just at handoff.
174
+ const CROSS_LAYER_RE =
175
+ /\bcross[- ]layer\b|\bcross[- ]cutting\b|\bback[- ]?end\s+and\s+front[- ]?end\b|\bfront[- ]?end\s+and\s+back[- ]?end\b/i;
176
+ const REVIEW_WITH_CHALLENGE_RE =
177
+ /\breview[- ]with[- ]challenge\b|\bchallenge[- ]and[- ]implement\b|\bimplementer\s+and\s+(?:a\s+)?(?:judge|reviewer)\s+(?:working\s+)?together\b|\bsecond\s+opinion\s+while\s+(?:building|implementing)\b/i;
178
+ const SHARED_TASK_LIST_RE =
179
+ /\bshared\s+(?:task\s+list|todo\s+list|backlog)\b|\bcoordinat(?:e|ed|ing)\s+(?:with|between|across)\s+(?:multiple\s+|several\s+)?(?:agents|workers|subagents|teammates)\b/i;
180
+
181
+ export function detectCommunicationNeed(text: string): { matched: boolean; reason: string } {
182
+ const crossLayer = CROSS_LAYER_RE.exec(text);
183
+ if (crossLayer) {
184
+ return { matched: true, reason: `cross-layer communication signal ("${crossLayer[0]}")` };
185
+ }
186
+ const reviewChallenge = REVIEW_WITH_CHALLENGE_RE.exec(text);
187
+ if (reviewChallenge) {
188
+ return { matched: true, reason: `review-with-challenge signal ("${reviewChallenge[0]}")` };
189
+ }
190
+ const sharedList = SHARED_TASK_LIST_RE.exec(text);
191
+ if (sharedList) {
192
+ return { matched: true, reason: `shared-task-list signal ("${sharedList[0]}")` };
193
+ }
194
+ return { matched: false, reason: 'no communication-need signal' };
195
+ }
196
+
197
+ // ── Rung 4 — contested-judgment signals (council) ────────────────────────
198
+ //
199
+ // "Judgment under disagreement" per the committed table: a design decision,
200
+ // a proposed security downgrade, or an escalation at a release gate — the
201
+ // three shapes the roadmap names explicitly. Deliberately its OWN, narrower
202
+ // vocabulary rather than an import of `ai_council/necessity.ts`'s broader
203
+ // `NECESSARY_TRIGGERS` (architecture/tradeoff/ambiguity/strategic): that
204
+ // module stays the council's own necessity gate; this resolver only needs
205
+ // the narrow signal that should route a TASK to rung 4 in the first place.
206
+ // A bare `\badr\b` (no decision-context anchor) matched "update the ADR
207
+ // index" and "fix the release-gate CI script typo" — routine maintenance
208
+ // prose that names the artefact/gate but decides nothing — and suppressed
209
+ // the correct rung-2 nudge on both. Both alternatives below now require an
210
+ // explicit decision/escalation VERB within 40 chars of the artefact/gate
211
+ // token, on either side, so the bare noun alone never fires.
212
+ const DESIGN_DECISION_RE =
213
+ /\bdesign\s+decision\b|\barchitectur(?:e|al)\s+decision\b|\b(?:record(?:ing)?|decide|deciding|decision|accept(?:ed|ing)?|approv(?:e|ed|ing|al)|challeng(?:e|ed|ing)|choos(?:e|ing)|reject(?:ed|ing)?)\b[^.!?\n]{0,40}\bADR\b|\bADR\b[^.!?\n]{0,40}\b(?:record(?:ing)?|decide|deciding|decision|accept(?:ed|ing)?|approv(?:e|ed|ing|al)|challeng(?:e|ed|ing)|choos(?:e|ing)|reject(?:ed|ing)?)\b/i;
214
+ const SECURITY_DOWNGRADE_RE = /\bsecurity\s+downgrade\b|\bdowngrade\s+(?:the\s+)?security\b|\bsecurity\s+exception\b/i;
215
+ const RELEASE_GATE_ESCALATION_RE =
216
+ /\bgate\s+escalation\b|\bescalat(?:e|ion)\s+to\s+(?:the\s+)?council\b|\b(?:escalat(?:e|ing|ion)|contest(?:ed|ing)?|dispute|disputing|override|overriding|waiv(?:e|ing)|bypass(?:ed|ing)?|challeng(?:e|ed|ing))\b[^.!?\n]{0,40}\brelease[- ]gate\b|\brelease[- ]gate\b[^.!?\n]{0,40}\b(?:escalat(?:e|ing|ion)|contest(?:ed|ing)?|dispute|disputing|override|overriding|waiv(?:e|ing)|bypass(?:ed|ing)?|challeng(?:e|ed|ing))\b/i;
217
+
218
+ export function detectContestedJudgment(text: string): { matched: boolean; reason: string } {
219
+ const design = DESIGN_DECISION_RE.exec(text);
220
+ if (design) {
221
+ return { matched: true, reason: `design-decision signal ("${design[0]}")` };
222
+ }
223
+ const security = SECURITY_DOWNGRADE_RE.exec(text);
224
+ if (security) {
225
+ return { matched: true, reason: `security-downgrade signal ("${security[0]}")` };
226
+ }
227
+ const releaseGate = RELEASE_GATE_ESCALATION_RE.exec(text);
228
+ if (releaseGate) {
229
+ return { matched: true, reason: `release-gate-escalation signal ("${releaseGate[0]}")` };
230
+ }
231
+ return { matched: false, reason: 'no contested-judgment signal' };
232
+ }
233
+
234
+ /**
235
+ * Resolve the judgment-ladder verdict for one task.
236
+ *
237
+ * FIXED priority order (the order is the contract):
238
+ *
239
+ * 1. `interactiveApprovalRequired` — caller-stated, overrides everything.
240
+ * 2. Rung 0 (`classifyLookup` primitive match OR a mechanical-transform
241
+ * signal) — never spawns, so neither the recursive guard nor the
242
+ * activation gate applies to it.
243
+ * 3. Recursive-dispatch guard (2.3) — `insideSubagentSession` resolves ∅
244
+ * for every rung below this point.
245
+ * 4. Activation gate (`halted` / no `subagent_spawn`) — nothing below this
246
+ * point can spawn without it.
247
+ * 5. Rung 4 (contested-judgment signal) — council.
248
+ * 6. Rung 3 (communication-need signal) — team when `agentTeams`; otherwise
249
+ * degrades to whatever `classifyTask` resolves on the SAME signals
250
+ * (usually rung 2 — slices that must communicate are usually plural
251
+ * slices — but this honestly follows `classifyTask`'s real verdict
252
+ * rather than fabricating a slice count nothing confirmed).
253
+ * 7. Rungs 1/2 — `classifyTask`'s dispatch verdict. `do-in-steps` and
254
+ * `do-in-parallel` both resolve rung 2 in this v1: the roadmap's rung
255
+ * 1-vs-2 split is single-slice vs multi-slice, and `classifyTask`
256
+ * structurally never dispatches on a single slice (its rules require
257
+ * >=2 slices, an ordered plan, or a declared `parallelizable` value) —
258
+ * so every `classifyTask` dispatch verdict is inherently multi-slice.
259
+ * Its `ask` / size-floor `in-session` outcomes get one more check
260
+ * first: rung 1's single-bounded-slice signal, the one shape
261
+ * `classifyTask`'s own rules cannot see.
262
+ *
263
+ * ## Recursive-dispatch guard — honest scope (2.3)
264
+ *
265
+ * No verified host discriminator exists for "is this classification running
266
+ * inside a subagent/teammate session". This roadmap's own
267
+ * `point-of-action-carrier` blocker records that the upstream PreToolUse
268
+ * agent-identity request is closed NOT_PLANNED, and nothing in this repo's
269
+ * hook envelope (`event` / `payload` / `platform` / `workspace_root` —
270
+ * `delegation_nudge_hook.ts`'s own `main()`) carries a session-lineage
271
+ * field. `insideSubagentSession` is therefore a CALLER-SUPPLIED fact: the
272
+ * code path that dispatched the subagent/teammate loop is the one place
273
+ * that genuinely knows. This resolver does NOT probe `process.env` for
274
+ * it — inventing an unverified `CLAUDE_AGENT_*`-shaped variable name here
275
+ * would be exactly the hallucinated-field failure `source-discovery-gate`
276
+ * exists to stop. Documented as a gap, not silently assumed away: on a
277
+ * host/caller that never sets this field, the guard is a no-op and rungs
278
+ * 1-4 resolve normally even inside a subagent — the same gap the
279
+ * `point-of-action-carrier` blocker already names as its pre-registered
280
+ * null ("no discriminator" is publishable and does not block this roadmap).
281
+ */
282
+ export function classifyLadder(inp: LadderInputs): LadderResult {
283
+ if (inp.interactiveApprovalRequired === true) {
284
+ return {
285
+ rung: null,
286
+ verdict: 'in-session',
287
+ reason: 'interactive-approval-required — caller-stated, needs a human decision before anything may run',
288
+ };
289
+ }
290
+
291
+ // Rung 0 — never spawns, so checked before the recursive guard and the
292
+ // activation gate: neither one is a precondition for a script that
293
+ // never dispatches anything.
294
+ const lookup = classifyLookup(inp.taskText);
295
+ if (lookup.route === 'primitive') {
296
+ return { rung: 0, verdict: 'script', reason: `lookup-class ${lookup.lookup_class} — ${lookup.reason}` };
297
+ }
298
+ const mechanical = detectMechanicalTransform(inp.taskText);
299
+ if (mechanical.matched) {
300
+ return { rung: 0, verdict: 'script', reason: mechanical.reason };
301
+ }
302
+
303
+ if (inp.insideSubagentSession === true) {
304
+ return {
305
+ rung: null,
306
+ verdict: 'in-session',
307
+ reason:
308
+ 'recursive-dispatch guard: this classification runs inside a subagent/teammate session — ' +
309
+ 'rungs 1-4 resolve in-session',
310
+ };
311
+ }
312
+
313
+ if (inp.activation.halted) {
314
+ return { rung: null, verdict: 'in-session', reason: 'emergency.orchestration_halt is set' };
315
+ }
316
+ if (!inp.activation.subagent_spawn) {
317
+ return { rung: null, verdict: 'in-session', reason: 'host has no subagent_spawn primitive' };
318
+ }
319
+
320
+ const contested = detectContestedJudgment(inp.taskText);
321
+ if (contested.matched) {
322
+ return { rung: 4, verdict: 'council', reason: contested.reason };
323
+ }
324
+
325
+ const communication = detectCommunicationNeed(inp.taskText);
326
+ if (communication.matched) {
327
+ if (inp.agentTeams) {
328
+ return { rung: 3, verdict: 'team', reason: communication.reason };
329
+ }
330
+ const fallback = classifyTask(inp.signals, inp.activation);
331
+ if (fallback.action === 'dispatch' && fallback.mode !== null) {
332
+ return {
333
+ rung: 2,
334
+ verdict: 'subagent',
335
+ mode: fallback.mode,
336
+ degraded_from: 3,
337
+ reason: `${communication.reason} — no agent_teams host capability, degraded to rung 2 (${fallback.reason})`,
338
+ };
339
+ }
340
+ return {
341
+ rung: null,
342
+ verdict: fallback.action === 'ask' ? 'ask' : 'in-session',
343
+ degraded_from: 3,
344
+ reason: `${communication.reason} — no agent_teams host capability, degraded below rung 2 (${fallback.reason})`,
345
+ };
346
+ }
347
+
348
+ const classification = classifyTask(inp.signals, inp.activation);
349
+ if (classification.action === 'dispatch' && classification.mode !== null) {
350
+ return { rung: 2, verdict: 'subagent', mode: classification.mode, reason: classification.reason };
351
+ }
352
+
353
+ // A task below the size floor never delegates — not even via rung 1's
354
+ // narrower single-slice regex. `classifyTask` already enforces this
355
+ // floor for rung 2 (its own `inSession('task below size floor …')`
356
+ // verdict); checked directly on `size_estimate` here — rather than by
357
+ // string-matching `classification.reason` — so this guard survives a
358
+ // future reword of that message, and so it fires regardless of WHICH of
359
+ // classifyTask's `in-session` branches produced the non-dispatch verdict.
360
+ // Before this check, a floor-worthy prompt like "review this diff" (no
361
+ // enumerated slices → size_estimate 0) still matched rung 1's read-heavy
362
+ // regex and dispatched a subagent anyway, silently bypassing the floor
363
+ // `classifyTask` had just enforced one line above.
364
+ if (inp.signals.size_estimate <= SIZE_FLOOR) {
365
+ return {
366
+ rung: null,
367
+ verdict: classification.action === 'ask' ? 'ask' : 'in-session',
368
+ reason: classification.reason,
369
+ };
370
+ }
371
+
372
+ const single = detectBoundedReadHeavySlice(inp.taskText);
373
+ if (single.matched) {
374
+ return { rung: 1, verdict: 'subagent', mode: null, reason: single.reason };
375
+ }
376
+
377
+ return {
378
+ rung: null,
379
+ verdict: classification.action === 'ask' ? 'ask' : 'in-session',
380
+ reason: classification.reason,
381
+ };
382
+ }
@@ -14,6 +14,14 @@
14
14
  *
15
15
  * This helper decides; it never runs the measurement. The signal is realized
16
16
  * orchestration telemetry, aggregated + evaluated out of band.
17
+ *
18
+ * **Amendment (road-to-always-on-orchestration Phase 1).** `subagents.auto`
19
+ * was DELETED as a settings key — always-on orchestration carries no
20
+ * per-layer on/off setting to demote. `resolveShippedDefault` (the
21
+ * `subagents.auto`-specific half below) has no production caller today and
22
+ * is kept as a historical record of the demotion mechanism, not a live
23
+ * decision path; `resolveRecursiveDefault` (the `verification.recursive`
24
+ * half) is unaffected by this deletion.
17
25
  */
18
26
 
19
27
  export interface BenchmarkResult {
@@ -28,6 +28,8 @@ export type TierSource = 'static' | 'inferred' | 'inherit';
28
28
  export type Band = 'low' | 'medium' | 'high';
29
29
  export type LinePhase = 'refine' | 'memory' | 'analyze' | 'plan' | 'implement' | 'test' | 'verify' | 'report';
30
30
  export type LineOutcome = 'success' | 'blocked' | 'skipped' | 'error';
31
+ /** Which capsule-emission trigger arm fired first (Phase 1 shadow comparison). */
32
+ export type TriggerArm = 'watermark' | 'saturation' | 'tie';
31
33
  /** The orchestration form the form-gate selected (road-to-opt-subagent-harvest P2). */
32
34
  export type DispatchModeId =
33
35
  | 'do-and-judge'
@@ -96,6 +98,20 @@ export interface RecordInput {
96
98
  /** Rules the worker actually applied/cited (count of the envelope's
97
99
  * `rules_applied`-equivalent on the worker side). Counts only. */
98
100
  rules_used?: number | null | undefined;
101
+ // ── capsule shadow-measurement fields (road-to-worker-generation-recycling
102
+ // Phase 1.2). SHADOW ONLY: the worker still runs to stop-loss, nothing
103
+ // reads a capsule. All counts / enums — a capsule's CONTENT never
104
+ // reaches telemetry, only its shape. ──
105
+ /** A CHECKPOINT capsule was emitted at the watermark. */
106
+ capsule_emitted?: boolean | undefined;
107
+ /** Total entries across the capsule's arrays — a size proxy, never content. */
108
+ capsule_entries?: number | null | undefined;
109
+ /** 1-based step at which the token-watermark arm fired (null = never). */
110
+ watermark_step?: number | null | undefined;
111
+ /** 1-based step at which the novelty-saturation arm fired (null = never). */
112
+ saturation_step?: number | null | undefined;
113
+ /** Which arm fired first on this dispatch — the paired-comparison datum. */
114
+ trigger_arm_earlier?: TriggerArm | null | undefined;
99
115
  // Audit-log envelope (sensible defaults for a dispatch record)
100
116
  phase?: LinePhase | undefined;
101
117
  outcome?: LineOutcome | undefined;
@@ -123,6 +139,7 @@ const BANDS: readonly Band[] = ['low', 'medium', 'high'];
123
139
  const PHASES: readonly LinePhase[] = ['refine', 'memory', 'analyze', 'plan', 'implement', 'test', 'verify', 'report'];
124
140
  const LOOKUP_CLASSES: readonly LookupClass[] = ['definition', 'references', 'string-existence', 'report-run'];
125
141
  const ROUTES_TAKEN: readonly RouteTaken[] = ['primitive', 'subagent'];
142
+ const TRIGGER_ARMS: readonly TriggerArm[] = ['watermark', 'saturation', 'tie'];
126
143
  /** Hex-only payload hash — a hash can never smuggle content (privacy by construction). */
127
144
  const PAYLOAD_HASH_RE = /^[a-f0-9]{8,64}$/i;
128
145
  /** Id-shaped origin tag — enum-ish, never free-form prose. */
@@ -233,6 +250,20 @@ export function buildOrchestrationLine(input: RecordInput): BuiltLine {
233
250
  errors.push('rules_used cannot exceed rules_carried (a worker cannot apply a rule it was not given)');
234
251
  }
235
252
 
253
+ if (input.capsule_emitted !== undefined && typeof input.capsule_emitted !== 'boolean') {
254
+ errors.push('capsule_emitted must be a boolean or omitted');
255
+ }
256
+ for (const [key, v] of [
257
+ ['capsule_entries', input.capsule_entries],
258
+ ['watermark_step', input.watermark_step],
259
+ ['saturation_step', input.saturation_step],
260
+ ] as const) {
261
+ if (v != null && (!isInt(v) || v < 0)) errors.push(`${key} must be a non-negative integer count`);
262
+ }
263
+ if (input.trigger_arm_earlier != null && !TRIGGER_ARMS.includes(input.trigger_arm_earlier)) {
264
+ errors.push(`trigger_arm_earlier must be one of ${TRIGGER_ARMS.join(' | ')} (or omitted / null when neither arm fired)`);
265
+ }
266
+
236
267
  if (!input.ts) errors.push('ts (ISO-8601 UTC) is required');
237
268
  if (!input.id) errors.push('id (ULID or content hash) is required');
238
269
 
@@ -267,6 +298,12 @@ export function buildOrchestrationLine(input: RecordInput): BuiltLine {
267
298
  origin: input.origin ?? null,
268
299
  rules_carried: input.rules_carried ?? null,
269
300
  rules_used: input.rules_used ?? null,
301
+ // capsule shadow-measurement — readers ignore unknowns per audit-log-v1
302
+ capsule_emitted: input.capsule_emitted ?? null,
303
+ capsule_entries: input.capsule_entries ?? null,
304
+ watermark_step: input.watermark_step ?? null,
305
+ saturation_step: input.saturation_step ?? null,
306
+ trigger_arm_earlier: input.trigger_arm_earlier ?? null,
270
307
  };
271
308
 
272
309
  const line: Record<string, unknown> = {
@@ -212,7 +212,7 @@ export function aggregateOrchestrationSavings(
212
212
 
213
213
  // Honest caveats — always attached so a consumer never over-reads the number.
214
214
  if (report.dispatches === 0) {
215
- report.notes.push('No orchestration telemetry yet (0 dispatches). Data accrues as `subagents.auto: on` delegates real slices (recorded via orchestration_record).');
215
+ report.notes.push('No orchestration telemetry yet (0 dispatches). Data accrues as delegable slices dispatch (always-on orchestration; recorded via orchestration_record).');
216
216
  } else {
217
217
  report.notes.push('Token counts are MEASURED but blind to the downshift rate win (same tokens on a cheaper tier ≈ same count). The MODELED cost-% below captures that axis.');
218
218
  if (report.by_provenance.estimated.dispatches > 0) {