@event4u/agent-config 9.31.0 → 9.32.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 +51 -0
- package/dist/agent-src/contexts/execution/host-capability-manifest.md +32 -1
- package/dist/agent-src/rules/delegation-policy.md +11 -1
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/scripts/work_engine/delivery_state.ts +6 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/_self_fix.ts +332 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/refine.ts +106 -1
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/test.ts +50 -15
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +49 -15
- package/dist/agent-src/templates/scripts/work_engine/state.ts +74 -0
- package/dist/agent-src/templates/scripts/work_engine/state_io.ts +6 -0
- package/dist/cli-delegate/chunk-2WELHO3P.js +582 -0
- package/dist/cli-delegate/{chunk-2WCLVDWC.js → chunk-542DZR4P.js} +117 -147
- package/dist/cli-delegate/chunk-PZWRPRWP.js +38 -0
- package/dist/cli-delegate/cmd_conformance.js +81 -30
- package/dist/cli-delegate/cmd_doctor.js +2 -1
- package/dist/cli-delegate/cmd_handoff.js +166 -57
- package/dist/cli-delegate/cmd_session_recycle.js +17 -2
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +3 -3
- 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 +1118 -690
- package/dist/mcp/registry-manifest.json +1 -1
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +79 -29
- package/package.json +1 -1
- package/src/agent-src/templates/scripts/work_engine/delivery_state.ts +6 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/_self_fix.ts +332 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/refine.ts +106 -1
- package/src/agent-src/templates/scripts/work_engine/directives/backend/test.ts +50 -15
- package/src/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +49 -15
- package/src/agent-src/templates/scripts/work_engine/state.ts +74 -0
- package/src/agent-src/templates/scripts/work_engine/state_io.ts +6 -0
- package/src/scripts/_cli/cmd_session_recycle.ts +25 -0
- package/src/scripts/_cli/handoff_generate.ts +36 -1
- package/src/scripts/_cli/handoff_sessions.ts +140 -9
- package/src/scripts/_lib/carrier_divergence.ts +98 -27
- package/src/scripts/_lib/envelope_grounding.ts +206 -0
- package/src/scripts/_lib/host_capability.ts +79 -0
- package/src/scripts/_lib/session_eol.ts +85 -0
- package/src/scripts/_lib/subagent_capsule.ts +256 -1
- package/src/scripts/check_secret_leak.test.ts +43 -0
- package/src/scripts/check_secret_leak.ts +12 -0
- package/src/scripts/handoff_context_hook.ts +66 -18
- package/src/scripts/hooks/session_eol_hook.ts +12 -12
- package/src/scripts/report_carrier_divergence.ts +94 -29
- package/src/scripts/report_conformance_funnel.ts +11 -4
- package/src/scripts/routing_doctor.ts +25 -2
- package/src/scripts/schemas/dod.schema.json +45 -0
- package/dist/cli-delegate/chunk-RVGBH4XK.js +0 -142
|
@@ -52,13 +52,25 @@
|
|
|
52
52
|
* reader acting on that would have believed the host resolves
|
|
53
53
|
* something it does not.
|
|
54
54
|
* frontmatter-only the prose is byte-identical and only the metadata block
|
|
55
|
-
* differs.
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
55
|
+
* differs. Split out because it was, measured, the ENTIRE
|
|
56
|
+
* content of the `body-diff` class: 109 of 109 on 2026-08-10.
|
|
57
|
+
* Left inside body-diff it manufactured the report's only
|
|
58
|
+
* actionable class out of a metadata difference — the same
|
|
59
|
+
* mistake the `unreadable` branch below already refuses to
|
|
60
|
+
* make.
|
|
61
|
+
* NOT inert as a whole, and an earlier version of this entry
|
|
62
|
+
* wrongly said it was: the `paths:` SUBSET below is reported
|
|
63
|
+
* separately and IS actionable. Only the remainder — keys this
|
|
64
|
+
* host does not read — is safe to ignore.
|
|
65
|
+
* `paths:` subset a `frontmatter-only` pair whose two copies disagree about
|
|
66
|
+
* `paths`, the one frontmatter key the host reads. It decides
|
|
67
|
+
* WHEN a rule loads (matching-file read and no /compact
|
|
68
|
+
* re-injection, versus unconditionally at launch), so the copy
|
|
69
|
+
* without `paths` DEFEATS the other's scoping and an
|
|
70
|
+
* obligation someone deliberately scoped is silently unscoped.
|
|
71
|
+
* Measured 2026-08-10: 24 of the 109, all of them `paths` in
|
|
72
|
+
* the project copy and none in the global one. Identical prose
|
|
73
|
+
* does not make a schedule difference inert.
|
|
62
74
|
* provenance-only identical after removing the two installer ownership keys.
|
|
63
75
|
* Structural, expected, and decided against closing —
|
|
64
76
|
* `agents/settings/contexts/dedup-reachability-refusal.md`.
|
|
@@ -83,7 +95,13 @@ import * as path from 'node:path';
|
|
|
83
95
|
import process from 'node:process';
|
|
84
96
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
85
97
|
|
|
86
|
-
import {
|
|
98
|
+
import {
|
|
99
|
+
comparePair,
|
|
100
|
+
frontmatterPaths,
|
|
101
|
+
type PairVerdict,
|
|
102
|
+
proseEqual,
|
|
103
|
+
splitFrontmatter,
|
|
104
|
+
} from './_lib/carrier_divergence.js';
|
|
87
105
|
|
|
88
106
|
const _HERE = fileURLToPath(import.meta.url);
|
|
89
107
|
const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
@@ -124,6 +142,13 @@ export interface CarrierDivergence {
|
|
|
124
142
|
* something a reader has to act on.
|
|
125
143
|
*/
|
|
126
144
|
frontmatterOnly: string[];
|
|
145
|
+
/**
|
|
146
|
+
* A SUBSET of `frontmatterOnly` whose two copies disagree about `paths:` —
|
|
147
|
+
* the one frontmatter key this host reads. Same prose, different load
|
|
148
|
+
* schedule, so this one IS actionable and must not be reported as inert
|
|
149
|
+
* metadata.
|
|
150
|
+
*/
|
|
151
|
+
pathsScopeDiff: string[];
|
|
127
152
|
bodyDiff: string[];
|
|
128
153
|
projectOnly: string[];
|
|
129
154
|
globalOnly: string[];
|
|
@@ -152,7 +177,17 @@ function _listRules(dir: string): string[] {
|
|
|
152
177
|
}
|
|
153
178
|
}
|
|
154
179
|
|
|
155
|
-
/**
|
|
180
|
+
/**
|
|
181
|
+
* `type:` from a rule's frontmatter, or `''` when unreadable / absent.
|
|
182
|
+
*
|
|
183
|
+
* Reads the shared fence parser rather than re-implementing one. It used to
|
|
184
|
+
* carry its own, and the two disagreed on an unterminated fence: this one
|
|
185
|
+
* treated the WHOLE file as frontmatter, so a `type:` line anywhere in the prose
|
|
186
|
+
* could be read as the rule's type. Now an unterminated fence yields no
|
|
187
|
+
* frontmatter and therefore no type — the conservative answer, since attributing
|
|
188
|
+
* a rule to the ADR-004 manual filter on a malformed read would hide it from the
|
|
189
|
+
* drift list.
|
|
190
|
+
*/
|
|
156
191
|
function _ruleType(file: string): string {
|
|
157
192
|
let text: string;
|
|
158
193
|
try {
|
|
@@ -160,10 +195,7 @@ function _ruleType(file: string): string {
|
|
|
160
195
|
} catch {
|
|
161
196
|
return '';
|
|
162
197
|
}
|
|
163
|
-
|
|
164
|
-
const end = text.indexOf('\n---', 3);
|
|
165
|
-
const fm = end === -1 ? text : text.slice(0, end);
|
|
166
|
-
const m = /^type:\s*"?([a-z]+)"?/m.exec(fm);
|
|
198
|
+
const m = /^type:\s*"?([a-z]+)"?/m.exec(splitFrontmatter(text).frontmatter);
|
|
167
199
|
return m === null ? '' : (m[1] as string);
|
|
168
200
|
}
|
|
169
201
|
|
|
@@ -193,6 +225,7 @@ export function compareCarriers(
|
|
|
193
225
|
identical: [],
|
|
194
226
|
provenanceOnly: [],
|
|
195
227
|
frontmatterOnly: [],
|
|
228
|
+
pathsScopeDiff: [],
|
|
196
229
|
bodyDiff: [],
|
|
197
230
|
projectOnly: [],
|
|
198
231
|
globalOnly: [],
|
|
@@ -218,12 +251,12 @@ export function compareCarriers(
|
|
|
218
251
|
}
|
|
219
252
|
out.shared += 1;
|
|
220
253
|
let verdict: PairVerdict;
|
|
221
|
-
let
|
|
254
|
+
let projectCopy: Buffer;
|
|
255
|
+
let globalCopy: Buffer;
|
|
222
256
|
try {
|
|
223
|
-
|
|
224
|
-
|
|
257
|
+
projectCopy = fs.readFileSync(path.join(projectDir, rule));
|
|
258
|
+
globalCopy = fs.readFileSync(path.join(globalDir, rule));
|
|
225
259
|
verdict = comparePair(projectCopy, globalCopy);
|
|
226
|
-
proseSame = proseEqual(projectCopy.toString('utf-8'), globalCopy.toString('utf-8'));
|
|
227
260
|
} catch {
|
|
228
261
|
// An unreadable copy is not a body difference and must not be
|
|
229
262
|
// reported as one — a permission error would otherwise manufacture
|
|
@@ -236,8 +269,23 @@ export function compareCarriers(
|
|
|
236
269
|
}
|
|
237
270
|
if (verdict === 'identical') out.identical.push(rule);
|
|
238
271
|
else if (verdict === 'provenance-only') out.provenanceOnly.push(rule);
|
|
239
|
-
else
|
|
240
|
-
|
|
272
|
+
else {
|
|
273
|
+
// Only this branch consumes the prose comparison, so it is computed
|
|
274
|
+
// here rather than beside `comparePair`: on an `identical` or
|
|
275
|
+
// `provenance-only` pair the two `toString` conversions and two fence
|
|
276
|
+
// scans are work whose result is never read.
|
|
277
|
+
const projectText = projectCopy.toString('utf-8');
|
|
278
|
+
const globalText = globalCopy.toString('utf-8');
|
|
279
|
+
if (proseEqual(projectText, globalText)) {
|
|
280
|
+
out.frontmatterOnly.push(rule);
|
|
281
|
+
// `paths:` is the one key the host reads, so a disagreement here
|
|
282
|
+
// is a load-schedule difference, not inert metadata. Recorded as
|
|
283
|
+
// a subset so the count stays reconcilable with `frontmatterOnly`.
|
|
284
|
+
if (frontmatterPaths(projectText) !== frontmatterPaths(globalText)) {
|
|
285
|
+
out.pathsScopeDiff.push(rule);
|
|
286
|
+
}
|
|
287
|
+
} else out.bodyDiff.push(rule);
|
|
288
|
+
}
|
|
241
289
|
}
|
|
242
290
|
return out;
|
|
243
291
|
}
|
|
@@ -269,6 +317,7 @@ export function render(d: CarrierDivergence): string {
|
|
|
269
317
|
lines.push(` byte-identical ${d.identical.length}`);
|
|
270
318
|
lines.push(` differ ONLY in the install stamp ${d.provenanceOnly.length}`);
|
|
271
319
|
lines.push(` differ ONLY in frontmatter ${d.frontmatterOnly.length}`);
|
|
320
|
+
lines.push(` of which disagree on \`paths:\` ${d.pathsScopeDiff.length} ← ACTIONABLE`);
|
|
272
321
|
lines.push(` differ in PROSE ${d.bodyDiff.length}`);
|
|
273
322
|
if (d.unreadable.length > 0) {
|
|
274
323
|
lines.push(` unreadable on one side ${d.unreadable.length} (${d.unreadable.join(', ')})`);
|
|
@@ -295,16 +344,28 @@ export function render(d: CarrierDivergence): string {
|
|
|
295
344
|
lines.push('');
|
|
296
345
|
}
|
|
297
346
|
|
|
298
|
-
if (d.
|
|
299
|
-
lines.push(` ${String(d.
|
|
300
|
-
lines.push('
|
|
301
|
-
lines.push('
|
|
302
|
-
lines.push('
|
|
303
|
-
lines.push('
|
|
304
|
-
lines.push('
|
|
305
|
-
lines.push('
|
|
306
|
-
lines.push('
|
|
307
|
-
|
|
347
|
+
if (d.pathsScopeDiff.length > 0) {
|
|
348
|
+
lines.push(` \`paths:\` SCOPE DISAGREEMENT (${String(d.pathsScopeDiff.length)}) — same prose, different load schedule.`);
|
|
349
|
+
lines.push(' ACT ON THESE. `paths` is the one frontmatter key this host reads: with it, a');
|
|
350
|
+
lines.push(' rule fires when a matching file is read and is NOT re-injected after');
|
|
351
|
+
lines.push(' /compact; without it, the rule loads unconditionally at launch. So the copy');
|
|
352
|
+
lines.push(' that lacks `paths` DEFEATS the other copy\'s scoping — the rule the project');
|
|
353
|
+
lines.push(' layer meant to deliver conditionally is delivered always, and an obligation');
|
|
354
|
+
lines.push(' a maintainer deliberately scoped is silently unscoped on any machine that');
|
|
355
|
+
lines.push(' carries both. Identical prose does not make this inert.');
|
|
356
|
+
for (const r of d.pathsScopeDiff) lines.push(` - ${r}`);
|
|
357
|
+
lines.push('');
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const inert = d.frontmatterOnly.length - d.pathsScopeDiff.length;
|
|
361
|
+
if (inert > 0) {
|
|
362
|
+
lines.push(` ${String(inert)} further pair(s) differ ONLY in frontmatter the host does not read —`);
|
|
363
|
+
lines.push(' prose byte-identical, `paths` in agreement, nothing to act on. Two writers,');
|
|
364
|
+
lines.push(' two policies: `generate-tools` emits `paths` where a rule is path-scoped and');
|
|
365
|
+
lines.push(' nothing otherwise; `install.ts` writes agent-config\'s own vocabulary plus its');
|
|
366
|
+
lines.push(' ownership stamp, and none of those keys is one this host reads');
|
|
367
|
+
lines.push(' (claude-code-rules-dir-contract.md). Reported as a count on purpose: naming');
|
|
368
|
+
lines.push(' every pair would bury the two classes above, which are the actionable ones.');
|
|
308
369
|
lines.push('');
|
|
309
370
|
}
|
|
310
371
|
|
|
@@ -346,6 +407,10 @@ export function render(d: CarrierDivergence): string {
|
|
|
346
407
|
if (d.bodyDiff.length === 0) {
|
|
347
408
|
lines.push('');
|
|
348
409
|
lines.push(' No prose divergence — no rule\'s governed text differs between the carriers.');
|
|
410
|
+
if (d.pathsScopeDiff.length > 0) {
|
|
411
|
+
lines.push(' NOT an all-clear: the `paths:` disagreements above are still open, and they');
|
|
412
|
+
lines.push(' change when a rule loads rather than what it says.');
|
|
413
|
+
}
|
|
349
414
|
lines.push(' The condition is transient — it returns whenever the checkout moves ahead of');
|
|
350
415
|
lines.push(' the installed release — so this is a reading of right now, not a property of');
|
|
351
416
|
lines.push(' the repo. Re-run it, do not cite it. That holds doubly here: the reading also');
|
|
@@ -147,7 +147,7 @@ export function render(r: FunnelReport): string {
|
|
|
147
147
|
} else {
|
|
148
148
|
lines.push(
|
|
149
149
|
` Divergence: ${d.shared} shared · ${d.bodyDiff.length} prose-diff · ` +
|
|
150
|
-
`${d.frontmatterOnly.length} frontmatter-only · ` +
|
|
150
|
+
`${d.frontmatterOnly.length} frontmatter-only (${d.pathsScopeDiff.length} on \`paths:\`) · ` +
|
|
151
151
|
`${d.provenanceOnly.length} provenance-only · ${d.projectOnly.length} project-only · ` +
|
|
152
152
|
`${d.globalOnly.length} global-only` +
|
|
153
153
|
(d.manualOnlyGlobal.length > 0 ? ` (${d.manualOnlyGlobal.length} by design, ADR-004 manual)` : ''),
|
|
@@ -159,9 +159,16 @@ export function render(r: FunnelReport): string {
|
|
|
159
159
|
lines.push(' (claude-code-rules-dir-contract.md). The project copy is merely the');
|
|
160
160
|
lines.push(' newer one — recency, not precedence.');
|
|
161
161
|
}
|
|
162
|
-
if (d.
|
|
163
|
-
lines.push(` ${String(d.
|
|
164
|
-
lines.push('
|
|
162
|
+
if (d.pathsScopeDiff.length > 0) {
|
|
163
|
+
lines.push(` \`paths:\` SCOPE DISAGREEMENT (${String(d.pathsScopeDiff.length)}) — same prose, different load`);
|
|
164
|
+
lines.push(' schedule. `paths` is the one frontmatter key the host reads, so the copy');
|
|
165
|
+
lines.push(' without it defeats the other copy\'s scoping: a deliberately scoped');
|
|
166
|
+
lines.push(' obligation is delivered unconditionally. Act on these.');
|
|
167
|
+
}
|
|
168
|
+
const inertFm = d.frontmatterOnly.length - d.pathsScopeDiff.length;
|
|
169
|
+
if (inertFm > 0) {
|
|
170
|
+
lines.push(` ${String(inertFm)} further pair(s) differ only in frontmatter the host does not read —`);
|
|
171
|
+
lines.push(' prose byte-identical, `paths` in agreement, nothing to act on.');
|
|
165
172
|
}
|
|
166
173
|
if (d.unreadable.length > 0) {
|
|
167
174
|
lines.push(` Unreadable on one side (${d.unreadable.length}) — a broken install, not a disagreement.`);
|
|
@@ -54,8 +54,9 @@ import {
|
|
|
54
54
|
import * as hooks_status from "./hooks_status.js";
|
|
55
55
|
import { load_agent_settings } from "./_lib/agent_settings.js";
|
|
56
56
|
import {
|
|
57
|
-
|
|
57
|
+
describeHostCapabilities,
|
|
58
58
|
type HostCapabilityManifest,
|
|
59
|
+
type HostCapabilitySources,
|
|
59
60
|
} from "./_lib/host_capability.js";
|
|
60
61
|
import {
|
|
61
62
|
classifyLookup,
|
|
@@ -124,6 +125,17 @@ export interface OrchestrationReport {
|
|
|
124
125
|
*/
|
|
125
126
|
host_platform: string;
|
|
126
127
|
host_platform_assumed: boolean;
|
|
128
|
+
/**
|
|
129
|
+
* Per-field provenance for `host_manifest` — `registry` (a committed
|
|
130
|
+
* observation about this host), `live-probe` (established in this process),
|
|
131
|
+
* or `default` (nothing answered; the all-false safe default applied).
|
|
132
|
+
*
|
|
133
|
+
* Without it the six booleans read alike, and `false` cannot be told apart
|
|
134
|
+
* from "no answer". That distinction is the whole difference between "this
|
|
135
|
+
* host cannot spawn subagents" and "nobody has ever checked this host" —
|
|
136
|
+
* and this command is the one a user runs precisely to find out which.
|
|
137
|
+
*/
|
|
138
|
+
host_manifest_sources: HostCapabilitySources;
|
|
127
139
|
/** Verdict of the real activation gate for a canonical delegable probe. */
|
|
128
140
|
activation: { action: string; reason: string };
|
|
129
141
|
cost_budgets: Record<string, unknown>;
|
|
@@ -326,7 +338,8 @@ export function collect_orchestration(
|
|
|
326
338
|
const emergency = (settings["emergency"] ?? {}) as Record<string, unknown>;
|
|
327
339
|
const halted = emergency["orchestration_halt"] === true;
|
|
328
340
|
const downshift = sub["downshift"] !== false;
|
|
329
|
-
const host_manifest =
|
|
341
|
+
const { manifest: host_manifest, sources: host_manifest_sources } =
|
|
342
|
+
describeHostCapabilities(platform);
|
|
330
343
|
const activationInputs: ActivationInputs = {
|
|
331
344
|
halted,
|
|
332
345
|
subagent_spawn: host_manifest.subagent_spawn,
|
|
@@ -372,6 +385,7 @@ export function collect_orchestration(
|
|
|
372
385
|
host_manifest,
|
|
373
386
|
host_platform: platform,
|
|
374
387
|
host_platform_assumed: platform_assumed,
|
|
388
|
+
host_manifest_sources,
|
|
375
389
|
activation: { action: probe.action, reason: probe.reason },
|
|
376
390
|
cost_budgets,
|
|
377
391
|
ledger_present,
|
|
@@ -611,6 +625,15 @@ function _render(report: DoctorReport): string {
|
|
|
611
625
|
`and the capability registry is keyed on it. Pass --platform <host> to check another.`,
|
|
612
626
|
);
|
|
613
627
|
}
|
|
628
|
+
const provenanceBits = Object.entries(o.host_manifest_sources).map(
|
|
629
|
+
([field, src]) =>
|
|
630
|
+
`${field}=${o.host_manifest[field as keyof HostCapabilityManifest]}(${src})`,
|
|
631
|
+
);
|
|
632
|
+
lines.push(` capability provenance: ${provenanceBits.join(" · ")}`);
|
|
633
|
+
lines.push(
|
|
634
|
+
` registry = committed observation about this host, not a live check · ` +
|
|
635
|
+
`default = nobody answered, rendered as false`,
|
|
636
|
+
);
|
|
614
637
|
const tierBits = Object.entries(o.tier_budgets).map(([t, s]) => {
|
|
615
638
|
const cap = s.ceiling_usd === null ? "no cap" : `$${s.ceiling_usd}`;
|
|
616
639
|
const cool = s.cooldown_until_ms > 0 ? " COOLING" : "";
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "dod.schema.json",
|
|
4
|
+
"title": "Executable definition of done",
|
|
5
|
+
"description": "The `dod[]` slot a refined ticket or prompt envelope may carry. `dod` is EXECUTABLE by construction: every item names the command that proves it, so the engine can report which items are still unproven without asking a human to interpret prose. Prose acceptance criteria stay where they are (`acceptance_criteria`) — this is the machine-checkable subset, not a replacement. The slot is OPTIONAL: a ticket without `dod` behaves exactly as before, which is why the `refine` gate validates the shape only when the key is present. Consumed by `work_engine/directives/backend/refine.ts` (shape gate) and reported by `work_engine/directives/backend/_self_fix.ts` at the PARTIAL exit.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": [
|
|
8
|
+
"dod"
|
|
9
|
+
],
|
|
10
|
+
"additionalProperties": true,
|
|
11
|
+
"properties": {
|
|
12
|
+
"dod": {
|
|
13
|
+
"type": "array",
|
|
14
|
+
"description": "May be empty. An empty array means the author declared no machine-checkable done-condition, which is different from omitting the key: omission means the question was never asked. Both are legal and neither blocks.",
|
|
15
|
+
"items": {
|
|
16
|
+
"type": "object",
|
|
17
|
+
"required": [
|
|
18
|
+
"id",
|
|
19
|
+
"check"
|
|
20
|
+
],
|
|
21
|
+
"additionalProperties": true,
|
|
22
|
+
"properties": {
|
|
23
|
+
"id": {
|
|
24
|
+
"type": "string",
|
|
25
|
+
"minLength": 1,
|
|
26
|
+
"description": "Stable identifier, unique within the array. Used verbatim in the self-fix PARTIAL exit so a reader can match an unproven item back to the ticket without re-reading the whole envelope."
|
|
27
|
+
},
|
|
28
|
+
"check": {
|
|
29
|
+
"type": "string",
|
|
30
|
+
"minLength": 1,
|
|
31
|
+
"description": "The command that proves this item — a test filter, a linter invocation, a curl probe. Prose here defeats the point of the slot: the engine never runs it (the dispatcher spawns no subprocesses, per the delegation contract), it hands it to the agent, and an agent cannot run a sentence."
|
|
32
|
+
},
|
|
33
|
+
"description": {
|
|
34
|
+
"type": "string",
|
|
35
|
+
"description": "Optional human sentence for the halt surface. Absent is fine — `check` is already the falsifiable half."
|
|
36
|
+
},
|
|
37
|
+
"proven": {
|
|
38
|
+
"type": "boolean",
|
|
39
|
+
"description": "Set by the agent once `check` has actually been run green. Absent or `false` both mean unproven; the engine never infers `true` from a passing suite, because a suite passing is not evidence that THIS check ran."
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
import { createRequire as __acCreateRequire } from 'node:module'; const require = globalThis.require ?? __acCreateRequire(import.meta.url);
|
|
2
|
-
|
|
3
|
-
// src/scripts/_lib/recycle_envelope_paths.ts
|
|
4
|
-
import * as path from "node:path";
|
|
5
|
-
var RECYCLE_ENVELOPE_REL = path.join("agents", "runtime", "state", "recycle-envelope.json");
|
|
6
|
-
var RECYCLE_CONSUMED_REL = path.join(
|
|
7
|
-
"agents",
|
|
8
|
-
"runtime",
|
|
9
|
-
"state",
|
|
10
|
-
"recycle-envelope.consumed.json"
|
|
11
|
-
);
|
|
12
|
-
var RECYCLE_MAX_AGE_HOURS = 48;
|
|
13
|
-
var RECYCLE_ENVELOPE_MAX_BYTES = 6144;
|
|
14
|
-
|
|
15
|
-
// src/scripts/_lib/subagent_capsule.ts
|
|
16
|
-
var EPISTEMIC_STATES = ["verified", "assumed", "gap"];
|
|
17
|
-
var EPISTEMIC = new Set(EPISTEMIC_STATES);
|
|
18
|
-
var MAX_REF_CHARS = 200;
|
|
19
|
-
var MAX_LINE_CHARS = 240;
|
|
20
|
-
var MAX_ENTRIES = 40;
|
|
21
|
-
var CAPSULE_SCHEMA_VERSION = 2;
|
|
22
|
-
function isShortLine(value, max) {
|
|
23
|
-
return typeof value === "string" && value.length > 0 && value.length <= max && !value.includes("\n");
|
|
24
|
-
}
|
|
25
|
-
function checkList(errors, field, value, max, required) {
|
|
26
|
-
if (value === void 0) {
|
|
27
|
-
if (required) errors.push(`${field} is required`);
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
if (!Array.isArray(value)) {
|
|
31
|
-
errors.push(`${field} must be an array`);
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
if (value.length > MAX_ENTRIES) {
|
|
35
|
-
errors.push(`${field} carries ${value.length} entries (max ${MAX_ENTRIES}) \u2014 a capsule is a handoff, not a transcript`);
|
|
36
|
-
}
|
|
37
|
-
const bad = value.filter((x) => !isShortLine(x, max));
|
|
38
|
-
if (bad.length > 0) {
|
|
39
|
-
errors.push(
|
|
40
|
-
`${field} carries ${bad.length} entr${bad.length === 1 ? "y" : "ies"} that are not single lines of \u2264 ${max} chars (refs and short lines only, never bodies)`
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
var RECYCLE_ENVELOPE_KEYS = /* @__PURE__ */ new Set([
|
|
45
|
-
"capsule_version",
|
|
46
|
-
"variant",
|
|
47
|
-
"summary",
|
|
48
|
-
"task",
|
|
49
|
-
"workspace",
|
|
50
|
-
"written_at",
|
|
51
|
-
"acceptance_criteria",
|
|
52
|
-
"remaining",
|
|
53
|
-
"not_carried_forward",
|
|
54
|
-
"decisions",
|
|
55
|
-
"constraints",
|
|
56
|
-
"open_worker_envelopes",
|
|
57
|
-
"artifact_paths",
|
|
58
|
-
"assumptions"
|
|
59
|
-
]);
|
|
60
|
-
function validateRecycleEnvelope(input) {
|
|
61
|
-
const errors = [];
|
|
62
|
-
if (typeof input !== "object" || input === null || Array.isArray(input)) {
|
|
63
|
-
return ["not an object"];
|
|
64
|
-
}
|
|
65
|
-
const e = input;
|
|
66
|
-
for (const key of Object.keys(e)) {
|
|
67
|
-
if (!RECYCLE_ENVELOPE_KEYS.has(key)) {
|
|
68
|
-
errors.push(
|
|
69
|
-
`unknown field "${key}" \u2014 a recycle envelope carries selection and pointers only; free-form additions (prose summaries included) are schema-invalid by construction`
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
if (e["capsule_version"] !== CAPSULE_SCHEMA_VERSION) {
|
|
74
|
-
errors.push(`capsule_version must be ${CAPSULE_SCHEMA_VERSION}`);
|
|
75
|
-
}
|
|
76
|
-
if (e["variant"] !== "main_session") {
|
|
77
|
-
errors.push("variant must be 'main_session'");
|
|
78
|
-
}
|
|
79
|
-
if (!isShortLine(e["summary"], MAX_LINE_CHARS)) {
|
|
80
|
-
errors.push(`summary must be a single line of 1\u2013${MAX_LINE_CHARS} chars`);
|
|
81
|
-
}
|
|
82
|
-
if (!isShortLine(e["task"], MAX_LINE_CHARS)) {
|
|
83
|
-
errors.push(`task must be a single line of 1\u2013${MAX_LINE_CHARS} chars`);
|
|
84
|
-
}
|
|
85
|
-
if (!isShortLine(e["workspace"], MAX_REF_CHARS)) {
|
|
86
|
-
errors.push(`workspace must be a path ref of \u2264 ${MAX_REF_CHARS} chars`);
|
|
87
|
-
}
|
|
88
|
-
const writtenAt = e["written_at"];
|
|
89
|
-
if (typeof writtenAt !== "string" || Number.isNaN(Date.parse(writtenAt))) {
|
|
90
|
-
errors.push("written_at must be a parseable ISO-8601 timestamp");
|
|
91
|
-
}
|
|
92
|
-
checkList(errors, "acceptance_criteria", e["acceptance_criteria"], MAX_LINE_CHARS, true);
|
|
93
|
-
if (Array.isArray(e["acceptance_criteria"]) && e["acceptance_criteria"].length === 0) {
|
|
94
|
-
errors.push('acceptance_criteria must carry at least one entry \u2014 the successor cannot know "done" without it');
|
|
95
|
-
}
|
|
96
|
-
checkList(errors, "remaining", e["remaining"], MAX_LINE_CHARS, true);
|
|
97
|
-
checkList(errors, "not_carried_forward", e["not_carried_forward"], MAX_LINE_CHARS, true);
|
|
98
|
-
checkList(errors, "decisions", e["decisions"], MAX_LINE_CHARS, false);
|
|
99
|
-
checkList(errors, "constraints", e["constraints"], MAX_LINE_CHARS, false);
|
|
100
|
-
checkList(errors, "open_worker_envelopes", e["open_worker_envelopes"], MAX_REF_CHARS, false);
|
|
101
|
-
checkList(errors, "artifact_paths", e["artifact_paths"], MAX_REF_CHARS, false);
|
|
102
|
-
const assumptions = e["assumptions"];
|
|
103
|
-
if (assumptions !== void 0) {
|
|
104
|
-
if (!Array.isArray(assumptions)) {
|
|
105
|
-
errors.push("assumptions must be an array");
|
|
106
|
-
} else {
|
|
107
|
-
if (assumptions.length > MAX_ENTRIES) {
|
|
108
|
-
errors.push(`assumptions carries ${assumptions.length} entries (max ${MAX_ENTRIES})`);
|
|
109
|
-
}
|
|
110
|
-
for (const [i, raw] of assumptions.entries()) {
|
|
111
|
-
errors.push(...validateAssumption(raw, `assumptions[${i}]`));
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
return errors;
|
|
116
|
-
}
|
|
117
|
-
function validateAssumption(input, label = "assumption") {
|
|
118
|
-
const errors = [];
|
|
119
|
-
if (typeof input !== "object" || input === null || Array.isArray(input)) {
|
|
120
|
-
return [`${label} is not an object`];
|
|
121
|
-
}
|
|
122
|
-
const a = input;
|
|
123
|
-
if (!isShortLine(a["statement"], MAX_LINE_CHARS)) {
|
|
124
|
-
errors.push(`${label}.statement must be a single line of 1\u2013${MAX_LINE_CHARS} chars`);
|
|
125
|
-
}
|
|
126
|
-
if (!isShortLine(a["basis"], MAX_REF_CHARS)) {
|
|
127
|
-
errors.push(`${label}.basis must be a ref token (file:line, id, path, command) of \u2264 ${MAX_REF_CHARS} chars`);
|
|
128
|
-
}
|
|
129
|
-
if (typeof a["epistemic_state"] !== "string" || !EPISTEMIC.has(a["epistemic_state"])) {
|
|
130
|
-
errors.push(`${label}.epistemic_state must be one of ${EPISTEMIC_STATES.join(" | ")}`);
|
|
131
|
-
}
|
|
132
|
-
return errors;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
export {
|
|
136
|
-
RECYCLE_ENVELOPE_REL,
|
|
137
|
-
RECYCLE_CONSUMED_REL,
|
|
138
|
-
RECYCLE_MAX_AGE_HOURS,
|
|
139
|
-
RECYCLE_ENVELOPE_MAX_BYTES,
|
|
140
|
-
CAPSULE_SCHEMA_VERSION,
|
|
141
|
-
validateRecycleEnvelope
|
|
142
|
-
};
|