@event4u/agent-config 14.4.0 → 14.6.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 (48) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +132 -291
  3. package/dist/agent-src/commands/analyze/decision.md +7 -0
  4. package/dist/agent-src/commands/optimize/project.md +10 -0
  5. package/dist/agent-src/commands/roadmap/process-full.md +45 -3
  6. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +130 -1
  7. package/dist/agent-src/rules/decision-revisit-gate.md +108 -7
  8. package/dist/agent-src/skills/adr-create/SKILL.md +25 -3
  9. package/dist/agent-src/skills/decision-review/SKILL.md +18 -0
  10. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  11. package/dist/cli-delegate/{chunk-ZPSXJTLI.js → chunk-P6LQDIWK.js} +15 -1
  12. package/dist/cli-delegate/cmd_conformance.js +1 -1
  13. package/dist/cli-delegate/cmd_handoff.js +1 -1
  14. package/dist/cli-delegate/cmd_session_recycle.js +1 -1
  15. package/dist/discovery/deprecation-report.md +1 -1
  16. package/dist/discovery/discovery-manifest.json +8 -8
  17. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  18. package/dist/discovery/discovery-manifest.summary.md +1 -1
  19. package/dist/discovery/dormancy-report.md +1 -1
  20. package/dist/discovery/orphan-report.md +1 -1
  21. package/dist/discovery/packs.json +2 -2
  22. package/dist/discovery/trust-report.md +1 -1
  23. package/dist/discovery/workspaces.json +2 -2
  24. package/dist/hooks/dispatch.js +419 -94
  25. package/dist/mcp/registry-manifest.json +2 -2
  26. package/dist/mcp/server.json +2 -2
  27. package/dist/router.json +1 -1
  28. package/docs/guidelines/agent-infra/engineering-memory-data-format.md +49 -0
  29. package/package.json +1 -1
  30. package/src/config/estate-count-budget.json +36 -1
  31. package/src/config/pack-size-budget.json +3 -2
  32. package/src/scripts/_lib/layer_overlap_notice.ts +85 -0
  33. package/src/scripts/_lib/scope_guard.sh +62 -3
  34. package/src/scripts/_lib/subagent_capsule.ts +81 -1
  35. package/src/scripts/adr/regenerate_index.ts +13 -2
  36. package/src/scripts/adr_cite_check.ts +424 -0
  37. package/src/scripts/bench_hook_injection.ts +18 -1
  38. package/src/scripts/check_adr_frontmatter.ts +187 -1
  39. package/src/scripts/check_single_delivery.ts +473 -0
  40. package/src/scripts/hooks/host_semantics.ts +28 -0
  41. package/src/scripts/hooks/injection_budget.ts +152 -16
  42. package/src/scripts/hooks/run_continuation_hook.ts +918 -46
  43. package/src/scripts/install.sh +12 -2
  44. package/src/scripts/memory_consolidation.ts +189 -0
  45. package/src/scripts/memory_eviction.ts +384 -0
  46. package/src/scripts/report_layer_overlap.ts +72 -0
  47. package/src/scripts/report_memory_pointers.ts +622 -0
  48. package/src/scripts/session_register_hook.ts +144 -21
@@ -237,7 +237,23 @@ function _read_claim_file(file: string): RoadmapClaim | null {
237
237
  }
238
238
 
239
239
  /**
240
- * The roadmap slug THIS session has claimed, or `null`. Never throws.
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 read_claimed_slug(
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
- ): string | null {
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) break;
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
  /**
@@ -320,8 +354,17 @@ export function claim_is_stale(
320
354
  // roadmaps directory" — and it is the same check for a traversal, an absolute
321
355
  // path, and an encoding nobody enumerated. A slug that fails it is reported
322
356
  // stale rather than resolved, so no read happens at all.
357
+ // Structural, not textual. Round 6 finding 3: the previous form ANDed a
358
+ // `!rel.includes('..')` substring test onto the prefix check, and round 5
359
+ // finding 10 removed exactly that test from `resolveRoadmap` — one-sided. The
360
+ // two functions then disagreed about one claim string in the opposite
361
+ // direction: for a legal slug like `road-to-a..b` the register rendered a live
362
+ // claim as stale (dropping it from the collision set and disabling the
363
+ // duplicate-work warning) while the hook resolved it and engaged. `path.dirname`
364
+ // asks the only question that matters — is the result a file directly inside the
365
+ // roadmaps directory — and is the same check both sides now make.
323
366
  const rel = path.normalize(path.join('agents', 'roadmaps', `${base}.md`));
324
- const inside = rel.startsWith(`agents${path.sep}roadmaps${path.sep}`) && !rel.includes('..');
367
+ const inside = path.dirname(rel) === path.join('agents', 'roadmaps');
325
368
  if (!inside || path.isAbsolute(base)) {
326
369
  return true; // not a slug this repo can hold; never render it as live work
327
370
  }
@@ -375,29 +418,98 @@ export function claim_is_stale(
375
418
  * conditions, because a wrong anchor is worse than the fallback:
376
419
  *
377
420
  * 1. it is an existing directory;
378
- * 2. it is the ROOT of a checkout (`git_dir` looks for `<dir>/.git` and does not
379
- * walk up, so a session whose cwd is a subdirectory degrades to
380
- * `workspace_root` — today's behaviour, never something worse);
381
- * 3. it belongs to the SAME repository — identical git common dir. A cwd in
382
- * another repo would otherwise write this repo's register with a foreign
421
+ * 2. it resolves to the NEAREST enclosing checkout root, walking up from the
422
+ * reported directory;
423
+ * 3. that root belongs to the SAME repository — identical git common dir. A cwd
424
+ * in another repo would otherwise write this repo's register with a foreign
383
425
  * branch, and the register is shared by every worktree here.
426
+ *
427
+ * ## Condition 2 used to reject a subdirectory, and that was wrong here
428
+ *
429
+ * It required the reported directory to BE a checkout root, and defended the
430
+ * rejection as "a session whose cwd is a subdirectory degrades to
431
+ * `workspace_root` — today's behaviour, never something worse". R2 round 3
432
+ * finding 2 measured that claim false in this repository's own layout, where
433
+ * worktrees live at `<parent>/.claude/worktrees/<name>` — NESTED under the
434
+ * parent. For a session standing at `<parent>/.claude/worktrees/wt/src`:
435
+ *
436
+ * - the degraded root is `<parent>`, i.e. equal to `workspace_root`;
437
+ * - so `git_dir` and `git_common_dir` taken from it are both `<parent>/.git`;
438
+ * - and the reported cwd is under `<parent>` as well.
439
+ *
440
+ * Every available signal then reports a healthy same-tree run for a genuine
441
+ * two-tree one. That is not a loss of precision, it is a confident wrong answer,
442
+ * which is strictly worse than the fallback the docblock was defending.
443
+ *
444
+ * The walk is bounded twice over, per the AI council's condition (2026-08-19,
445
+ * 2/2 convergent on this shape): it stops at the FIRST enclosing checkout root,
446
+ * so a nested worktree is found before the parent that contains it, and the
447
+ * same-repository check is retained on whatever it finds — a cwd inside an
448
+ * unrelated repo nested in this tree resolves to that repo's root, fails the
449
+ * identity check, and falls back. Paths are canonicalised before the walk so a
450
+ * symlinked cwd cannot walk a different chain than the one it names.
384
451
  */
385
452
  export function session_checkout(
386
453
  workspace_root: string,
387
454
  payload_cwd: string | null | undefined,
388
455
  ): string {
456
+ // EVERY branch returns a canonical path, resolve and fallback alike.
457
+ //
458
+ // Round 4 finding 5: the resolve branch realpath-normalised and the four
459
+ // fallback branches returned `workspace_root` verbatim, so under a repo
460
+ // reached through a symlinked ancestor two sessions in the SAME working tree
461
+ // stored different strings for it — `/private/var/…/repo` from the resolver
462
+ // and `/var/…/repo` from the envelope. `foreign_sessions_block` compares the
463
+ // stored forms with `path.resolve`, which does not follow symlinks, so it read
464
+ // them as different worktrees and printed the benign "separate trees" note
465
+ // where the same-worktree COLLISION prompt belongs. The walk-up widened the
466
+ // exposure by moving subdirectory sessions onto the normalising branch.
467
+ const fallback = canonical(workspace_root);
389
468
  const cwd = String(payload_cwd ?? '').trim();
390
- if (cwd === '') return workspace_root;
469
+ if (cwd === '') return fallback;
470
+ let start: string;
391
471
  try {
392
- if (!fs.statSync(cwd).isDirectory()) return workspace_root;
472
+ if (!fs.statSync(cwd).isDirectory()) return fallback;
473
+ start = fs.realpathSync(cwd);
393
474
  } catch {
394
- return workspace_root;
475
+ return fallback;
395
476
  }
396
- if (git_dir(cwd) === null) return workspace_root;
397
- const mine = git_common_dir(cwd);
477
+ const root = nearest_checkout_root(start);
478
+ if (root === null) return fallback;
479
+ const mine = git_common_dir(root);
398
480
  const theirs = git_common_dir(workspace_root);
399
- if (mine === null || theirs === null || mine !== theirs) return workspace_root;
400
- return cwd;
481
+ if (mine === null || theirs === null || mine !== theirs) return fallback;
482
+ return root;
483
+ }
484
+
485
+ /** `realpath` that falls back to an absolute form when the path is unreadable. */
486
+ function canonical(p: string): string {
487
+ const abs = path.resolve(p);
488
+ try {
489
+ return fs.realpathSync(abs);
490
+ } catch {
491
+ return abs;
492
+ }
493
+ }
494
+
495
+ /**
496
+ * The nearest enclosing directory that is a checkout root, or `null`.
497
+ *
498
+ * The iteration cap is a loop guard, not a policy: `path.dirname` is a fixed
499
+ * point at the filesystem root, so the terminating condition is the unchanged
500
+ * path and the counter only bounds a pathological filesystem. The FIRST hit
501
+ * wins, which is what makes a nested worktree resolve to itself rather than to
502
+ * the checkout it sits inside.
503
+ */
504
+ function nearest_checkout_root(start: string): string | null {
505
+ let dir = start;
506
+ for (let i = 0; i < 64; i++) {
507
+ if (git_dir(dir) !== null) return dir;
508
+ const up = path.dirname(dir);
509
+ if (up === dir) return null;
510
+ dir = up;
511
+ }
512
+ return null;
401
513
  }
402
514
 
403
515
  /**
@@ -585,8 +697,19 @@ export function foreign_sessions_block(
585
697
  // `session_checkout`. Before that every record claimed the main checkout, so
586
698
  // every collision looked like the dangerous one.
587
699
  if (branch_hit !== null) {
588
- const peer_tree = path.resolve(branch_hit.record.worktree ?? '');
589
- const same_tree = peer_tree !== '' && peer_tree === path.resolve(workspace_root);
700
+ // Compared through `canonical`, not `path.resolve`, because resolve does not
701
+ // follow symlinks — round 5 finding 11. Round 4 finding 5 made every
702
+ // `session_checkout` branch canonical, which fixes records written from now
703
+ // on; it does nothing for a record already in the register from before the
704
+ // upgrade, and those live for the whole TTL window. Under a symlinked
705
+ // ancestor such a peer holds `/var/…/repo` while this session holds
706
+ // `/private/var/…/repo`, and comparing the raw strings reads two sessions
707
+ // in ONE working tree as separate trees — printing the benign note where
708
+ // the collision prompt belongs. Canonicalising at the comparison closes the
709
+ // upgrade boundary the writer side cannot reach.
710
+ const peer_tree = canonical(branch_hit.record.worktree ?? '');
711
+ const same_tree =
712
+ (branch_hit.record.worktree ?? '') !== '' && peer_tree === canonical(workspace_root);
590
713
  if (same_tree) {
591
714
  parts.push(
592
715
  '',