@event4u/agent-config 14.19.0 → 14.20.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 (101) hide show
  1. package/.augment-plugin/marketplace.json +3 -3
  2. package/.augment-plugin/plugin.json +1 -1
  3. package/.claude-plugin/marketplace.json +1 -1
  4. package/CHANGELOG.md +108 -156
  5. package/dist/agent-src/commands/agent-handoff.md +42 -41
  6. package/dist/agent-src/contexts/execution/host-capability-manifest.md +7 -1
  7. package/dist/agent-src/rules/untrusted-input-defense.md +0 -2
  8. package/dist/agent-src/skills/evaluate-llm-feature/SKILL.md +1 -1
  9. package/dist/agent-src/skills/markitdown/SKILL.md +1 -1
  10. package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +28 -6
  11. package/dist/agent-src/skills/test-driven-development/SKILL.md +1 -3
  12. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  13. package/dist/agent-src/templates/roadmaps.md +14 -1
  14. package/dist/agent-src/templates/scripts/telemetry/mcp_lite.ts +213 -0
  15. package/dist/agent-src/templates/scripts/telemetry/report_renderer.ts +16 -0
  16. package/dist/agent-src/templates/scripts/telemetry_report.ts +22 -2
  17. package/dist/cli/mcp/dispatch.js +10 -0
  18. package/dist/cli/mcp/stdio.js +33 -2
  19. package/dist/cli/mcp/telemetry.js +227 -0
  20. package/dist/cli-delegate/{chunk-GZTMRYCN.js → chunk-C6IPZRDA.js} +79 -12
  21. package/dist/cli-delegate/{chunk-CYH5SP6M.js → chunk-I6RHFQEG.js} +151 -16
  22. package/dist/cli-delegate/{chunk-IDHUQIXP.js → chunk-NUMQHLS5.js} +21 -4
  23. package/dist/cli-delegate/{chunk-PMEKGDED.js → chunk-PI2GU3DP.js} +163 -1
  24. package/dist/cli-delegate/{chunk-7PRE6ZV2.js → chunk-YS6XTLEK.js} +199 -4
  25. package/dist/cli-delegate/chunk-ZVBOHSVF.js +1109 -0
  26. package/dist/cli-delegate/cmd_conformance.js +73 -109
  27. package/dist/cli-delegate/cmd_doctor.js +2 -2
  28. package/dist/cli-delegate/cmd_export.js +2 -2
  29. package/dist/cli-delegate/cmd_handoff.js +41 -11
  30. package/dist/cli-delegate/cmd_memory_get.js +1 -1
  31. package/dist/cli-delegate/cmd_prune.js +2 -2
  32. package/dist/cli-delegate/cmd_route_audit.js +1 -1
  33. package/dist/cli-delegate/cmd_session_recycle.js +59 -902
  34. package/dist/cli-delegate/cmd_settings_migrate.js +2 -2
  35. package/dist/cli-delegate/cmd_sync.js +2 -2
  36. package/dist/cli-delegate/cmd_uninstall.js +2 -2
  37. package/dist/cli-delegate/cmd_validate.js +2 -2
  38. package/dist/discovery/deprecation-report.md +1 -1
  39. package/dist/discovery/discovery-manifest.json +9 -9
  40. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  41. package/dist/discovery/discovery-manifest.summary.md +1 -1
  42. package/dist/discovery/dormancy-report.md +1 -1
  43. package/dist/discovery/orphan-report.md +1 -1
  44. package/dist/discovery/packs.json +2 -2
  45. package/dist/discovery/trust-report.md +1 -1
  46. package/dist/discovery/workspaces.json +2 -2
  47. package/dist/hooks/dispatch.js +7257 -6497
  48. package/dist/install/install.mjs +393 -168
  49. package/dist/mcp/registry-manifest.json +2 -2
  50. package/dist/mcp/server.json +2 -2
  51. package/dist/mcp/server.mjs +497 -118
  52. package/dist/scripts/mcp_server/telemetry.js +148 -0
  53. package/docs/guidelines/agent-infra/naming.md +1 -1
  54. package/docs/guidelines/agent-infra/security-lint-containment.md +46 -6
  55. package/docs/guidelines/agent-infra/self-improvement-pipeline.md +4 -4
  56. package/docs/guidelines/agent-infra/untrusted-input-spotlighting.md +1 -1
  57. package/docs/guidelines/redundancy-taxonomy.md +28 -0
  58. package/package.json +1 -1
  59. package/src/agent-src/templates/scripts/telemetry/mcp_lite.ts +213 -0
  60. package/src/agent-src/templates/scripts/telemetry/report_renderer.ts +16 -0
  61. package/src/agent-src/templates/scripts/telemetry_report.ts +22 -2
  62. package/src/config/assurance-capability-registry.json +9 -0
  63. package/src/config/gate-coverage.yml +55 -0
  64. package/src/config/gate-violation-baselines.json +15 -3
  65. package/src/scripts/_cli/cmd_session_recycle.ts +51 -3
  66. package/src/scripts/_lib/audit_field_provenance.ts +44 -3
  67. package/src/scripts/_lib/host_capability.ts +38 -0
  68. package/src/scripts/_lib/install_reach_checks.ts +33 -1
  69. package/src/scripts/_lib/mcp_bridge.ts +222 -10
  70. package/src/scripts/_lib/mcp_consent_residual.ts +173 -0
  71. package/src/scripts/_lib/orchestration_record.ts +23 -1
  72. package/src/scripts/_lib/recycle_envelope_paths.ts +190 -0
  73. package/src/scripts/_lib/release_position.ts +96 -0
  74. package/src/scripts/_lib/review_skipped_record.ts +17 -1
  75. package/src/scripts/_lib/security_lint.ts +179 -7
  76. package/src/scripts/_lib/self_repair.ts +141 -6
  77. package/src/scripts/_lib/self_repair_store.ts +273 -7
  78. package/src/scripts/_lib/subagent_capsule.ts +155 -3
  79. package/src/scripts/build_rule_trigger_matrix.ts +5 -0
  80. package/src/scripts/check_mcp_doc_drift.ts +217 -0
  81. package/src/scripts/check_pack_size.ts +321 -3
  82. package/src/scripts/handoff_context_hook.ts +54 -7
  83. package/src/scripts/hooks/session_eol_hook.ts +18 -7
  84. package/src/scripts/install.ts +4 -4
  85. package/src/scripts/lint_agent_security.ts +316 -25
  86. package/src/scripts/lint_confusables.ts +42 -6
  87. package/src/scripts/lint_handoffs.ts +0 -132
  88. package/src/scripts/lint_hidden_unicode.ts +49 -7
  89. package/src/scripts/lint_instruction_smuggling.ts +42 -6
  90. package/src/scripts/lint_mcp_config_security.ts +42 -6
  91. package/src/scripts/lint_roadmap_later_disposition.ts +217 -27
  92. package/src/scripts/lint_skill_frontmatter_safety.ts +43 -7
  93. package/src/scripts/mcp_server/resources.ts +41 -0
  94. package/src/scripts/mcp_server/telemetry.ts +19 -4
  95. package/src/scripts/orchestration_record.ts +12 -1
  96. package/src/scripts/release.ts +24 -24
  97. package/src/scripts/release_publication.ts +6 -2
  98. package/src/scripts/self_repair_cli.ts +31 -1
  99. package/src/scripts/skill_scout.ts +69 -0
  100. package/dist/cli-delegate/chunk-2GNWGMAL.js +0 -172
  101. package/dist/cli-delegate/chunk-LPPWO5CE.js +0 -161
@@ -731,8 +731,12 @@ export function guard_release_curation(version: string, prMerged = false): void
731
731
  marked.join('\n')
732
732
  : '') +
733
733
  '\n Stopped BEFORE committing — no release commit, no branch on ' +
734
- `${REMOTE}, no pull request, no tag. Curate the ` +
735
- '`### Release highlights` head, then re-run `task release`.',
734
+ `${REMOTE}, no pull request, no tag. HEAD is on the LOCAL ` +
735
+ `\`release/${version}\` branch and the generated section is in the working ` +
736
+ 'tree: curate the `### Release highlights` head in `CHANGELOG.md` there, then ' +
737
+ 're-run `task release`. That re-run works from this position — `preflightPosition` ' +
738
+ 'accepts the release branch with the uncommitted step-2 output, which it did not ' +
739
+ 'until 2026-09-07 (both spellings of the re-run were refused before step 1).',
736
740
  );
737
741
  }
738
742
 
@@ -48,6 +48,7 @@ import {
48
48
  attachReleaseErrors,
49
49
  listRecords,
50
50
  markReleased,
51
+ writeTargetIndex,
51
52
  openRecords,
52
53
  readOverflow,
53
54
  readRecord,
@@ -503,6 +504,30 @@ function releaseCmd(root: string, argv: string[]): number {
503
504
  return EXIT_OK;
504
505
  }
505
506
 
507
+ /**
508
+ * Rebuild the per-target occurrence index and print it.
509
+ *
510
+ * A DERIVED read: the record files are the source, the index is regenerable
511
+ * and gitignored with the rest of `agents/runtime/`. It exists because
512
+ * `occurrences` counts a fingerprint (class + normalised evidence shape), so a
513
+ * rule's weakness showing up in three different shapes was three records with
514
+ * one occurrence each — and the third-recurrence escalation had nothing that
515
+ * could ever establish its own precondition.
516
+ */
517
+ function targetsCmd(root: string): number {
518
+ const counts = writeTargetIndex(root);
519
+ if (counts.length === 0) {
520
+ process.stdout.write('self-repair: no record carries a resolved target yet.\n');
521
+ return EXIT_OK;
522
+ }
523
+ for (const c of counts) {
524
+ process.stdout.write(
525
+ ` ${c.target} ${c.occurrences} occurrence(s) across ${c.records.length} record(s)\n`,
526
+ );
527
+ }
528
+ return EXIT_OK;
529
+ }
530
+
506
531
  export function main(argv: readonly string[]): number {
507
532
  const root = process.cwd();
508
533
  const sub = argv[0] ?? 'status';
@@ -512,7 +537,12 @@ export function main(argv: readonly string[]): number {
512
537
  if (sub === 'release') {
513
538
  return releaseCmd(root, [...argv.slice(1)]);
514
539
  }
515
- process.stderr.write(`self-repair: unknown subcommand '${sub}' (status | release).\n`);
540
+ if (sub === 'targets') {
541
+ return targetsCmd(root);
542
+ }
543
+ process.stderr.write(
544
+ `self-repair: unknown subcommand '${sub}' (status | release | targets).\n`,
545
+ );
516
546
  return EXIT_FAIL;
517
547
  }
518
548
 
@@ -48,6 +48,12 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
48
48
 
49
49
  import { collect, _keyword_vector, _cosine } from './audit_skill_overlap.js';
50
50
  import { reportScanned, DeadScopeError } from './_lib/scan_scope.js';
51
+ import * as sl from './_lib/security_lint.js';
52
+ import { _scan as _scanHiddenUnicode } from './lint_hidden_unicode.js';
53
+ import { _scan as _scanConfusables } from './lint_confusables.js';
54
+ import { _scan as _scanInstructionSmuggling } from './lint_instruction_smuggling.js';
55
+ import { _scan as _scanMcpConfigSecurity } from './lint_mcp_config_security.js';
56
+ import { _scan as _scanFrontmatterSafety } from './lint_skill_frontmatter_safety.js';
51
57
 
52
58
  const _HERE = fileURLToPath(import.meta.url);
53
59
  const _DEFAULT_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
@@ -262,6 +268,60 @@ export function resolveCandidateDir(
262
268
  return { dir, refusal: null };
263
269
  }
264
270
 
271
+ /**
272
+ * The five content linters, by the check id each one reports under.
273
+ *
274
+ * The SAME functions the `lint_agent_security` umbrella spawns as processes.
275
+ * Called in-process here rather than shelled out: five `tsx` cold starts per
276
+ * candidate is a cost with no benefit, and the thing that must be identical is
277
+ * the DETECTION, which is these functions.
278
+ */
279
+ const CONTENT_LINTERS: ReadonlyArray<readonly [string, (sf: sl.ScannedFile) => sl.Finding[]]> = [
280
+ ['hidden-unicode', _scanHiddenUnicode],
281
+ ['mixed-script-confusable', _scanConfusables],
282
+ ['instruction-smuggling', _scanInstructionSmuggling],
283
+ ['mcp-config-security', _scanMcpConfigSecurity],
284
+ ['dangerous-frontmatter', _scanFrontmatterSafety],
285
+ ];
286
+
287
+ /**
288
+ * Read what a quarantined candidate SAYS, and refuse on a payload.
289
+ *
290
+ * `intake`'s other checks are properties of the file — symlink, extension, exec
291
+ * bit, size. Every one of them can be true of a file whose text tells an agent
292
+ * to hide its output from the user. "Inert" was a claim about the container that
293
+ * the gate read as a claim about the contents.
294
+ *
295
+ * TWO DELIBERATE DIFFERENCES FROM HOW THESE LINTERS RUN OVER THIS PACKAGE:
296
+ *
297
+ * 1. **Any HIGH refuses, whatever its weight.** The 0.25 confidence weighting is
298
+ * a false-positive containment device for THIS repository's own corpus, where
299
+ * a path containing `docs` or `examples` really is documentation somebody
300
+ * here wrote. A quarantined third-party candidate carries no such provenance,
301
+ * and a directory named `examples/` in it is an attacker's word for it. A
302
+ * refusal is also cheaper than a false pass in exactly this direction: the
303
+ * candidate is rejected and a human reads it.
304
+ * 2. **Every text extension is scanned, not just `.md`.** The candidate
305
+ * allow-list already bounds what may be present; a payload in the `.yml` a
306
+ * candidate ships is a payload.
307
+ *
308
+ * The refusal NAMES the linter, because "the content scan refused this" sends a
309
+ * reader to five detectors.
310
+ */
311
+ export function scanCandidateContent(candidateDir: string): string[] {
312
+ const out: string[] = [];
313
+ for (const sf of sl.iter_corpus(['.'], [...ALLOWED_EXT], candidateDir)) {
314
+ for (const [check, scan] of CONTENT_LINTERS) {
315
+ for (const finding of scan(sf)) {
316
+ if (finding.severity !== 'HIGH') continue;
317
+ const loc = finding.line ? `${sf.rel}:${String(finding.line)}` : sf.rel;
318
+ out.push(`${loc}: flagged by ${check} — ${finding.message}`);
319
+ }
320
+ }
321
+ }
322
+ return out;
323
+ }
324
+
265
325
  /**
266
326
  * Refuse a candidate that is not inert.
267
327
  *
@@ -296,6 +356,15 @@ export function intake(candidateDir: string): IntakeResult {
296
356
  if (files.length === 0) {
297
357
  refusals.push('candidate directory is empty');
298
358
  }
359
+ // Content, after the file properties. Ordering is not cosmetic: a candidate
360
+ // that fails an inertness check may not be safe to READ (a symlink out of
361
+ // quarantine is the case the read-time re-check exists for), so the bytes
362
+ // are only opened once the container has been judged.
363
+ if (refusals.length === 0) {
364
+ for (const flagged of scanCandidateContent(candidateDir)) {
365
+ refusals.push(flagged);
366
+ }
367
+ }
299
368
  return { accepted: refusals.length === 0, refusals, files_seen: files.length };
300
369
  }
301
370
 
@@ -1,172 +0,0 @@
1
- import { createRequire as __acCreateRequire } from 'node:module'; const require = globalThis.require ?? __acCreateRequire(import.meta.url);
2
- import {
3
- read_lockfile
4
- } from "./chunk-T7L2QK3K.js";
5
-
6
- // src/scripts/_lib/turn_end_refusals.ts
7
- import * as crypto from "node:crypto";
8
- import * as fs from "node:fs";
9
- import * as path from "node:path";
10
- var DETECTOR_IDS = [
11
- "promissory",
12
- "language",
13
- "verification",
14
- "completion"
15
- ];
16
- var REFUSAL_STATE_REL = path.join("agents", "runtime", "state", "turn-end-gate");
17
- function refusalStateDir(workspaceRoot) {
18
- return path.join(workspaceRoot, REFUSAL_STATE_REL);
19
- }
20
- function deriveSessionKey(sessionId) {
21
- return crypto.createHash("sha256").update(sessionId).digest("hex").slice(0, 32);
22
- }
23
- function sessionRefusalFile(workspaceRoot, sessionKey) {
24
- return path.join(refusalStateDir(workspaceRoot), `${sessionKey}.json`);
25
- }
26
- function readSessionCounts(workspaceRoot, sessionId) {
27
- try {
28
- const raw = fs.readFileSync(
29
- sessionRefusalFile(workspaceRoot, deriveSessionKey(sessionId)),
30
- "utf-8"
31
- );
32
- const rec = parseRecord(raw);
33
- return rec === null ? null : countsOf(rec);
34
- } catch {
35
- return null;
36
- }
37
- }
38
- function emptyCounts() {
39
- return { promissory: 0, language: 0, verification: 0, completion: 0 };
40
- }
41
- function isDetector(v) {
42
- return typeof v === "string" && DETECTOR_IDS.includes(v);
43
- }
44
- function parseRecord(raw) {
45
- let decoded;
46
- try {
47
- decoded = JSON.parse(raw);
48
- } catch {
49
- return null;
50
- }
51
- if (typeof decoded !== "object" || decoded === null || Array.isArray(decoded)) return null;
52
- const o = decoded;
53
- if (typeof o["refused_at"] !== "string") return null;
54
- if (typeof o["refused_turn"] !== "number") return null;
55
- if (!isDetector(o["detector"])) return null;
56
- const rec = {
57
- refused_at: o["refused_at"],
58
- refused_turn: o["refused_turn"],
59
- detector: o["detector"]
60
- };
61
- if (typeof o["first_refused_at"] === "string") rec.first_refused_at = o["first_refused_at"];
62
- if (typeof o["agent_config_version"] === "string") {
63
- rec.agent_config_version = o["agent_config_version"];
64
- }
65
- const counts = o["counts"];
66
- if (typeof counts === "object" && counts !== null && !Array.isArray(counts)) {
67
- const parsed = {};
68
- for (const id of DETECTOR_IDS) {
69
- const n = counts[id];
70
- if (typeof n === "number" && Number.isFinite(n) && n >= 0) parsed[id] = n;
71
- }
72
- rec.counts = parsed;
73
- }
74
- return rec;
75
- }
76
- function countsOf(rec) {
77
- const out = emptyCounts();
78
- if (rec.counts === void 0) {
79
- out[rec.detector] = 1;
80
- return out;
81
- }
82
- let any = false;
83
- for (const id of DETECTOR_IDS) {
84
- const n = rec.counts[id];
85
- if (typeof n === "number" && n > 0) {
86
- out[id] = n;
87
- any = true;
88
- }
89
- }
90
- if (!any) out[rec.detector] = 1;
91
- return out;
92
- }
93
- function foldRefusal(prev, input) {
94
- const counts = prev === null ? emptyCounts() : countsOf(prev);
95
- for (const d of input.detectors) counts[d] += 1;
96
- const primary = input.detectors[0] ?? prev?.detector ?? "verification";
97
- const rec = {
98
- refused_at: input.at,
99
- refused_turn: input.turnOrdinal,
100
- detector: primary,
101
- first_refused_at: prev?.first_refused_at ?? prev?.refused_at ?? input.at,
102
- counts
103
- };
104
- if (input.version !== void 0) rec.agent_config_version = input.version;
105
- else if (prev?.agent_config_version !== void 0) {
106
- rec.agent_config_version = prev.agent_config_version;
107
- }
108
- return rec;
109
- }
110
- function readInstallBoundary() {
111
- try {
112
- const lock = read_lockfile();
113
- if (lock === null) return { version: null, installed_at: null };
114
- return {
115
- version: lock.agent_config_version ?? null,
116
- installed_at: lock.installed_at ?? null
117
- };
118
- } catch {
119
- return { version: null, installed_at: null };
120
- }
121
- }
122
- var REFUSAL_STATE_MAX_AGE_DAYS = 90;
123
- function pruneAgedRefusalState(workspaceRoot, opts = {}) {
124
- const maxAgeDays = opts.maxAgeDays ?? REFUSAL_STATE_MAX_AGE_DAYS;
125
- const now = opts.now ?? /* @__PURE__ */ new Date();
126
- const dir = refusalStateDir(workspaceRoot);
127
- const result = { scanned: 0, pruned: 0, kept: 0 };
128
- let entries;
129
- try {
130
- entries = fs.readdirSync(dir);
131
- } catch {
132
- return result;
133
- }
134
- const cutoffMs = now.getTime() - maxAgeDays * 24 * 60 * 60 * 1e3;
135
- for (const name of entries) {
136
- if (!name.endsWith(".json")) continue;
137
- result.scanned += 1;
138
- const file = path.join(dir, name);
139
- let rec = null;
140
- try {
141
- rec = parseRecord(fs.readFileSync(file, "utf-8"));
142
- } catch {
143
- rec = null;
144
- }
145
- if (rec === null) {
146
- result.kept += 1;
147
- continue;
148
- }
149
- const at = Date.parse(rec.refused_at);
150
- if (!Number.isFinite(at) || at >= cutoffMs) {
151
- result.kept += 1;
152
- continue;
153
- }
154
- try {
155
- fs.unlinkSync(file);
156
- result.pruned += 1;
157
- } catch {
158
- result.kept += 1;
159
- }
160
- }
161
- return result;
162
- }
163
-
164
- export {
165
- deriveSessionKey,
166
- sessionRefusalFile,
167
- readSessionCounts,
168
- parseRecord,
169
- foldRefusal,
170
- readInstallBoundary,
171
- pruneAgedRefusalState
172
- };
@@ -1,161 +0,0 @@
1
- import { createRequire as __acCreateRequire } from 'node:module'; const require = globalThis.require ?? __acCreateRequire(import.meta.url);
2
- import {
3
- statePathFor
4
- } from "./chunk-7PRE6ZV2.js";
5
- import {
6
- git_common_dir
7
- } from "./chunk-E4XIZDIB.js";
8
-
9
- // src/scripts/_lib/envelope_grounding.ts
10
- import { execFileSync } from "node:child_process";
11
- import * as fs from "node:fs";
12
- import * as path from "node:path";
13
-
14
- // src/scripts/_lib/git_env.ts
15
- var GIT_DISCOVERY_VARS = [
16
- "GIT_DIR",
17
- "GIT_WORK_TREE",
18
- "GIT_INDEX_FILE",
19
- "GIT_OBJECT_DIRECTORY",
20
- "GIT_ALTERNATE_OBJECT_DIRECTORIES",
21
- "GIT_COMMON_DIR",
22
- "GIT_NAMESPACE",
23
- "GIT_PREFIX"
24
- ];
25
- function gitEnv(base = process.env) {
26
- const env = { ...base };
27
- for (const key of GIT_DISCOVERY_VARS) {
28
- delete env[key];
29
- }
30
- return env;
31
- }
32
-
33
- // src/scripts/_lib/envelope_grounding.ts
34
- var MAX_FIELD_CHARS = 200;
35
- var MAX_PATHS = 40;
36
- function git(root, args) {
37
- try {
38
- return execFileSync("git", args, {
39
- cwd: root,
40
- env: gitEnv(),
41
- encoding: "utf-8",
42
- stdio: ["ignore", "pipe", "ignore"]
43
- }).trim();
44
- } catch {
45
- return null;
46
- }
47
- }
48
- function canonicalizeRepoIdentity(raw) {
49
- let s = raw.trim();
50
- s = s.replace(/^[a-z][a-z0-9+.-]*:\/\//i, "");
51
- s = s.replace(/^[^/@]+@/, "");
52
- s = s.replace(/^([^/:]+):(?!\/)/, "$1/");
53
- s = s.replace(/\/+$/, "");
54
- s = s.replace(/\.git$/i, "");
55
- const slash = s.indexOf("/");
56
- if (slash > 0) {
57
- s = s.slice(0, slash).toLowerCase() + s.slice(slash);
58
- } else {
59
- s = s.toLowerCase();
60
- }
61
- return s;
62
- }
63
- function collectRepoAnchor(root) {
64
- const remote = git(root, ["config", "--get", "remote.origin.url"]);
65
- let identity = null;
66
- if (remote) {
67
- identity = canonicalizeRepoIdentity(remote);
68
- } else {
69
- const common = git_common_dir(root);
70
- if (common !== null) {
71
- try {
72
- identity = fs.realpathSync(common);
73
- } catch {
74
- identity = common;
75
- }
76
- }
77
- }
78
- return {
79
- repo_identity: identity ? identity.slice(0, MAX_FIELD_CHARS) : null,
80
- branch: git(root, ["rev-parse", "--abbrev-ref", "HEAD"]),
81
- head: git(root, ["rev-parse", "HEAD"])
82
- };
83
- }
84
- function readLastVerify(root, session_id) {
85
- if (typeof session_id !== "string" || session_id.trim() === "") return null;
86
- try {
87
- const raw = fs.readFileSync(path.join(root, statePathFor(session_id)), "utf-8");
88
- const state = JSON.parse(raw);
89
- const lv = state["last_verification"];
90
- if (typeof lv !== "object" || lv === null || Array.isArray(lv)) return null;
91
- const rec = lv;
92
- const command = typeof rec["command"] === "string" ? rec["command"] : null;
93
- if (!command) return null;
94
- const at = typeof rec["at"] === "string" ? rec["at"] : "unknown time";
95
- const flat = `${command} @ ${at} (no exit status is recorded anywhere)`.replace(/\s+/g, " ").trim();
96
- return flat.slice(0, MAX_FIELD_CHARS);
97
- } catch {
98
- return null;
99
- }
100
- }
101
- function collectGrounding(root, session_id) {
102
- const anchor = collectRepoAnchor(root);
103
- const porcelain = git(root, ["status", "--porcelain"]);
104
- if (porcelain === null) {
105
- return { ...anchor, uncommitted_paths: [], status_summary: null, last_verify: readLastVerify(root, session_id) };
106
- }
107
- const lines = porcelain.split("\n").filter((l) => l.trim().length > 0);
108
- const paths = lines.map((l) => l.slice(3).trim()).filter((p) => p.length > 0 && p.length <= MAX_FIELD_CHARS).slice(0, MAX_PATHS);
109
- return {
110
- ...anchor,
111
- uncommitted_paths: paths,
112
- status_summary: lines.length === 0 ? "clean working tree" : `${lines.length} uncommitted path(s)${lines.length > MAX_PATHS ? `, first ${MAX_PATHS} listed` : ""}`,
113
- last_verify: readLastVerify(root, session_id)
114
- };
115
- }
116
- function describeDrift(envelope, actual) {
117
- if (typeof envelope !== "object" || envelope === null || Array.isArray(envelope)) return [];
118
- const e = envelope;
119
- const drift = [];
120
- const recordedIdentity = typeof e["repo_identity"] === "string" ? e["repo_identity"] : null;
121
- if (recordedIdentity && actual.repo_identity && recordedIdentity !== actual.repo_identity) {
122
- drift.push(
123
- `IDENTITY DRIFT: this envelope was written in "${recordedIdentity}", you are in "${actual.repo_identity}". Re-verify every path before acting \u2014 same branch name does not mean same working tree.`
124
- );
125
- }
126
- const recordedBranch = typeof e["branch"] === "string" ? e["branch"] : null;
127
- if (recordedBranch && actual.branch && recordedBranch !== actual.branch) {
128
- drift.push(
129
- `BRANCH DRIFT: envelope written on "${recordedBranch}", checkout is on "${actual.branch}".`
130
- );
131
- }
132
- const recordedHead = typeof e["head"] === "string" ? e["head"] : null;
133
- if (recordedHead && actual.head && recordedHead !== actual.head) {
134
- drift.push(
135
- `COMMIT DRIFT: envelope written at ${recordedHead.slice(0, 12)}, HEAD is now ${actual.head.slice(0, 12)} \u2014 re-read anything the envelope quotes.`
136
- );
137
- }
138
- return drift;
139
- }
140
-
141
- // src/scripts/_lib/recycle_envelope_paths.ts
142
- import * as path2 from "node:path";
143
- var RECYCLE_ENVELOPE_REL = path2.join("agents", "runtime", "state", "recycle-envelope.json");
144
- var RECYCLE_CONSUMED_REL = path2.join(
145
- "agents",
146
- "runtime",
147
- "state",
148
- "recycle-envelope.consumed.json"
149
- );
150
- var RECYCLE_MAX_AGE_HOURS = 48;
151
- var RECYCLE_ENVELOPE_MAX_BYTES = 6144;
152
-
153
- export {
154
- collectRepoAnchor,
155
- collectGrounding,
156
- describeDrift,
157
- RECYCLE_ENVELOPE_REL,
158
- RECYCLE_CONSUMED_REL,
159
- RECYCLE_MAX_AGE_HOURS,
160
- RECYCLE_ENVELOPE_MAX_BYTES
161
- };