@event4u/agent-config 9.29.0 → 9.30.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.
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +108 -7
- package/README.md +1 -0
- package/dist/agent-src/commands/analyze/inbox.md +22 -1
- package/dist/agent-src/commands/explain-run.md +31 -1
- package/dist/agent-src/commands/team/delegate.md +1 -0
- package/dist/agent-src/contexts/execution/orchestration-telemetry.md +4 -1
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +17 -0
- package/dist/agent-src/contexts/execution/subagent-response-contract.md +11 -1
- package/dist/agent-src/rules/preservation-guard.md +9 -0
- package/dist/agent-src/scripts/roadmap_gates.ts +312 -0
- package/dist/agent-src/scripts/update_roadmap_progress.ts +72 -4
- package/dist/agent-src/skills/subagent-orchestration/SKILL.md +10 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/cli/registry.js +2 -0
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli-delegate/{chunk-OJM3MBJM.js → chunk-BTJNRUX3.js} +21 -34
- package/dist/cli-delegate/{chunk-MMXA5GY7.js → chunk-DDUNNFYX.js} +14 -0
- package/dist/cli-delegate/{chunk-NZ5N2XQL.js → chunk-EQY4FLJB.js} +1 -1
- package/dist/cli-delegate/{chunk-RCIA67XN.js → chunk-IYTGIG2Y.js} +44 -1
- package/dist/cli-delegate/chunk-MMJL4TIX.js +18 -0
- package/dist/cli-delegate/chunk-O3CO5BFB.js +161 -0
- package/dist/cli-delegate/{chunk-REKCMYOU.js → chunk-PFBV6XRT.js} +36 -20
- package/dist/cli-delegate/chunk-RVGBH4XK.js +142 -0
- package/dist/cli-delegate/cmd_conformance.js +2798 -95
- package/dist/cli-delegate/cmd_doctor.js +2 -1
- package/dist/cli-delegate/cmd_export.js +2 -2
- package/dist/cli-delegate/cmd_handoff.js +86 -6
- package/dist/cli-delegate/cmd_prune.js +2 -2
- package/dist/cli-delegate/cmd_route_audit.js +2 -1
- package/dist/cli-delegate/cmd_session_recycle.js +177 -0
- package/dist/cli-delegate/cmd_settings_get.js +1 -1
- package/dist/cli-delegate/cmd_settings_migrate.js +2 -2
- package/dist/cli-delegate/cmd_settings_set.js +2 -2
- package/dist/cli-delegate/cmd_sync.js +2 -2
- package/dist/cli-delegate/cmd_uninstall.js +2 -2
- package/dist/cli-delegate/cmd_validate.js +2 -2
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +7 -7
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +2 -2
- package/dist/discovery/trust-report.md +1 -1
- package/dist/discovery/workspaces.json +2 -2
- package/dist/hooks/dispatch.js +1904 -575
- package/dist/install/install.mjs +14 -0
- package/dist/mcp/registry-manifest.json +2 -2
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +3760 -726
- package/dist/server/schemas/settings.js +6 -0
- package/dist/server/schemas/settings.js.map +1 -1
- package/dist/ui/assets/index-B5qGYOJo.js +26 -0
- package/dist/ui/assets/index-B5qGYOJo.js.map +1 -0
- package/dist/ui/index.html +1 -1
- package/docs/contracts/settings-classes.md +21 -5
- package/package.json +1 -1
- package/src/agent-src/scripts/roadmap_gates.ts +312 -0
- package/src/agent-src/scripts/update_roadmap_progress.ts +72 -4
- package/src/config/agent-settings.template.yml +36 -0
- package/src/config/dispatch-economy-metrics.json +61 -0
- package/src/config/evaluator-budgets.json +4 -4
- package/src/config/hook-token-budget.json +91 -0
- package/src/config/recycle-threshold-budget.json +12 -0
- package/src/config/rule-enforcement-baseline.json +86 -0
- package/src/scripts/_cli/cmd_conformance.ts +17 -2
- package/src/scripts/_cli/cmd_session_recycle.ts +226 -0
- package/src/scripts/_dispatch.bash +23 -0
- package/src/scripts/_lib/judgment_ladder.ts +254 -2
- package/src/scripts/_lib/orchestration_record.ts +41 -10
- package/src/scripts/_lib/recycle_envelope_paths.ts +35 -0
- package/src/scripts/_lib/self_repair.ts +194 -0
- package/src/scripts/_lib/session_eol.ts +230 -0
- package/src/scripts/_lib/session_role.ts +42 -0
- package/src/scripts/_lib/subagent_capsule.ts +159 -0
- package/src/scripts/_lib/subagent_response.ts +75 -0
- package/src/scripts/ai_council/clients.ts +50 -1
- package/src/scripts/ask_transport.ts +221 -0
- package/src/scripts/bench_hook_injection.ts +234 -0
- package/src/scripts/check_adr_frontmatter.ts +84 -40
- package/src/scripts/check_branch_freshness.ts +1 -0
- package/src/scripts/check_corpus_staleness.ts +18 -4
- package/src/scripts/check_detector_corpus.ts +17 -0
- package/src/scripts/check_gitignore_freshness.ts +42 -8
- package/src/scripts/check_hook_bundle_freshness.ts +20 -0
- package/src/scripts/check_kernel_prefix_stability.ts +1 -0
- package/src/scripts/check_standing_rule_delivery.ts +73 -32
- package/src/scripts/check_static_layer_stability.ts +154 -0
- package/src/scripts/check_suppression_hygiene.ts +48 -0
- package/src/scripts/dispatch_economy_report.ts +448 -0
- package/src/scripts/dispatch_r2_reviewer.ts +15 -2
- package/src/scripts/explain_run.ts +206 -4
- package/src/scripts/generate_settings_reference.ts +1 -0
- package/src/scripts/handoff_context_hook.ts +112 -4
- package/src/scripts/hook_manifest.yaml +117 -10
- package/src/scripts/hooks/concern_registry.ts +8 -0
- package/src/scripts/hooks/delegation_nudge_hook.ts +40 -3
- package/src/scripts/hooks/dispatch_hook.ts +39 -2
- package/src/scripts/hooks/edit_shape_hook.ts +303 -0
- package/src/scripts/hooks/orchestration_record_hook.ts +19 -1
- package/src/scripts/hooks/reread_guard_hook.ts +314 -0
- package/src/scripts/hooks/rtk_wrap_hook.ts +133 -14
- package/src/scripts/hooks/session_eol_hook.ts +268 -0
- package/src/scripts/hooks/turn_end_gate_hook.ts +507 -0
- package/src/scripts/install-hooks.sh +1 -1
- package/src/scripts/language_mirror_hook.ts +89 -0
- package/src/scripts/lint_decision_impact.ts +14 -1
- package/src/scripts/lint_hook_manifest.ts +82 -0
- package/src/scripts/lint_readme_serial_comma.ts +1 -0
- package/src/scripts/lint_rule_enforcement_declaration.ts +347 -0
- package/src/scripts/measure_turn_end_gate.ts +194 -0
- package/src/scripts/orchestration_record.ts +4 -1
- package/src/scripts/rebuild_hook_bundle.ts +1 -0
- package/src/scripts/report_conformance_funnel.ts +284 -0
- package/src/scripts/report_imperative_density.ts +1 -0
- package/src/scripts/routing_doctor.ts +97 -0
- package/src/scripts/self_repair_cli.ts +51 -3
- package/src/scripts/session_eol_report.ts +261 -0
- package/src/server/schemas/settings.ts +14 -0
- package/src/templates/compact-instructions.md +40 -0
- package/dist/ui/assets/index-JDAF00bC.js +0 -26
- package/dist/ui/assets/index-JDAF00bC.js.map +0 -1
|
@@ -19,6 +19,14 @@
|
|
|
19
19
|
* 4. Subagent dispatches in window — mode, tiers, token deltas,
|
|
20
20
|
* first_pass_success/escalated. Source: same audit-log-v1 files,
|
|
21
21
|
* `orchestration` sub-object.
|
|
22
|
+
* 4b. (opt-in via `--decision "<task text>"`) Dispatch-decision trace —
|
|
23
|
+
* the judgment-ladder rung taken, every rung rejected with its
|
|
24
|
+
* detector's own reason (why-not-team / why-not-council /
|
|
25
|
+
* why-no-spawn), and the token/cost estimate from the most recent
|
|
26
|
+
* matching orchestration-telemetry record. No record in window → an
|
|
27
|
+
* honest "no telemetry record" line, never a fabricated estimate.
|
|
28
|
+
* Sources: `_lib/judgment_ladder.ts::explainLadder` + the same
|
|
29
|
+
* audit-log-v1 files as §4.
|
|
22
30
|
* 5. Hook / loop / freshness state snapshot. Source:
|
|
23
31
|
* `agents/state/context-hygiene.json` (the code path the hook
|
|
24
32
|
* actually writes — see `context_hygiene_hook.ts::STATE_FILE`) or
|
|
@@ -39,7 +47,10 @@
|
|
|
39
47
|
* ./scripts-run src/scripts/explain_run \
|
|
40
48
|
* [--task <id>] [--since <ISO-8601>] \
|
|
41
49
|
* [--router <path>] [--audit-dir <path>] [--engagement <path>] \
|
|
42
|
-
* [--hygiene <path>] [--output <path>]
|
|
50
|
+
* [--hygiene <path>] [--output <path>] \
|
|
51
|
+
* [--decision "<task text>"] [--size-estimate <n>] [--slices <n>] \
|
|
52
|
+
* [--ordered-plan] [--agent-teams] [--halted] [--no-spawn-primitive] \
|
|
53
|
+
* [--inside-subagent] [--approval-required]
|
|
43
54
|
*
|
|
44
55
|
* Defaults: --router dist/router.json · --audit-dir agents/runtime/state/audit
|
|
45
56
|
* · --engagement .agent-engagement.jsonl · --hygiene tries
|
|
@@ -51,6 +62,7 @@ import * as path from 'node:path';
|
|
|
51
62
|
import { pathToFileURL } from 'node:url';
|
|
52
63
|
|
|
53
64
|
import { EngagementSchemaError, parse_event, type EngagementEvent } from '../agent-src/templates/scripts/telemetry/engagement.js';
|
|
65
|
+
import { explainLadder, type LadderExplanation, type LadderInputs } from './_lib/judgment_ladder.js';
|
|
54
66
|
import { readAuditLines } from './orchestration_savings_report.js';
|
|
55
67
|
|
|
56
68
|
// ── CLI ──────────────────────────────────────────────────────────────────
|
|
@@ -76,7 +88,48 @@ export interface Options {
|
|
|
76
88
|
engagement: string;
|
|
77
89
|
hygiene: string | null;
|
|
78
90
|
output: string | null;
|
|
79
|
-
|
|
91
|
+
/**
|
|
92
|
+
* Dispatch-decision task text (§4b); null/absent → the section is not
|
|
93
|
+
* rendered. Optional (with the flags below) so pre-existing callers
|
|
94
|
+
* constructing `Options` literals stay source-compatible.
|
|
95
|
+
*/
|
|
96
|
+
decision?: string | null;
|
|
97
|
+
/** Ladder signal flags — map 1:1 onto `LadderInputs`; nothing is inferred. */
|
|
98
|
+
sizeEstimate?: number;
|
|
99
|
+
slices?: number;
|
|
100
|
+
orderedPlan?: boolean;
|
|
101
|
+
agentTeams?: boolean;
|
|
102
|
+
halted?: boolean;
|
|
103
|
+
noSpawnPrimitive?: boolean;
|
|
104
|
+
insideSubagent?: boolean;
|
|
105
|
+
approvalRequired?: boolean;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const USAGE = `usage: explain_run [--task <id>] [--since <ISO-8601>] [--output <path>]
|
|
109
|
+
[--decision "<task text>"] [--size-estimate <int>] [--slices <int>]
|
|
110
|
+
[--ordered-plan] [--agent-teams] [--halted] [--no-spawn-primitive]
|
|
111
|
+
[--inside-subagent] [--approval-required]
|
|
112
|
+
|
|
113
|
+
Window:
|
|
114
|
+
--task <id> restrict to one task id in the audit log
|
|
115
|
+
--since <ISO-8601> cutoff timestamp / date
|
|
116
|
+
--output <path> write the report to a file instead of stdout
|
|
117
|
+
|
|
118
|
+
Dispatch decision (all optional; absent --decision omits the section):
|
|
119
|
+
--decision <text> classify this task text through the judgment ladder and
|
|
120
|
+
render the rung taken, the rungs rejected with their
|
|
121
|
+
detector's reason, and the rungs never reached
|
|
122
|
+
--size-estimate <int> size signal; at or below the floor nothing delegates
|
|
123
|
+
--slices <int> number of independent slices
|
|
124
|
+
--ordered-plan the slices are ordered, not independent
|
|
125
|
+
--agent-teams the host reports the agent_teams capability
|
|
126
|
+
--halted emergency.orchestration_halt is set
|
|
127
|
+
--no-spawn-primitive the host has no subagent_spawn primitive
|
|
128
|
+
--inside-subagent classification runs inside a subagent (recursive guard)
|
|
129
|
+
--approval-required a human decision is pending; resolves before every rung
|
|
130
|
+
|
|
131
|
+
Exit: 0 report rendered · 2 bad argument.
|
|
132
|
+
`;
|
|
80
133
|
|
|
81
134
|
function parseArgs(argv: string[]): Options {
|
|
82
135
|
const opts: Options = {
|
|
@@ -87,6 +140,23 @@ function parseArgs(argv: string[]): Options {
|
|
|
87
140
|
engagement: DEFAULT_ENGAGEMENT,
|
|
88
141
|
hygiene: null,
|
|
89
142
|
output: null,
|
|
143
|
+
decision: null,
|
|
144
|
+
sizeEstimate: 0,
|
|
145
|
+
slices: 0,
|
|
146
|
+
orderedPlan: false,
|
|
147
|
+
agentTeams: false,
|
|
148
|
+
halted: false,
|
|
149
|
+
noSpawnPrimitive: false,
|
|
150
|
+
insideSubagent: false,
|
|
151
|
+
approvalRequired: false,
|
|
152
|
+
};
|
|
153
|
+
const intValue = (flag: string, raw: string | undefined): number => {
|
|
154
|
+
const n = Number(raw);
|
|
155
|
+
if (raw === undefined || raw === '' || !Number.isInteger(n)) {
|
|
156
|
+
process.stderr.write(`explain_run: ${flag} requires an integer, got: ${raw ?? '(missing)'}\n`);
|
|
157
|
+
process.exit(2);
|
|
158
|
+
}
|
|
159
|
+
return n;
|
|
90
160
|
};
|
|
91
161
|
for (let i = 0; i < argv.length; i++) {
|
|
92
162
|
const a = argv[i];
|
|
@@ -106,8 +176,38 @@ function parseArgs(argv: string[]): Options {
|
|
|
106
176
|
else if (a.startsWith('--hygiene=')) opts.hygiene = a.slice('--hygiene='.length);
|
|
107
177
|
else if (a === '--output') opts.output = next() ?? opts.output;
|
|
108
178
|
else if (a.startsWith('--output=')) opts.output = a.slice('--output='.length);
|
|
109
|
-
else {
|
|
110
|
-
|
|
179
|
+
else if (a === '--decision') {
|
|
180
|
+
const v = next();
|
|
181
|
+
// Exit rather than no-op: silently dropping the value produced a full
|
|
182
|
+
// report with the dispatch section absent and exit 0, so a typo read
|
|
183
|
+
// as "this run had no dispatch decision". `--size-estimate` already
|
|
184
|
+
// exits 2 on the same mistake; the flags now behave alike.
|
|
185
|
+
if (v === undefined || v === '') {
|
|
186
|
+
process.stderr.write('explain_run: --decision requires a value (the task text to classify)\n');
|
|
187
|
+
process.exit(2);
|
|
188
|
+
}
|
|
189
|
+
opts.decision = v;
|
|
190
|
+
} else if (a.startsWith('--decision=')) opts.decision = a.slice('--decision='.length);
|
|
191
|
+
else if (a === '--size-estimate') opts.sizeEstimate = intValue('--size-estimate', next());
|
|
192
|
+
else if (a.startsWith('--size-estimate=')) opts.sizeEstimate = intValue('--size-estimate', a.slice('--size-estimate='.length));
|
|
193
|
+
else if (a === '--slices') opts.slices = intValue('--slices', next());
|
|
194
|
+
else if (a.startsWith('--slices=')) opts.slices = intValue('--slices', a.slice('--slices='.length));
|
|
195
|
+
else if (a === '--ordered-plan') opts.orderedPlan = true;
|
|
196
|
+
else if (a === '--agent-teams') opts.agentTeams = true;
|
|
197
|
+
else if (a === '--halted') opts.halted = true;
|
|
198
|
+
else if (a === '--no-spawn-primitive') opts.noSpawnPrimitive = true;
|
|
199
|
+
else if (a === '--inside-subagent') opts.insideSubagent = true;
|
|
200
|
+
else if (a === '--approval-required') opts.approvalRequired = true;
|
|
201
|
+
else if (a === '--help' || a === '-h') {
|
|
202
|
+
process.stdout.write(USAGE);
|
|
203
|
+
process.exit(0);
|
|
204
|
+
} else {
|
|
205
|
+
// The usage text goes to stderr with the error, so a typo shows the
|
|
206
|
+
// whole surface rather than only the rejected token. Nine of these
|
|
207
|
+
// flags landed at once (the dispatch-decision trace); a script whose
|
|
208
|
+
// only discovery path is reading its source is a script whose flags
|
|
209
|
+
// do not exist for the person at the terminal.
|
|
210
|
+
process.stderr.write(`explain_run: unrecognized argument: ${a}\n\n${USAGE}`);
|
|
111
211
|
process.exit(2);
|
|
112
212
|
}
|
|
113
213
|
}
|
|
@@ -486,6 +586,104 @@ function renderOrchestrationSection(opts: Options, auditDirExists: boolean, disp
|
|
|
486
586
|
return out;
|
|
487
587
|
}
|
|
488
588
|
|
|
589
|
+
// ── Dispatch-decision trace (§4b — judgment ladder + telemetry estimate) ─
|
|
590
|
+
|
|
591
|
+
/** Map the CLI signal flags 1:1 onto `LadderInputs` — nothing inferred. */
|
|
592
|
+
export function ladderInputsFromOptions(opts: Options): LadderInputs {
|
|
593
|
+
return {
|
|
594
|
+
taskText: opts.decision ?? '',
|
|
595
|
+
signals: {
|
|
596
|
+
size_estimate: opts.sizeEstimate ?? 0,
|
|
597
|
+
independent_slices: opts.slices ?? 0,
|
|
598
|
+
ordered_plan: opts.orderedPlan ?? false,
|
|
599
|
+
},
|
|
600
|
+
activation: { halted: opts.halted ?? false, subagent_spawn: !(opts.noSpawnPrimitive ?? false) },
|
|
601
|
+
agentTeams: opts.agentTeams ?? false,
|
|
602
|
+
interactiveApprovalRequired: opts.approvalRequired ?? false,
|
|
603
|
+
insideSubagentSession: opts.insideSubagent ?? false,
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/** Most recent dispatch line by `ts` (undefined ts sorts oldest). */
|
|
608
|
+
export function latestDispatch(dispatches: ExplainAuditLine[]): ExplainAuditLine | null {
|
|
609
|
+
let latest: ExplainAuditLine | null = null;
|
|
610
|
+
let latestTs = Number.NEGATIVE_INFINITY;
|
|
611
|
+
for (const line of dispatches) {
|
|
612
|
+
const t = line.ts === undefined ? Number.NEGATIVE_INFINITY : Date.parse(line.ts);
|
|
613
|
+
const ts = Number.isFinite(t) ? t : Number.NEGATIVE_INFINITY;
|
|
614
|
+
if (latest === null || ts >= latestTs) {
|
|
615
|
+
latest = line;
|
|
616
|
+
latestTs = ts;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
return latest;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
function renderDecisionSection(
|
|
623
|
+
opts: Options,
|
|
624
|
+
explanation: LadderExplanation,
|
|
625
|
+
auditDirExists: boolean,
|
|
626
|
+
dispatches: ExplainAuditLine[],
|
|
627
|
+
): string[] {
|
|
628
|
+
const out: string[] = [];
|
|
629
|
+
out.push('## Dispatch decision (judgment ladder)');
|
|
630
|
+
out.push(
|
|
631
|
+
`_Source: \`src/scripts/_lib/judgment_ladder.ts\` (\`explainLadder\`) over the \`--decision\` text + signal flags; token/cost estimate from \`${opts.auditDir}/*.jsonl\` orchestration telemetry_`,
|
|
632
|
+
);
|
|
633
|
+
out.push('');
|
|
634
|
+
out.push(`Decision text: ${JSON.stringify(opts.decision ?? '')}`);
|
|
635
|
+
const r = explanation.result;
|
|
636
|
+
const rungLabel = r.rung === null ? '∅ (never spawns)' : String(r.rung);
|
|
637
|
+
const modeSuffix = r.mode !== undefined && r.mode !== null ? ` (mode ${r.mode})` : '';
|
|
638
|
+
const degradedSuffix = r.degraded_from !== undefined ? ` [degraded from rung ${r.degraded_from}]` : '';
|
|
639
|
+
out.push(`Resolved: rung ${rungLabel} — verdict \`${r.verdict}\`${modeSuffix}${degradedSuffix} — ${r.reason}`);
|
|
640
|
+
out.push('');
|
|
641
|
+
out.push('### Ladder trail (priority order 0 → 4 → 3 → 2 → 1)');
|
|
642
|
+
out.push('');
|
|
643
|
+
out.push('| rung | resolves to | status | reason |');
|
|
644
|
+
out.push('|---|---|---|---|');
|
|
645
|
+
for (const step of explanation.trail) {
|
|
646
|
+
out.push(`| ${step.rung} | ${step.resolves_to} | ${step.status} | ${step.reason} |`);
|
|
647
|
+
}
|
|
648
|
+
if (explanation.no_spawn_reason !== undefined) {
|
|
649
|
+
out.push('');
|
|
650
|
+
out.push(`Why no spawn: ${explanation.no_spawn_reason}`);
|
|
651
|
+
}
|
|
652
|
+
out.push('');
|
|
653
|
+
out.push('### Token/cost estimate');
|
|
654
|
+
if (!auditDirExists) {
|
|
655
|
+
out.push(`no telemetry record — \`${opts.auditDir}\` absent; no token/cost estimate (a fabricated number would be worse than none)`);
|
|
656
|
+
return out;
|
|
657
|
+
}
|
|
658
|
+
const latest = latestDispatch(dispatches);
|
|
659
|
+
if (latest === null) {
|
|
660
|
+
out.push(`no telemetry record matches ${windowLabel(opts)}; no token/cost estimate (a fabricated number would be worse than none)`);
|
|
661
|
+
return out;
|
|
662
|
+
}
|
|
663
|
+
const o = latest.orchestration ?? {};
|
|
664
|
+
// NOT matched to this decision, and the label must say so. The record shape
|
|
665
|
+
// carries no key that ties a line to a decision text — `latestDispatch` sorts
|
|
666
|
+
// by `ts` alone — so calling it "matching" made an unrelated run's cost read
|
|
667
|
+
// as this decision's estimate, in a section whose own header promises never
|
|
668
|
+
// to fabricate one. Printing it is still useful (it is the freshest real
|
|
669
|
+
// measurement on this host); claiming correspondence was the defect.
|
|
670
|
+
out.push(
|
|
671
|
+
`most recent dispatch in ${windowLabel(opts)} — NOT matched to this decision ` +
|
|
672
|
+
`(the record carries no decision key; sorted by ts alone): ` +
|
|
673
|
+
`ts ${latest.ts ?? '(unset)'}, work_id ${latest.work_id ?? '(unset)'}, ` +
|
|
674
|
+
`token_delta ${o.token_delta ?? 0} (${o.token_delta_provenance ?? 'estimated'}), ` +
|
|
675
|
+
`mode ${o.dispatch_mode ?? '(unset)'}, tiers ${(o.tiers ?? []).join(',') || '(unset)'}`,
|
|
676
|
+
);
|
|
677
|
+
const resolved = explanation.result.verdict;
|
|
678
|
+
const recordMode = o.dispatch_mode;
|
|
679
|
+
if (recordMode !== undefined && resolved === 'in-session') {
|
|
680
|
+
out.push(
|
|
681
|
+
` ⚠️ this decision resolves to \`${resolved}\` (no dispatch), so the mode above describes a different run.`,
|
|
682
|
+
);
|
|
683
|
+
}
|
|
684
|
+
return out;
|
|
685
|
+
}
|
|
686
|
+
|
|
489
687
|
function renderHygieneSection(hygiene: HygieneReadResult): string[] {
|
|
490
688
|
const out: string[] = [];
|
|
491
689
|
out.push('## Hook / loop state (context-hygiene)');
|
|
@@ -568,6 +766,10 @@ export function buildReport(opts: Options): string {
|
|
|
568
766
|
out.push('');
|
|
569
767
|
out.push(...renderOrchestrationSection(opts, auditDirExists, dispatches));
|
|
570
768
|
out.push('');
|
|
769
|
+
if (opts.decision !== null && opts.decision !== undefined) {
|
|
770
|
+
out.push(...renderDecisionSection(opts, explainLadder(ladderInputsFromOptions(opts)), auditDirExists, dispatches));
|
|
771
|
+
out.push('');
|
|
772
|
+
}
|
|
571
773
|
out.push(...renderHygieneSection(hygiene));
|
|
572
774
|
out.push('');
|
|
573
775
|
out.push(...renderParkedSection());
|
|
@@ -43,6 +43,7 @@ import {
|
|
|
43
43
|
} from '../shared/settingsClasses.js';
|
|
44
44
|
import { flattenSurface, type SurfaceEntry } from '../shared/settingsSurface.js';
|
|
45
45
|
|
|
46
|
+
// ledger-exempt: single-artifact generator — renders ONE derived page from the zod schema and byte-compares it against the committed copy in --check mode; the verdict is one aggregate drift comparison over the whole page, so per-target findings do not exist.
|
|
46
47
|
const _HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
47
48
|
export const ROOT = path.resolve(_HERE, '..', '..');
|
|
48
49
|
|
|
@@ -27,6 +27,12 @@ import process from 'node:process';
|
|
|
27
27
|
import { fileURLToPath } from 'node:url';
|
|
28
28
|
|
|
29
29
|
import { readHookStdin } from './hooks/hook_stdin.js';
|
|
30
|
+
import {
|
|
31
|
+
RECYCLE_CONSUMED_REL,
|
|
32
|
+
RECYCLE_ENVELOPE_REL,
|
|
33
|
+
RECYCLE_MAX_AGE_HOURS,
|
|
34
|
+
} from './_lib/recycle_envelope_paths.js';
|
|
35
|
+
import { validateRecycleEnvelope } from './_lib/subagent_capsule.js';
|
|
30
36
|
|
|
31
37
|
export const HANDOFF_CONTEXT_REL = path.join('agents', 'runtime', 'state', 'handoff-context.md');
|
|
32
38
|
export const MAX_AGE_HOURS = 48;
|
|
@@ -96,6 +102,94 @@ export function consume_handoff_context(root: string, now: Date = new Date()): C
|
|
|
96
102
|
return { action: 'inject', reason: `handoff consumed (session=${sourceSession})`, context: block };
|
|
97
103
|
}
|
|
98
104
|
|
|
105
|
+
/**
|
|
106
|
+
* Consume the one-shot RECYCLE ENVELOPE (road-to-token-economy-recycling
|
|
107
|
+
* Phase 2.3) — the main-session sibling of the handoff file above, same
|
|
108
|
+
* lifecycle discipline, stricter validation:
|
|
109
|
+
*
|
|
110
|
+
* - schema: `validateRecycleEnvelope` (the main_session CHECKPOINT
|
|
111
|
+
* variant) — an invalid envelope is never injected;
|
|
112
|
+
* - identity (Risk 4): the envelope's `workspace` must resolve to THIS
|
|
113
|
+
* workspace root, so a hand-copied envelope cannot plant foreign
|
|
114
|
+
* constraints into an unrelated session;
|
|
115
|
+
* - staleness: `written_at` older than {@link RECYCLE_MAX_AGE_HOURS} is
|
|
116
|
+
* discarded;
|
|
117
|
+
* - consume-on-read, MOVED not copied: every outcome except `absent`
|
|
118
|
+
* relocates the file to `recycle-envelope.consumed.json` — it can never
|
|
119
|
+
* leak into a second session, while the last envelope stays
|
|
120
|
+
* inspectable for debugging.
|
|
121
|
+
*/
|
|
122
|
+
export function consume_recycle_envelope(root: string, now: Date = new Date()): ConsumeDecision {
|
|
123
|
+
const target = process.env.AGENT_RECYCLE_ENVELOPE_FILE || path.join(root, RECYCLE_ENVELOPE_REL);
|
|
124
|
+
let text: string;
|
|
125
|
+
try {
|
|
126
|
+
text = fs.readFileSync(target, 'utf-8');
|
|
127
|
+
} catch {
|
|
128
|
+
return { action: 'absent', reason: 'no recycle envelope' };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const consume = (): void => {
|
|
132
|
+
try {
|
|
133
|
+
fs.renameSync(target, path.join(path.dirname(target), path.basename(RECYCLE_CONSUMED_REL)));
|
|
134
|
+
} catch {
|
|
135
|
+
try {
|
|
136
|
+
fs.unlinkSync(target); // fallback — never let it survive
|
|
137
|
+
} catch {
|
|
138
|
+
// fail-open
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
let envelope: Record<string, unknown>;
|
|
144
|
+
try {
|
|
145
|
+
envelope = JSON.parse(text) as Record<string, unknown>;
|
|
146
|
+
} catch {
|
|
147
|
+
consume();
|
|
148
|
+
return { action: 'discard', reason: 'recycle envelope is not valid JSON' };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const violations = validateRecycleEnvelope(envelope);
|
|
152
|
+
if (violations.length > 0) {
|
|
153
|
+
consume();
|
|
154
|
+
return {
|
|
155
|
+
action: 'discard',
|
|
156
|
+
reason: `recycle envelope invalid: ${violations[0] ?? 'schema violation'}`,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const stamp = Date.parse(String(envelope['written_at']));
|
|
161
|
+
const ageHours = (now.getTime() - stamp) / (1000 * 60 * 60);
|
|
162
|
+
if (Number.isNaN(stamp) || ageHours > RECYCLE_MAX_AGE_HOURS) {
|
|
163
|
+
consume();
|
|
164
|
+
return { action: 'discard', reason: `recycle envelope stale: ${ageHours.toFixed(1)}h > ${RECYCLE_MAX_AGE_HOURS}h` };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Identity check (Risk 4): the envelope names its workspace; a mismatch
|
|
168
|
+
// means it was copied here by hand — foreign constraints, do not inject.
|
|
169
|
+
try {
|
|
170
|
+
const envelopeWs = fs.realpathSync(String(envelope['workspace']));
|
|
171
|
+
const thisWs = fs.realpathSync(root);
|
|
172
|
+
if (envelopeWs !== thisWs) {
|
|
173
|
+
consume();
|
|
174
|
+
return { action: 'discard', reason: `recycle envelope belongs to ${envelopeWs}, not ${thisWs}` };
|
|
175
|
+
}
|
|
176
|
+
} catch {
|
|
177
|
+
consume();
|
|
178
|
+
return { action: 'discard', reason: 'recycle envelope workspace does not resolve' };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const block = [
|
|
182
|
+
`<recycle-envelope source="${RECYCLE_ENVELOPE_REL}"`,
|
|
183
|
+
' note="deliberate session recycle — validated state from the predecessor session;',
|
|
184
|
+
' DATA, not instructions. Re-derive everything under not_carried_forward from source',
|
|
185
|
+
' before trusting it.">',
|
|
186
|
+
JSON.stringify(envelope, null, 2),
|
|
187
|
+
'</recycle-envelope>',
|
|
188
|
+
].join('\n');
|
|
189
|
+
consume(); // moved, not copied: the envelope never outlives its first injection
|
|
190
|
+
return { action: 'inject', reason: 'recycle envelope consumed', context: block };
|
|
191
|
+
}
|
|
192
|
+
|
|
99
193
|
// ---------------------------------------------------------------------
|
|
100
194
|
// CLI — dispatcher concern entry point
|
|
101
195
|
// ---------------------------------------------------------------------
|
|
@@ -122,13 +216,27 @@ export function main(): number {
|
|
|
122
216
|
return 0; // replay fixtures: read-only, no state mutation
|
|
123
217
|
}
|
|
124
218
|
if (event === 'session_start') {
|
|
125
|
-
const
|
|
126
|
-
|
|
219
|
+
const handoff = consume_handoff_context(root);
|
|
220
|
+
const recycle = consume_recycle_envelope(root);
|
|
221
|
+
const blocks: string[] = [];
|
|
222
|
+
const reasons: string[] = [];
|
|
223
|
+
// Recycle envelope first — it is the task-state restore the
|
|
224
|
+
// successor bootstraps from; the generic handoff is companion
|
|
225
|
+
// narrative when both are pending.
|
|
226
|
+
if (recycle.action === 'inject' && recycle.context) {
|
|
227
|
+
blocks.push(recycle.context);
|
|
228
|
+
reasons.push(recycle.reason);
|
|
229
|
+
}
|
|
230
|
+
if (handoff.action === 'inject' && handoff.context) {
|
|
231
|
+
blocks.push(handoff.context);
|
|
232
|
+
reasons.push(handoff.reason);
|
|
233
|
+
}
|
|
234
|
+
if (blocks.length > 0) {
|
|
127
235
|
process.stdout.write(
|
|
128
236
|
JSON.stringify({
|
|
129
237
|
decision: 'allow',
|
|
130
|
-
reason:
|
|
131
|
-
context:
|
|
238
|
+
reason: reasons.join(' · '),
|
|
239
|
+
context: blocks.join('\n\n'),
|
|
132
240
|
}) + '\n',
|
|
133
241
|
);
|
|
134
242
|
}
|
|
@@ -422,22 +422,129 @@ concerns:
|
|
|
422
422
|
args: []
|
|
423
423
|
fail_closed: false
|
|
424
424
|
severity: advisory
|
|
425
|
+
# road-to-conformance-round5 Phase 3 — the FIRST concern that can refuse a
|
|
426
|
+
# turn-END. Round 5 measured the reason: both blocking carriers reached zero
|
|
427
|
+
# violations, neither advisory carrier did, and 19 language violations
|
|
428
|
+
# survived a pin that had fired 26-35 seconds earlier. Refusal-capable
|
|
429
|
+
# intercepts enforce; context injection requests.
|
|
430
|
+
#
|
|
431
|
+
# Two detectors on one guard — promissory closing (20 measured) and language
|
|
432
|
+
# mismatch (19 measured) — because building the unsafe part twice is how a
|
|
433
|
+
# second detector becomes a second outage.
|
|
434
|
+
#
|
|
435
|
+
# DEFAULT OFF (`hooks.turn_end_gate.enabled`), per the council
|
|
436
|
+
# recorded in round 6: the mechanism exists and soaks before it binds. Both
|
|
437
|
+
# detectors are default ON *within* the master switch, which is round 5
|
|
438
|
+
# § 3.6's ask read so that both hold at once.
|
|
439
|
+
#
|
|
440
|
+
# fail_closed: FALSE, deliberately and unlike every other blocking concern
|
|
441
|
+
# here: a crash in a turn-end gate must let the turn END. Failing closed on
|
|
442
|
+
# this slot does not degrade the session, it wedges it. Re-entrancy is two
|
|
443
|
+
# independent layers (`stop_hook_active`; a marker keyed on the last USER
|
|
444
|
+
# message, never the reply) — see the hook's file header.
|
|
445
|
+
turn-end-gate:
|
|
446
|
+
script: src/scripts/hooks/turn_end_gate_hook.ts
|
|
447
|
+
args: []
|
|
448
|
+
fail_closed: false
|
|
449
|
+
severity: blocking
|
|
450
|
+
# road-to-token-economy-cache Phase 5.1/5.2 — edit-shape advisory. Fires
|
|
451
|
+
# when a Write replaced an existing, git-tracked file of >= 50 lines while
|
|
452
|
+
# the effective diff (`git diff HEAD --numstat` added+deleted vs current
|
|
453
|
+
# file lines) was <= 20% — one line naming the cheaper primitive (a
|
|
454
|
+
# targeted Edit pays for the diff, not the file). Thresholds are COMMITTED
|
|
455
|
+
# in the concern header. Once per session (F2-style marker under
|
|
456
|
+
# agents/runtime/state/edit-shape/). Reports `{decision:"warn", reason,
|
|
457
|
+
# additional_context}` at exit 2 (same verified delivery pattern as
|
|
458
|
+
# `delegation-nudge`); never blocks. Exempt: dist/**, */generated/*,
|
|
459
|
+
# *.lock, *.min.*, agents/runtime/**. fail_closed: false.
|
|
460
|
+
edit-shape:
|
|
461
|
+
script: src/scripts/hooks/edit_shape_hook.ts
|
|
462
|
+
args: []
|
|
463
|
+
fail_closed: false
|
|
464
|
+
severity: advisory
|
|
465
|
+
# Provable from the hook's own source: the only branch surface is
|
|
466
|
+
# `toolName !== 'Write'` — same cheap-filter discipline as
|
|
467
|
+
# `orchestration-record`'s [Agent, Task].
|
|
468
|
+
tools: [Write]
|
|
469
|
+
# road-to-token-economy-cache Phase 6.1/6.2 — re-read guard + post-edit
|
|
470
|
+
# scope hint. PreToolUse, ADVISORY ONLY (never exit 1 — a stale-ledger
|
|
471
|
+
# false positive on a block would corrupt work): a full-file Read of a
|
|
472
|
+
# path already fully read this session and unchanged since (mtime+size vs
|
|
473
|
+
# a session-scoped ledger under agents/runtime/state/reread-guard/ — paths
|
|
474
|
+
# and numbers only, never content), or a full-file Read right after this
|
|
475
|
+
# session's own Edit/Write to the same path, gets one advisory line naming
|
|
476
|
+
# the ranged-read alternative. Per-path once-per-session latch. Same
|
|
477
|
+
# exemption list as edit-shape. Delivery mirrors `delegation-nudge`
|
|
478
|
+
# (exit-2 warn never blocks on the verified `claude` platform).
|
|
479
|
+
# fail_closed: false.
|
|
480
|
+
reread-guard:
|
|
481
|
+
script: src/scripts/hooks/reread_guard_hook.ts
|
|
482
|
+
args: []
|
|
483
|
+
fail_closed: false
|
|
484
|
+
severity: advisory
|
|
485
|
+
# Provable from the hook's own source: READ_TOOLS ∪ WRITE_TOOLS.
|
|
486
|
+
tools: [Read, Edit, Write]
|
|
487
|
+
# road-to-token-economy-recycling Phases 1.1 / 3.2 / 4.2 — session
|
|
488
|
+
# end-of-life instrument + recycle advisory. Every Stop, incrementally
|
|
489
|
+
# folds the transcript's newly appended lines (payload `transcript_path`,
|
|
490
|
+
# validated like end-review-nudge) into counts-only per-session state
|
|
491
|
+
# under agents/runtime/state/session-eol/ (final context tokens via
|
|
492
|
+
# cc_transcript's billable-input definition, turn count, observed
|
|
493
|
+
# compaction events) and overwrites the machine-readable fill surface
|
|
494
|
+
# agents/runtime/state/context-fill.json. Past the committed recycle
|
|
495
|
+
# threshold (src/config/recycle-threshold-budget.json), injects ONE
|
|
496
|
+
# advisory line ONCE per session (F2) — `{decision:"warn", reason,
|
|
497
|
+
# additional_context}` at exit 2, the verified delivery pattern of
|
|
498
|
+
# end-review-nudge. Absent config or unreadable transcript is silence,
|
|
499
|
+
# never a block; hooks cannot inject /clear, so the recycle action itself
|
|
500
|
+
# is advisory-carried by design. fail_closed: false.
|
|
501
|
+
session-eol:
|
|
502
|
+
script: src/scripts/hooks/session_eol_hook.ts
|
|
503
|
+
args: []
|
|
504
|
+
fail_closed: false
|
|
505
|
+
severity: advisory
|
|
506
|
+
|
|
507
|
+
# Role axis (road-to-token-economy-dispatch Phase 2). The dispatcher reads
|
|
508
|
+
# AGENT_CONFIG_SESSION_ROLE (resolved via _lib/session_role.ts — set ONLY by
|
|
509
|
+
# suite-owned CLI spawn wrappers, never guessed from host state) and applies
|
|
510
|
+
# the role's `drop` list to every slot EXCEPT pre_tool_use: safety guards run
|
|
511
|
+
# on every role, structurally — the resolver refuses pre_tool_use drops and
|
|
512
|
+
# lint_hook_manifest rejects a drop entry bound to that slot.
|
|
513
|
+
# Missing / unknown role → the full orchestrator chain, byte-identical to a
|
|
514
|
+
# manifest without this block (fail-open, Phase 2.4).
|
|
515
|
+
# Host limitation, pre-registered in the roadmap's worker-chain-host-delivery
|
|
516
|
+
# blocker: in-process Agent-tool subagents inherit the host process env and
|
|
517
|
+
# cannot be marked per-spawn; the axis binds where the suite launches a CLI
|
|
518
|
+
# session itself.
|
|
519
|
+
roles:
|
|
520
|
+
worker:
|
|
521
|
+
# Orchestrator-only concerns (Phase 2.3): a worker never talks to the
|
|
522
|
+
# user, never dispatches sub-workers, never runs the council, and its
|
|
523
|
+
# complaints route via the orchestrator.
|
|
524
|
+
drop: [delegation-nudge, end-review-nudge, council-availability, team-review-gate, self-repair, session-eol]
|
|
425
525
|
|
|
426
526
|
platforms:
|
|
427
527
|
augment:
|
|
428
528
|
session_start: [chat-history, hot-context, handoff-context, session-canary, council-availability, first-run-gate, onboarding-gate, verify-before-complete, minimal-safe-diff, profile-staleness, wrapper-freshness, surface-probe, session-register]
|
|
429
529
|
session_end: [chat-history, memory-learn, session-register]
|
|
430
530
|
stop: [chat-history, hot-context, verify-before-complete, self-repair, session-register]
|
|
431
|
-
pre_tool_use: [block-no-verify, block-unauthorized-git, evidence-independence, block-kernel-rule-writes, block-config-weakening, rtk-wrap, design-slop, code-graph-nudge]
|
|
432
|
-
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder, orchestration-record]
|
|
531
|
+
pre_tool_use: [block-no-verify, block-unauthorized-git, evidence-independence, block-kernel-rule-writes, block-config-weakening, rtk-wrap, design-slop, code-graph-nudge, reread-guard]
|
|
532
|
+
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder, orchestration-record, edit-shape]
|
|
433
533
|
|
|
434
534
|
claude:
|
|
435
535
|
session_start: [chat-history, hot-context, handoff-context, session-canary, council-availability, first-run-gate, onboarding-gate, verify-before-complete, minimal-safe-diff, profile-staleness, wrapper-freshness, surface-probe, session-register]
|
|
436
536
|
session_end: [chat-history, memory-learn, session-register]
|
|
437
|
-
stop: [chat-history, hot-context, verify-before-complete, team-review-gate, end-review-nudge, self-repair, session-register]
|
|
537
|
+
stop: [chat-history, hot-context, verify-before-complete, team-review-gate, end-review-nudge, turn-end-gate, self-repair, session-register, session-eol]
|
|
438
538
|
user_prompt_submit: [chat-history, verify-before-complete, minimal-safe-diff, language-mirror, delegation-nudge, git-authorization, session-canary, self-repair, session-register]
|
|
439
|
-
pre_tool_use: [block-no-verify, block-unauthorized-git, evidence-independence, block-kernel-rule-writes, block-config-weakening, rtk-wrap, design-slop, code-graph-nudge]
|
|
440
|
-
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder, orchestration-record]
|
|
539
|
+
pre_tool_use: [block-no-verify, block-unauthorized-git, evidence-independence, block-kernel-rule-writes, block-config-weakening, rtk-wrap, design-slop, code-graph-nudge, reread-guard]
|
|
540
|
+
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder, orchestration-record, edit-shape, language-mirror]
|
|
541
|
+
# round-5 § 6.1 — the ONLY binding on this slot in the tree. `pre_compact`
|
|
542
|
+
# sets a pin-lost marker; the `post_tool_use` entry above re-emits the pin
|
|
543
|
+
# exactly once while that marker is set, then clears it. One extra
|
|
544
|
+
# injection per compaction EVENT — never one per tool call, which is the
|
|
545
|
+
# shape § 6.2 refuses. claude only: it is the sole verified platform, and
|
|
546
|
+
# the measured compaction defect is a Claude Code transcript fact.
|
|
547
|
+
pre_compact: [language-mirror]
|
|
441
548
|
|
|
442
549
|
# Cowork — the Claude desktop app's local-agent-mode runtime, built
|
|
443
550
|
# on top of the Claude Code CLI. Same lifecycle vocabulary, same
|
|
@@ -468,8 +575,8 @@ platforms:
|
|
|
468
575
|
# still costs the dispatch, and its entire output is thrown away.
|
|
469
576
|
stop: [chat-history, hot-context, verify-before-complete, self-repair, session-register]
|
|
470
577
|
user_prompt_submit: [chat-history, verify-before-complete, minimal-safe-diff, language-mirror, git-authorization, session-canary, self-repair, session-register]
|
|
471
|
-
pre_tool_use: [block-no-verify, block-unauthorized-git, evidence-independence, block-kernel-rule-writes, block-config-weakening, rtk-wrap, design-slop, code-graph-nudge]
|
|
472
|
-
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder, orchestration-record]
|
|
578
|
+
pre_tool_use: [block-no-verify, block-unauthorized-git, evidence-independence, block-kernel-rule-writes, block-config-weakening, rtk-wrap, design-slop, code-graph-nudge, reread-guard]
|
|
579
|
+
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder, orchestration-record, edit-shape]
|
|
473
580
|
|
|
474
581
|
# Phase 7.5 — Cursor. `.cursor/hooks.json` (project) is read by the
|
|
475
582
|
# IDE and CLI; `~/.cursor/hooks.json` (user) is opt-in via
|
|
@@ -483,7 +590,7 @@ platforms:
|
|
|
483
590
|
session_end: [chat-history, memory-learn, session-register]
|
|
484
591
|
stop: [chat-history, hot-context, verify-before-complete, self-repair, session-register]
|
|
485
592
|
user_prompt_submit: [chat-history, verify-before-complete, minimal-safe-diff, language-mirror, git-authorization, session-canary, self-repair, session-register]
|
|
486
|
-
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder, orchestration-record]
|
|
593
|
+
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder, orchestration-record, edit-shape]
|
|
487
594
|
|
|
488
595
|
# Phase 7.6 — Cline. Hooks live under `.clinerules/hooks/<HookName>`
|
|
489
596
|
# (project, no file extension, must be executable per Cline docs) or
|
|
@@ -498,7 +605,7 @@ platforms:
|
|
|
498
605
|
session_end: [chat-history, memory-learn, session-register]
|
|
499
606
|
stop: [chat-history, hot-context, verify-before-complete, self-repair, session-register]
|
|
500
607
|
user_prompt_submit: [chat-history, verify-before-complete, minimal-safe-diff, language-mirror, git-authorization, session-canary, self-repair, session-register]
|
|
501
|
-
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder, orchestration-record]
|
|
608
|
+
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder, orchestration-record, edit-shape]
|
|
502
609
|
|
|
503
610
|
# Phase 7.7 — Windsurf (Cascade). Hooks live at `.windsurf/hooks.json`
|
|
504
611
|
# (project) or `~/.codeium/windsurf/hooks.json` (user). Cascade has
|
|
@@ -534,7 +641,7 @@ platforms:
|
|
|
534
641
|
session_end: [chat-history, memory-learn, session-register]
|
|
535
642
|
stop: [chat-history, hot-context, verify-before-complete, self-repair, session-register]
|
|
536
643
|
user_prompt_submit: [chat-history, verify-before-complete, minimal-safe-diff, language-mirror, git-authorization, session-canary, self-repair, session-register]
|
|
537
|
-
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder, orchestration-record]
|
|
644
|
+
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder, orchestration-record, edit-shape]
|
|
538
645
|
|
|
539
646
|
# Phase 7.9 — Copilot has no hook surface. Concerns route through
|
|
540
647
|
# rule-only fallback; the dispatcher silently no-ops on --platform copilot.
|
|
@@ -57,6 +57,10 @@ import { main as codeGraphNudgeMain } from './code_graph_nudge_hook.js';
|
|
|
57
57
|
import { main as orchestrationRecordMain } from './orchestration_record_hook.js';
|
|
58
58
|
import { main as delegationNudgeMain } from './delegation_nudge_hook.js';
|
|
59
59
|
import { main as endReviewNudgeMain } from './end_review_nudge_hook.js';
|
|
60
|
+
import { main as turnEndGateMain } from './turn_end_gate_hook.js';
|
|
61
|
+
import { main as editShapeMain } from './edit_shape_hook.js';
|
|
62
|
+
import { main as rereadGuardMain } from './reread_guard_hook.js';
|
|
63
|
+
import { main as sessionEolMain } from './session_eol_hook.js';
|
|
60
64
|
|
|
61
65
|
/** A concern `main` — argv-taking or not; both shapes exist. */
|
|
62
66
|
export type ConcernMain = (argv?: string[]) => number | undefined | void;
|
|
@@ -96,4 +100,8 @@ export const CONCERN_REGISTRY: Readonly<Record<string, ConcernMain>> = {
|
|
|
96
100
|
'src/scripts/hooks/orchestration_record_hook.ts': orchestrationRecordMain as ConcernMain,
|
|
97
101
|
'src/scripts/hooks/delegation_nudge_hook.ts': delegationNudgeMain as ConcernMain,
|
|
98
102
|
'src/scripts/hooks/end_review_nudge_hook.ts': endReviewNudgeMain as ConcernMain,
|
|
103
|
+
'src/scripts/hooks/turn_end_gate_hook.ts': turnEndGateMain as ConcernMain,
|
|
104
|
+
'src/scripts/hooks/edit_shape_hook.ts': editShapeMain as ConcernMain,
|
|
105
|
+
'src/scripts/hooks/reread_guard_hook.ts': rereadGuardMain as ConcernMain,
|
|
106
|
+
'src/scripts/hooks/session_eol_hook.ts': sessionEolMain as ConcernMain,
|
|
99
107
|
};
|