@event4u/agent-config 14.4.0 → 14.5.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 +26 -313
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +1 -1
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +1 -1
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +1 -1
- package/dist/discovery/trust-report.md +1 -1
- package/dist/discovery/workspaces.json +1 -1
- package/dist/hooks/dispatch.js +93 -25
- package/dist/mcp/registry-manifest.json +1 -1
- package/dist/mcp/server.json +2 -2
- package/package.json +1 -1
- package/src/config/estate-count-budget.json +8 -1
- package/src/scripts/bench_hook_injection.ts +18 -1
- package/src/scripts/hooks/host_semantics.ts +28 -0
- package/src/scripts/hooks/injection_budget.ts +152 -16
- package/src/scripts/hooks/run_continuation_hook.ts +59 -3
- package/src/scripts/session_register_hook.ts +40 -6
|
@@ -51,6 +51,12 @@
|
|
|
51
51
|
* checkbox delta) → allow (event: halt-stall)
|
|
52
52
|
* otherwise → BLOCK + continue (event: engage)
|
|
53
53
|
*
|
|
54
|
+
* Every event this concern writes carries `workspace_root`, `git_dir`,
|
|
55
|
+
* `git_common_dir` and `claim_path` — the two-tree provenance, per
|
|
56
|
+
* `provenance()` below. Without them the ledger records that an engagement
|
|
57
|
+
* happened but not that it crossed the tree boundary the fix was about, and
|
|
58
|
+
* crossing it is the whole claim.
|
|
59
|
+
*
|
|
54
60
|
* Stall counts CONSECUTIVE ENGAGEMENTS whose open-step count did not move.
|
|
55
61
|
* The upstream stalls on 5-of-5 completed-count readings; AC's checkbox
|
|
56
62
|
* signal is finer-grained (a step in progress usually flips SOMETHING), so
|
|
@@ -88,7 +94,8 @@ import { unwrap, type JsonObject, type JsonValue } from './envelope.js';
|
|
|
88
94
|
import { readHookStdin } from './hook_stdin.js';
|
|
89
95
|
import { is_replay_mode } from './state_io.js';
|
|
90
96
|
import { readTranscriptTail } from './turn_end_gate_hook.js';
|
|
91
|
-
import {
|
|
97
|
+
import { resolve_claim } from '../session_register_hook.js';
|
|
98
|
+
import { git_common_dir, git_dir } from '../_lib/git_common_dir.js';
|
|
92
99
|
import {
|
|
93
100
|
deriveSessionKey,
|
|
94
101
|
parseRecord,
|
|
@@ -386,6 +393,48 @@ function readState(file: string): RunState | null {
|
|
|
386
393
|
}
|
|
387
394
|
}
|
|
388
395
|
|
|
396
|
+
/**
|
|
397
|
+
* The four fields that make an event's two-tree property checkable by a third
|
|
398
|
+
* party holding nothing but the ledger line.
|
|
399
|
+
*
|
|
400
|
+
* The defect this concern shipped with was a writer and a reader resolving
|
|
401
|
+
* DIFFERENT trees: `sessions:claim` wrote under `process.cwd()` (the operator's
|
|
402
|
+
* worktree) while this concern read under `--project-dir` (the parent
|
|
403
|
+
* checkout), so the claim was never found and no event was written. An empty
|
|
404
|
+
* ledger looked exactly like a healthy idle run, which is how it survived a
|
|
405
|
+
* release.
|
|
406
|
+
*
|
|
407
|
+
* A reader therefore has to see, from the line alone, which trees were in play
|
|
408
|
+
* and where the contract was actually read out of. A boolean such as
|
|
409
|
+
* `worktree_started: true` would be another assertion by the system under
|
|
410
|
+
* observation rather than a check on it; the falsifiable facts are concrete
|
|
411
|
+
* (AI council 2026-08-19, 2/2 convergent):
|
|
412
|
+
*
|
|
413
|
+
* `git_dir !== git_common_dir` → this IS a linked worktree
|
|
414
|
+
* `claim_path` under `git_common_dir` → the contract crossed into the shared
|
|
415
|
+
* root, which is the fix working
|
|
416
|
+
*
|
|
417
|
+
* `process.cwd()` is deliberately NOT among them. The dispatcher chdirs into
|
|
418
|
+
* `--project-dir` before the chain runs, so at concern time cwd equals
|
|
419
|
+
* `workspace_root` on every shipped path — a field that can never disagree with
|
|
420
|
+
* another cannot falsify anything, and shipping it as provenance would be the
|
|
421
|
+
* decorative-evidence shape this whole change exists to remove. Measured while
|
|
422
|
+
* writing the test that asserted the opposite.
|
|
423
|
+
*
|
|
424
|
+
* Absolute paths, deliberately: a relative form is interpreted against whichever
|
|
425
|
+
* root the reader happens to hold, which is the exact ambiguity being removed.
|
|
426
|
+
* These are local filesystem paths in gitignored runtime state, never published
|
|
427
|
+
* — the same posture the session register already takes.
|
|
428
|
+
*/
|
|
429
|
+
export function provenance(workspaceRoot: string, claimPath: string): JsonObject {
|
|
430
|
+
return {
|
|
431
|
+
workspace_root: workspaceRoot,
|
|
432
|
+
git_dir: git_dir(workspaceRoot) ?? '',
|
|
433
|
+
git_common_dir: git_common_dir(workspaceRoot) ?? '',
|
|
434
|
+
claim_path: claimPath,
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
|
|
389
438
|
function appendEvent(workspaceRoot: string, record: JsonObject): void {
|
|
390
439
|
try {
|
|
391
440
|
const file = path.join(workspaceRoot, EVENTS_RELPATH);
|
|
@@ -438,8 +487,12 @@ export function main(): number {
|
|
|
438
487
|
str(envelope['session_id'] as JsonValue | undefined);
|
|
439
488
|
|
|
440
489
|
// ── contract gate ────────────────────────────────────────────────
|
|
441
|
-
const
|
|
442
|
-
if (
|
|
490
|
+
const claim = resolve_claim(workspaceRoot, sessionId || null);
|
|
491
|
+
if (claim === null) return EXIT_ALLOW;
|
|
492
|
+
const slug = claim.slug;
|
|
493
|
+
// The two-root provenance, carried on every event this run emits. See
|
|
494
|
+
// `provenance()` for why these three and not a boolean.
|
|
495
|
+
const roots = provenance(workspaceRoot, claim.path);
|
|
443
496
|
|
|
444
497
|
const roadmapPath = path.join(workspaceRoot, 'agents', 'roadmaps', `${slug}.md`);
|
|
445
498
|
let roadmapText: string;
|
|
@@ -491,6 +544,7 @@ export function main(): number {
|
|
|
491
544
|
roadmap: slug,
|
|
492
545
|
turn: turnOrdinal,
|
|
493
546
|
at: new Date().toISOString(),
|
|
547
|
+
...roots,
|
|
494
548
|
});
|
|
495
549
|
return EXIT_ALLOW;
|
|
496
550
|
}
|
|
@@ -529,6 +583,7 @@ export function main(): number {
|
|
|
529
583
|
open: scan.open,
|
|
530
584
|
blocked: scan.blocked,
|
|
531
585
|
at: new Date().toISOString(),
|
|
586
|
+
...roots,
|
|
532
587
|
});
|
|
533
588
|
if (action === 'complete') {
|
|
534
589
|
// Only a COMPLETION clears the state, so a later run on the same
|
|
@@ -580,6 +635,7 @@ export function main(): number {
|
|
|
580
635
|
open: scan.open,
|
|
581
636
|
blocked: scan.blocked,
|
|
582
637
|
at: new Date().toISOString(),
|
|
638
|
+
...roots,
|
|
583
639
|
});
|
|
584
640
|
|
|
585
641
|
process.stderr.write(_continuationText(slug, scan, state.iterations));
|
|
@@ -237,7 +237,23 @@ function _read_claim_file(file: string): RoadmapClaim | null {
|
|
|
237
237
|
}
|
|
238
238
|
|
|
239
239
|
/**
|
|
240
|
-
*
|
|
240
|
+
* A resolved claim plus the file it was actually read from.
|
|
241
|
+
*
|
|
242
|
+
* The path is not decoration. The defect this whole surface exists to prevent
|
|
243
|
+
* was a WRITER and a READER resolving different trees, and the only fact that
|
|
244
|
+
* falsifies it is the concrete pair of roots — a boolean asserting "worktree
|
|
245
|
+
* started" is another claim by the system under observation, not a check on it
|
|
246
|
+
* (AI council 2026-08-19, 2/2, on the run-continuation observation).
|
|
247
|
+
*/
|
|
248
|
+
export interface ResolvedClaim {
|
|
249
|
+
slug: string;
|
|
250
|
+
/** Absolute path of the claim file this read came from. */
|
|
251
|
+
path: string;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* The roadmap slug THIS session has claimed, and the file it came from — or
|
|
256
|
+
* `null`. Never throws.
|
|
241
257
|
*
|
|
242
258
|
* Two reads, in order, and the second one is why a peer's claim can no longer be
|
|
243
259
|
* inherited: the per-session file first, then the legacy per-worktree file —
|
|
@@ -246,14 +262,14 @@ function _read_claim_file(file: string): RoadmapClaim | null {
|
|
|
246
262
|
* that session's claim, and reading it as this session's is exactly the defect
|
|
247
263
|
* that put one archived slug on four live records.
|
|
248
264
|
*/
|
|
249
|
-
export function
|
|
265
|
+
export function resolve_claim(
|
|
250
266
|
workspace_root: string,
|
|
251
267
|
// REQUIRED, on the reviewer's point: an optional id means a caller that forgets
|
|
252
268
|
// it silently gets legacy semantics instead of a type error, and there is
|
|
253
269
|
// exactly one call site that matters (`build_record`). `null` stays a legal
|
|
254
270
|
// value — it is the host-exports-nothing case — but it has to be passed.
|
|
255
271
|
session_id: string | null,
|
|
256
|
-
):
|
|
272
|
+
): ResolvedClaim | null {
|
|
257
273
|
const own = roadmap_claim_rel(session_id);
|
|
258
274
|
if (own !== ROADMAP_CLAIM_REL) {
|
|
259
275
|
// Per-session, shared dir first, then the pre-fix per-tree path. The
|
|
@@ -262,16 +278,20 @@ export function read_claimed_slug(
|
|
|
262
278
|
for (const p of claim_read_paths(workspace_root, session_id)) {
|
|
263
279
|
if (path.basename(p) === path.basename(ROADMAP_CLAIM_REL)) continue;
|
|
264
280
|
const mine = _read_claim_file(p);
|
|
265
|
-
if (mine !== null) return mine.slug;
|
|
281
|
+
if (mine !== null) return { slug: mine.slug, path: p };
|
|
266
282
|
}
|
|
267
283
|
}
|
|
268
284
|
// The legacy per-WORKTREE file, in the same two locations. Unchanged in
|
|
269
285
|
// meaning: it is a claim on the checkout, not on a session.
|
|
270
286
|
let legacy: RoadmapClaim | null = null;
|
|
287
|
+
let legacy_path = '';
|
|
271
288
|
for (const p of claim_read_paths(workspace_root, session_id)) {
|
|
272
289
|
if (path.basename(p) !== path.basename(ROADMAP_CLAIM_REL)) continue;
|
|
273
290
|
legacy = _read_claim_file(p);
|
|
274
|
-
if (legacy !== null)
|
|
291
|
+
if (legacy !== null) {
|
|
292
|
+
legacy_path = p;
|
|
293
|
+
break;
|
|
294
|
+
}
|
|
275
295
|
}
|
|
276
296
|
if (legacy === null) {
|
|
277
297
|
return null;
|
|
@@ -293,7 +313,21 @@ export function read_claimed_slug(
|
|
|
293
313
|
if (id !== '') {
|
|
294
314
|
return null;
|
|
295
315
|
}
|
|
296
|
-
return legacy.slug;
|
|
316
|
+
return { slug: legacy.slug, path: legacy_path };
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* The roadmap slug THIS session has claimed, or `null`. Never throws.
|
|
321
|
+
*
|
|
322
|
+
* Thin projection of `resolve_claim` — kept because every existing caller wants
|
|
323
|
+
* the slug alone, and widening their return type to carry a path none of them
|
|
324
|
+
* reads would be a signature change for no consumer.
|
|
325
|
+
*/
|
|
326
|
+
export function read_claimed_slug(
|
|
327
|
+
workspace_root: string,
|
|
328
|
+
session_id: string | null,
|
|
329
|
+
): string | null {
|
|
330
|
+
return resolve_claim(workspace_root, session_id)?.slug ?? null;
|
|
297
331
|
}
|
|
298
332
|
|
|
299
333
|
/**
|