@event4u/agent-config 9.16.0 → 9.18.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 (227) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/AGENTS.md +1 -1
  3. package/CHANGELOG.md +80 -130
  4. package/README.md +1 -1
  5. package/dist/agent-src/commands/agent-handoff.md +100 -48
  6. package/dist/agent-src/commands/challenge-me/vision.md +5 -0
  7. package/dist/agent-src/commands/commit.md +1 -1
  8. package/dist/agent-src/commands/condense.md +19 -64
  9. package/dist/agent-src/commands/evals/agent-handoff.json +13 -1
  10. package/dist/agent-src/commands/evals/rule-compliance-audit.json +46 -0
  11. package/dist/agent-src/commands/feature/plan.md +20 -0
  12. package/dist/agent-src/commands/feature/roadmap.md +19 -0
  13. package/dist/agent-src/commands/memory/promote.md +14 -1
  14. package/dist/agent-src/commands/mode.md +9 -5
  15. package/dist/agent-src/commands/optimize/deep.md +72 -11
  16. package/dist/agent-src/commands/pr/create.md +29 -0
  17. package/dist/agent-src/commands/roadmap/create.md +29 -0
  18. package/dist/agent-src/commands/rule-compliance-audit.md +28 -5
  19. package/dist/agent-src/contexts/authority/commit-mechanics.md +5 -5
  20. package/dist/agent-src/contexts/communication/rules-auto/guidelines-mechanics.md +1 -0
  21. package/dist/agent-src/contexts/communication/rules-auto/skill-quality-mechanics.md +1 -1
  22. package/dist/agent-src/contexts/communication/rules-auto/think-before-action-mechanics.md +6 -1
  23. package/dist/agent-src/contexts/communication/rules-auto/token-efficiency-mechanics.md +4 -1
  24. package/dist/agent-src/contexts/execution/autonomy-mechanics.md +27 -1
  25. package/dist/agent-src/contexts/execution/plan-confidence-gate.md +133 -0
  26. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +5 -2
  27. package/dist/agent-src/profiles/agency.yml +3 -3
  28. package/dist/agent-src/profiles/content_creator.yml +2 -2
  29. package/dist/agent-src/profiles/developer.yml +2 -2
  30. package/dist/agent-src/profiles/finance.yml +1 -1
  31. package/dist/agent-src/profiles/founder.yml +2 -2
  32. package/dist/agent-src/profiles/ops.yml +2 -2
  33. package/dist/agent-src/rules/active-remediation.md +2 -0
  34. package/dist/agent-src/rules/architecture.md +4 -2
  35. package/dist/agent-src/rules/augment-edit-discipline.md +2 -0
  36. package/dist/agent-src/rules/autonomous-execution.md +1 -1
  37. package/dist/agent-src/rules/brand-consistency.md +10 -62
  38. package/dist/agent-src/rules/brand-source-of-truth.md +62 -11
  39. package/dist/agent-src/rules/broken-access-control.md +4 -0
  40. package/dist/agent-src/rules/cli-output-handling.md +5 -0
  41. package/dist/agent-src/rules/code-comment-discipline.md +3 -0
  42. package/dist/agent-src/rules/commit-conventions.md +2 -0
  43. package/dist/agent-src/rules/context-hygiene.md +3 -2
  44. package/dist/agent-src/rules/cross-source-consistency.md +3 -0
  45. package/dist/agent-src/rules/design-fidelity.md +2 -0
  46. package/dist/agent-src/rules/doc-screenshot-hygiene.md +2 -0
  47. package/dist/agent-src/rules/docker-commands.md +4 -0
  48. package/dist/agent-src/rules/domain-adoption-policy.md +2 -0
  49. package/dist/agent-src/rules/domain-safety-disclaimer.md +5 -2
  50. package/dist/agent-src/rules/domain-safety-pii.md +1 -1
  51. package/dist/agent-src/rules/engineering-safety-floor.md +2 -0
  52. package/dist/agent-src/rules/framework-neutrality-in-generic-skills.md +9 -0
  53. package/dist/agent-src/rules/image-likeness-and-rights.md +4 -0
  54. package/dist/agent-src/rules/improve-before-implement.md +4 -0
  55. package/dist/agent-src/rules/laravel-routing.md +4 -0
  56. package/dist/agent-src/rules/legal-safety-floor.md +3 -0
  57. package/dist/agent-src/rules/lethal-trifecta-guard.md +3 -0
  58. package/dist/agent-src/rules/media-governance-routing.md +5 -0
  59. package/dist/agent-src/rules/minimal-safe-diff.md +2 -0
  60. package/dist/agent-src/rules/no-roadmap-references.md +2 -0
  61. package/dist/agent-src/rules/notes-first-reasoning.md +2 -0
  62. package/dist/agent-src/rules/output-discipline.md +9 -2
  63. package/dist/agent-src/rules/php-coding.md +2 -0
  64. package/dist/agent-src/rules/prefer-enums-over-literals.md +3 -0
  65. package/dist/agent-src/rules/provider-lifecycle-discipline.md +5 -0
  66. package/dist/agent-src/rules/roadmap-ci-steps-policy.md +2 -0
  67. package/dist/agent-src/rules/roadmap-progress-sync.md +2 -0
  68. package/dist/agent-src/rules/rule-type-governance.md +2 -0
  69. package/dist/agent-src/rules/scale-discipline.md +3 -0
  70. package/dist/agent-src/rules/secret-vcs-guard.md +2 -0
  71. package/dist/agent-src/rules/security-sensitive-stop.md +9 -2
  72. package/dist/agent-src/rules/senior-engineering-discipline.md +6 -0
  73. package/dist/agent-src/rules/skill-quality.md +2 -0
  74. package/dist/agent-src/rules/source-confidentiality.md +4 -0
  75. package/dist/agent-src/rules/source-discovery-gate.md +3 -0
  76. package/dist/agent-src/rules/source-of-truth.md +2 -0
  77. package/dist/agent-src/rules/strategy-safety-floor.md +2 -0
  78. package/dist/agent-src/rules/think-before-action.md +5 -0
  79. package/dist/agent-src/rules/token-budget-discipline.md +2 -2
  80. package/dist/agent-src/rules/ui-audit-gate.md +2 -0
  81. package/dist/agent-src/rules/user-interaction.md +3 -4
  82. package/dist/agent-src/skills/build-buy-partner/SKILL.md +1 -1
  83. package/dist/agent-src/skills/copilot-agents-optimization/SKILL.md +4 -4
  84. package/dist/agent-src/skills/copilot-config/SKILL.md +1 -1
  85. package/dist/agent-src/skills/deep-reading-analyst/SKILL.md +7 -4
  86. package/dist/agent-src/skills/description-assist/SKILL.md +1 -1
  87. package/dist/agent-src/skills/design-intelligence/SKILL.md +6 -180
  88. package/dist/agent-src/skills/design-intelligence/references/context-and-registers.md +100 -0
  89. package/dist/agent-src/skills/design-intelligence/references/integration-mapping.md +91 -0
  90. package/dist/agent-src/skills/design-review/SKILL.md +4 -141
  91. package/dist/agent-src/skills/design-review/references/review-communication.md +92 -0
  92. package/dist/agent-src/skills/design-review/references/verification-automation.md +61 -0
  93. package/dist/agent-src/skills/existing-ui-audit/SKILL.md +8 -27
  94. package/dist/agent-src/skills/existing-ui-audit/references/anti-slop-cross-reference.md +31 -0
  95. package/dist/agent-src/skills/fe-design/SKILL.md +4 -289
  96. package/dist/agent-src/skills/fe-design/references/design-patterns.md +227 -0
  97. package/dist/agent-src/skills/fe-design/references/design-read-and-memory.md +75 -0
  98. package/dist/agent-src/skills/market-entry-analysis/SKILL.md +1 -1
  99. package/dist/agent-src/skills/persona-writing/SKILL.md +2 -2
  100. package/dist/agent-src/skills/readme-reviewer/SKILL.md +2 -1
  101. package/dist/agent-src/skills/roadmap-management/SKILL.md +10 -0
  102. package/dist/agent-src/skills/roadmap-writing/SKILL.md +12 -1
  103. package/dist/agent-src/skills/rule-writing/SKILL.md +4 -2
  104. package/dist/agent-src/skills/skill-reviewer/SKILL.md +1 -1
  105. package/dist/agent-src/skills/skill-writing/SKILL.md +2 -2
  106. package/dist/agent-src/skills/tailwind-engineer/SKILL.md +3 -1
  107. package/dist/agent-src/skills/token-optimizer/SKILL.md +2 -2
  108. package/dist/agent-src/skills/ui-component-architect/SKILL.md +6 -3
  109. package/dist/agent-src/templates/agent-settings.md +13 -15
  110. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +6 -2
  111. package/dist/agent-src/templates/persona.md +2 -2
  112. package/dist/agent-src/templates/roadmaps.md +13 -1
  113. package/dist/agent-src/templates/rule.md +1 -1
  114. package/dist/agent-src/templates/scripts/work_engine/directives/ui/_fix_lane.ts +52 -0
  115. package/dist/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +8 -0
  116. package/dist/agent-src/templates/scripts/work_engine/directives/ui/design.ts +6 -0
  117. package/dist/agent-src/templates/scripts/work_engine/intent/classify.ts +56 -4
  118. package/dist/agent-src/templates/skill.md +1 -1
  119. package/dist/cli/registry.js +3 -0
  120. package/dist/cli/registry.js.map +1 -1
  121. package/dist/cli-delegate/chunk-AIIUU73N.js +2040 -0
  122. package/dist/cli-delegate/{chunk-ZXXJLL5K.js → chunk-BSLUHV2F.js} +127 -1487
  123. package/dist/cli-delegate/{chunk-H574KOUH.js → chunk-ELMFOQPW.js} +35 -1
  124. package/dist/cli-delegate/chunk-LF2KKJ4Q.js +183 -0
  125. package/dist/cli-delegate/chunk-VSYT7OHO.js +1382 -0
  126. package/dist/cli-delegate/chunk-XGA2NTU4.js +3727 -0
  127. package/dist/cli-delegate/cmd_conformance.js +4 -3
  128. package/dist/cli-delegate/cmd_converge.js +3 -3
  129. package/dist/cli-delegate/cmd_doctor.js +3 -2
  130. package/dist/cli-delegate/cmd_explain.js +36 -36
  131. package/dist/cli-delegate/cmd_export.js +4 -4
  132. package/dist/cli-delegate/cmd_handoff.js +1169 -0
  133. package/dist/cli-delegate/cmd_memory_get.js +35 -3754
  134. package/dist/cli-delegate/cmd_migrate.js +2 -2
  135. package/dist/cli-delegate/cmd_prune.js +4 -4
  136. package/dist/cli-delegate/cmd_route_audit.js +249 -0
  137. package/dist/cli-delegate/cmd_route_explain.js +285 -0
  138. package/dist/cli-delegate/cmd_settings_migrate.js +4 -4
  139. package/dist/cli-delegate/cmd_sync.js +4 -4
  140. package/dist/cli-delegate/cmd_uninstall.js +4 -4
  141. package/dist/cli-delegate/cmd_update.js +3 -3
  142. package/dist/cli-delegate/cmd_upgrade.js +3 -3
  143. package/dist/cli-delegate/cmd_validate.js +4 -4
  144. package/dist/cli-delegate/cmd_versions.js +3 -3
  145. package/dist/discovery/deprecation-report.md +1 -1
  146. package/dist/discovery/discovery-manifest.json +84 -84
  147. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  148. package/dist/discovery/discovery-manifest.summary.md +1 -1
  149. package/dist/discovery/orphan-report.md +1 -1
  150. package/dist/discovery/packs.json +2 -2
  151. package/dist/discovery/trust-report.md +1 -1
  152. package/dist/discovery/workspaces.json +2 -2
  153. package/dist/hooks/dispatch.js +602 -507
  154. package/dist/install/install.mjs +34 -0
  155. package/dist/mcp/awesome-mcp-servers.row.md +1 -1
  156. package/dist/mcp/mcp-cloudflare-catalogue.json +2 -2
  157. package/dist/mcp/registry-manifest.json +4 -4
  158. package/dist/mcp/server.json +2 -2
  159. package/dist/mcp/server.mjs +25 -3
  160. package/dist/router.json +1 -1
  161. package/dist/scripts/_lib/agent_src.js +39 -6
  162. package/dist/scripts/_lib/agent_src.js.map +1 -1
  163. package/dist/server/schemas/settings.js +25 -0
  164. package/dist/server/schemas/settings.js.map +1 -1
  165. package/docs/guidelines/abstraction-thresholds.md +53 -0
  166. package/docs/guidelines/agent-infra/agent-interaction-and-decision-quality.md +1 -1
  167. package/docs/guidelines/agent-infra/asking-and-brevity-examples.md +7 -0
  168. package/docs/guidelines/agent-infra/context-hygiene-mechanics.md +2 -2
  169. package/docs/guidelines/agent-infra/minimal-safe-diff-mechanics.md +5 -3
  170. package/docs/guidelines/agent-infra/size-and-scope.md +8 -2
  171. package/docs/guidelines/component-oriented-and-oop-development.md +11 -6
  172. package/docs/guidelines/docs/readme-size-and-splitting.md +5 -2
  173. package/llms.txt +1 -0
  174. package/package.json +1 -1
  175. package/src/agent-src/templates/scripts/work_engine/directives/ui/_fix_lane.ts +52 -0
  176. package/src/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +8 -0
  177. package/src/agent-src/templates/scripts/work_engine/directives/ui/design.ts +6 -0
  178. package/src/agent-src/templates/scripts/work_engine/intent/classify.ts +56 -4
  179. package/src/config/agent-settings.template.yml +68 -0
  180. package/src/config/evaluator-budgets.json +8 -11
  181. package/src/config/gate-coverage.yml +70 -0
  182. package/src/scripts/_cli/cmd_explain.ts +41 -36
  183. package/src/scripts/_cli/cmd_handoff.ts +405 -0
  184. package/src/scripts/_cli/cmd_route_audit.ts +290 -0
  185. package/src/scripts/_cli/cmd_route_explain.ts +269 -0
  186. package/src/scripts/_cli/handoff_generate.ts +453 -0
  187. package/src/scripts/_cli/handoff_sessions.ts +435 -0
  188. package/src/scripts/_dispatch.bash +32 -0
  189. package/src/scripts/_lib/agent_src.ts +36 -6
  190. package/src/scripts/_lib/git_env.ts +40 -0
  191. package/src/scripts/_lib/md_table.ts +27 -0
  192. package/src/scripts/_lib/planning_settings.ts +43 -0
  193. package/src/scripts/_lib/release_material.ts +156 -0
  194. package/src/scripts/_lib/router_match.ts +301 -0
  195. package/src/scripts/annotate_r1_outcomes.ts +258 -0
  196. package/src/scripts/check_completion_review.ts +1140 -0
  197. package/src/scripts/check_condensation.ts +2 -1
  198. package/src/scripts/check_finding_dispositions.ts +278 -0
  199. package/src/scripts/check_pack_size.ts +4 -3
  200. package/src/scripts/check_references.ts +1 -0
  201. package/src/scripts/check_release_highlights.ts +300 -0
  202. package/src/scripts/check_release_pr_shape.ts +4 -0
  203. package/src/scripts/check_release_surface_equality.ts +132 -0
  204. package/src/scripts/check_reply_consistency.ts +187 -138
  205. package/src/scripts/compile_router.ts +20 -3
  206. package/src/scripts/dispatch_r2_reviewer.ts +968 -0
  207. package/src/scripts/eval_ui_triviality.ts +156 -0
  208. package/src/scripts/handoff_context_hook.ts +148 -0
  209. package/src/scripts/hook_manifest.yaml +17 -7
  210. package/src/scripts/hooks/concern_registry.ts +2 -0
  211. package/src/scripts/lint_abstraction_thresholds.ts +327 -0
  212. package/src/scripts/lint_framework_leakage_allowlist.json +2 -2
  213. package/src/scripts/lint_plan_risk_register.ts +931 -0
  214. package/src/scripts/lint_profile_personas.ts +153 -0
  215. package/src/scripts/lint_token_budget_discipline.ts +148 -0
  216. package/src/scripts/lint_trigger_collisions.ts +257 -0
  217. package/src/scripts/release.ts +156 -32
  218. package/src/scripts/release_drill.ts +491 -0
  219. package/src/scripts/router_telemetry.ts +16 -290
  220. package/src/scripts/schemas/command.schema.json +33 -0
  221. package/src/scripts/schemas/rule.schema.json +10 -0
  222. package/src/scripts/self_review_gate.ts +41 -3
  223. package/src/scripts/skill_linter.ts +3 -1
  224. package/src/scripts/smoke/router.sh +1 -1
  225. package/src/scripts/update_counts.ts +60 -12
  226. package/src/server/schemas/settings.ts +41 -0
  227. package/dist/cli-delegate/{chunk-XX6TOPD7.js → chunk-WR6GTRHJ.js} +3 -3
@@ -0,0 +1,453 @@
1
+ /**
2
+ * Handoff generator (road-to-agent-handoff-resume Phase 2). Takes a session
3
+ * picked by `handoff_sessions.ts`, reads its FULL transcript, and emits the
4
+ * handoff document with the exact section set from
5
+ * `src/domains/meta/agent-handoff/command.md` step 2 — deterministically:
6
+ * no LLM call, reproducible, privacy-floored per line.
7
+ *
8
+ * - User messages are preserved VERBATIM (the template's load-bearing
9
+ * rule). A prompt containing any line the low-impact redactor refuses
10
+ * is withheld whole (drop, never rewrite) and counted in the
11
+ * `[N line(s) withheld by privacy floor]` marker.
12
+ * - Assistant/tool content is extracted with the hot-context heuristics:
13
+ * last intents, tool-result changes, failure-pattern regex for errors.
14
+ * - Output lands atomically in `agents/runtime/state/handoff-context.md`
15
+ * with `Generated:` / `Source-Session:` / `Branch:` parse anchors for
16
+ * the session_start hook, hard-capped at ~1200 words (priority-trim
17
+ * like `build_hot_context`).
18
+ * - `--llm` polish is a seam only in v1: requesting it raises a clear
19
+ * "not implemented" error. No spend without the flag, and none with it.
20
+ */
21
+
22
+ import { spawnSync } from 'node:child_process';
23
+ import * as fs from 'node:fs';
24
+ import * as path from 'node:path';
25
+ import process from 'node:process';
26
+
27
+ import { hardenedSpawnEnv } from '../_lib/spawn_env.js';
28
+ import { redact_low_impact_entry } from '../ai_council/redact_low_impact_entry.js';
29
+ import { read_entries } from '../chat_history.js';
30
+ import { HANDOFF_CONTEXT_REL } from '../handoff_context_hook.js';
31
+ import type { HandoffSession } from './handoff_sessions.js';
32
+
33
+ export const WORD_CAP = 1200;
34
+
35
+ const SNIPPET_CHARS = 200;
36
+ const DONE_SNIPPET_CHARS = 400;
37
+ const MAX_ERRORS = 5;
38
+ const MAX_DECISIONS = 5;
39
+ const MAX_FILES = 15;
40
+
41
+ const FAILURE_RE = /\b(fail(ing|ed|ure)?|error|exit[= ][1-9]|red\b|broken)\b/i;
42
+ const DECISION_RE = /\b(decided|decision|chose|choosing|locked|we'll use|we will use)\b/i;
43
+ const FILE_PATH_RE = /(?:src|tests|docs|agents|scripts|config)\/[\w./-]+\.\w{1,8}/g;
44
+ const ROADMAP_RE = /agents\/roadmaps\/[\w.-]+\.md/g;
45
+
46
+ export class LlmPolishNotImplementedError extends Error {
47
+ constructor() {
48
+ super(
49
+ '--llm polish is a v1 seam and not implemented yet — ' +
50
+ 'the deterministic handoff (default) is the supported path.',
51
+ );
52
+ }
53
+ }
54
+
55
+ // ---------------------------------------------------------------------
56
+ // unified transcript model
57
+ // ---------------------------------------------------------------------
58
+
59
+ export interface TranscriptTurn {
60
+ kind: 'user' | 'assistant' | 'tool';
61
+ text: string;
62
+ ts: string | null;
63
+ tool?: string;
64
+ }
65
+
66
+ export interface TranscriptData {
67
+ turns: TranscriptTurn[];
68
+ branch: string | null;
69
+ }
70
+
71
+ function _read_lines(file: string): string[] {
72
+ try {
73
+ return fs.readFileSync(file, 'utf-8').split('\n');
74
+ } catch {
75
+ return [];
76
+ }
77
+ }
78
+
79
+ function _parse_json(line: string): Record<string, unknown> | null {
80
+ const trimmed = line.trim();
81
+ if (!trimmed) return null;
82
+ try {
83
+ const parsed = JSON.parse(trimmed) as unknown;
84
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
85
+ return parsed as Record<string, unknown>;
86
+ }
87
+ } catch {
88
+ // fail-open
89
+ }
90
+ return null;
91
+ }
92
+
93
+ function _from_chat_history(sessionId: string, chatHistoryPath: string | null): TranscriptData {
94
+ let entries: Array<Record<string, unknown>>;
95
+ try {
96
+ entries = read_entries({ session: sessionId, path: chatHistoryPath, last: null });
97
+ } catch {
98
+ entries = [];
99
+ }
100
+ const turns: TranscriptTurn[] = [];
101
+ for (const e of entries) {
102
+ const text = typeof e.text === 'string' ? e.text : '';
103
+ if (!text) continue;
104
+ const t = String(e.t ?? '');
105
+ const kind: TranscriptTurn['kind'] = t === 'user' ? 'user' : t === 'tool' ? 'tool' : 'assistant';
106
+ turns.push({
107
+ kind,
108
+ text,
109
+ ts: typeof e.ts === 'string' ? e.ts : null,
110
+ ...(typeof e.tool === 'string' && e.tool ? { tool: e.tool } : {}),
111
+ });
112
+ }
113
+ return { turns, branch: null };
114
+ }
115
+
116
+ function _cc_text_from_content(content: unknown): string {
117
+ if (typeof content === 'string') return content;
118
+ if (Array.isArray(content)) {
119
+ return content
120
+ .map((block) => {
121
+ if (block && typeof block === 'object' && !Array.isArray(block)) {
122
+ const b = block as Record<string, unknown>;
123
+ if (b.type === 'text' && typeof b.text === 'string') return b.text;
124
+ }
125
+ return '';
126
+ })
127
+ .filter((t) => t)
128
+ .join('\n');
129
+ }
130
+ return '';
131
+ }
132
+
133
+ function _from_claude_transcript(file: string): TranscriptData {
134
+ const turns: TranscriptTurn[] = [];
135
+ let branch: string | null = null;
136
+ for (const line of _read_lines(file)) {
137
+ const rec = _parse_json(line);
138
+ if (!rec || rec.isSidechain === true) continue;
139
+ if (!branch && typeof rec.gitBranch === 'string' && rec.gitBranch) branch = rec.gitBranch;
140
+ const ts = typeof rec.timestamp === 'string' ? rec.timestamp : null;
141
+ const msg =
142
+ rec.message && typeof rec.message === 'object' && !Array.isArray(rec.message)
143
+ ? (rec.message as Record<string, unknown>)
144
+ : null;
145
+ if (!msg) continue;
146
+ if (rec.type === 'user') {
147
+ // real prompts have string content; tool_results arrive as lists
148
+ if (typeof msg.content === 'string') {
149
+ const text = msg.content.trim();
150
+ if (text && !text.startsWith('<') && !text.startsWith('Caveat:')) {
151
+ turns.push({ kind: 'user', text, ts });
152
+ }
153
+ } else {
154
+ const text = _cc_text_from_content(msg.content).trim();
155
+ if (text) turns.push({ kind: 'tool', text, ts });
156
+ }
157
+ } else if (rec.type === 'assistant') {
158
+ const text = _cc_text_from_content(msg.content).trim();
159
+ if (text) turns.push({ kind: 'assistant', text, ts });
160
+ }
161
+ }
162
+ return { turns, branch };
163
+ }
164
+
165
+ function _from_codex_session(file: string): TranscriptData {
166
+ const turns: TranscriptTurn[] = [];
167
+ let branch: string | null = null;
168
+ for (const line of _read_lines(file)) {
169
+ const rec = _parse_json(line);
170
+ if (!rec) continue;
171
+ const ts = typeof rec.timestamp === 'string' ? rec.timestamp : null;
172
+ const p =
173
+ rec.payload && typeof rec.payload === 'object' && !Array.isArray(rec.payload)
174
+ ? (rec.payload as Record<string, unknown>)
175
+ : null;
176
+ if (!p) continue;
177
+ if (rec.type === 'session_meta') {
178
+ const git =
179
+ p.git && typeof p.git === 'object' && !Array.isArray(p.git)
180
+ ? (p.git as Record<string, unknown>)
181
+ : {};
182
+ if (typeof git.branch === 'string' && git.branch) branch = git.branch;
183
+ } else if (rec.type === 'event_msg' && p.type === 'user_message') {
184
+ const text = typeof p.message === 'string' ? p.message.trim() : '';
185
+ if (text && !text.startsWith('<')) turns.push({ kind: 'user', text, ts });
186
+ } else if (rec.type === 'event_msg' && p.type === 'agent_message') {
187
+ const text = typeof p.message === 'string' ? p.message.trim() : '';
188
+ if (text) turns.push({ kind: 'assistant', text, ts });
189
+ }
190
+ }
191
+ return { turns, branch };
192
+ }
193
+
194
+ export function load_transcript(
195
+ session: Pick<HandoffSession, 'id' | 'source' | 'transcriptPath'>,
196
+ options: { chatHistoryPath?: string | null } = {},
197
+ ): TranscriptData {
198
+ if (session.source === 'chat-history') {
199
+ return _from_chat_history(session.id, options.chatHistoryPath ?? null);
200
+ }
201
+ if (!session.transcriptPath) return { turns: [], branch: null };
202
+ return session.source === 'claude-transcript'
203
+ ? _from_claude_transcript(session.transcriptPath)
204
+ : _from_codex_session(session.transcriptPath);
205
+ }
206
+
207
+ // ---------------------------------------------------------------------
208
+ // deterministic section extraction
209
+ // ---------------------------------------------------------------------
210
+
211
+ function _snippet(text: string, max: number): string {
212
+ const oneLine = text.replace(/\s+/g, ' ').trim();
213
+ return oneLine.length <= max ? oneLine : oneLine.slice(0, max - 1) + '…';
214
+ }
215
+
216
+ function _word_count(text: string): number {
217
+ return text.split(/\s+/).filter((w) => w.length > 0).length;
218
+ }
219
+
220
+ /** Privacy floor for NON-verbatim items: drop refused lines, count them. */
221
+ function _redact_items(items: string[]): { kept: string[]; dropped: number } {
222
+ const kept: string[] = [];
223
+ let dropped = 0;
224
+ for (const item of items) {
225
+ try {
226
+ if (redact_low_impact_entry(item).ok) kept.push(item);
227
+ else dropped += 1;
228
+ } catch {
229
+ dropped += 1; // fail-closed per line
230
+ }
231
+ }
232
+ return { kept, dropped };
233
+ }
234
+
235
+ /**
236
+ * Verbatim items: a prompt is included ONLY when every one of its lines
237
+ * passes the redactor — otherwise it is withheld whole and its failing
238
+ * lines are counted (drop, never rewrite).
239
+ */
240
+ function _redact_verbatim(prompts: string[]): { kept: string[]; withheldLines: number } {
241
+ const kept: string[] = [];
242
+ let withheldLines = 0;
243
+ for (const prompt of prompts) {
244
+ const lines = prompt.split('\n');
245
+ let failing = 0;
246
+ for (const line of lines) {
247
+ try {
248
+ if (!redact_low_impact_entry(line).ok) failing += 1;
249
+ } catch {
250
+ failing += 1;
251
+ }
252
+ }
253
+ if (failing === 0) kept.push(prompt);
254
+ else withheldLines += failing;
255
+ }
256
+ return { kept, withheldLines };
257
+ }
258
+
259
+ function _current_head(root: string): string | null {
260
+ try {
261
+ const proc = spawnSync('git', ['log', '--oneline', '-1'], {
262
+ cwd: root,
263
+ encoding: 'utf-8',
264
+ timeout: 5000,
265
+ env: hardenedSpawnEnv(),
266
+ });
267
+ if (proc.status === 0) return (proc.stdout || '').trim() || null;
268
+ } catch {
269
+ // fall through
270
+ }
271
+ return null;
272
+ }
273
+
274
+ function _current_branch(root: string): string {
275
+ try {
276
+ const proc = spawnSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {
277
+ cwd: root,
278
+ encoding: 'utf-8',
279
+ timeout: 5000,
280
+ env: hardenedSpawnEnv(),
281
+ });
282
+ if (proc.status === 0) return (proc.stdout || '').trim() || 'unknown';
283
+ } catch {
284
+ // fall through
285
+ }
286
+ return 'unknown';
287
+ }
288
+
289
+ export interface BuildOptions {
290
+ cwd?: string;
291
+ chatHistoryPath?: string | null;
292
+ now?: Date;
293
+ wordCap?: number;
294
+ /** v1 seam — requesting it throws LlmPolishNotImplementedError. */
295
+ llm?: boolean;
296
+ }
297
+
298
+ export function build_handoff(
299
+ session: Pick<HandoffSession, 'id' | 'source' | 'transcriptPath' | 'branch'>,
300
+ options: BuildOptions = {},
301
+ ): string {
302
+ if (options.llm) throw new LlmPolishNotImplementedError();
303
+
304
+ const root = options.cwd ?? process.cwd();
305
+ const now = options.now ?? new Date();
306
+ const wordCap = options.wordCap ?? WORD_CAP;
307
+ const transcript = load_transcript(session, { chatHistoryPath: options.chatHistoryPath ?? null });
308
+
309
+ const users = transcript.turns.filter((t) => t.kind === 'user');
310
+ const assistants = transcript.turns.filter((t) => t.kind === 'assistant');
311
+ const toolish = transcript.turns.filter((t) => t.kind !== 'user');
312
+
313
+ // User instructions — verbatim or withheld, never rewritten.
314
+ const verbatim = _redact_verbatim(users.map((t) => t.text));
315
+
316
+ // Done — the session's final assistant text.
317
+ const lastAssistant = assistants[assistants.length - 1]?.text ?? '';
318
+ const done = _redact_items(lastAssistant ? [_snippet(lastAssistant, DONE_SNIPPET_CHARS)] : []);
319
+
320
+ // Open + Resume pointer — the last user ask is what was in flight.
321
+ const lastUser = users[users.length - 1]?.text ?? '';
322
+ const open = _redact_items(lastUser ? [`in-flight ask: ${_snippet(lastUser, SNIPPET_CHARS)}`] : []);
323
+ const resume = _redact_items(
324
+ lastUser ? [`continue from the last user ask: ${_snippet(lastUser, SNIPPET_CHARS)}`] : [],
325
+ );
326
+
327
+ // Errors + fixes — failure-pattern turns, oldest→newest, last 5.
328
+ const errors = _redact_items(
329
+ toolish
330
+ .filter((t) => FAILURE_RE.test(t.text))
331
+ .slice(-MAX_ERRORS)
332
+ .map((t) => _snippet(`${t.tool ? `${t.tool}: ` : ''}${t.text}`, SNIPPET_CHARS)),
333
+ );
334
+
335
+ // Key decisions — decision-pattern assistant turns, last 5.
336
+ const decisions = _redact_items(
337
+ assistants
338
+ .filter((t) => DECISION_RE.test(t.text))
339
+ .slice(-MAX_DECISIONS)
340
+ .map((t) => _snippet(t.text, SNIPPET_CHARS)),
341
+ );
342
+
343
+ // Relevant files — path mentions across the whole transcript, deduped.
344
+ const fileSet = new Set<string>();
345
+ for (const t of transcript.turns) {
346
+ for (const m of t.text.match(FILE_PATH_RE) ?? []) fileSet.add(m);
347
+ }
348
+ const files = _redact_items([...fileSet].slice(-MAX_FILES));
349
+
350
+ // Roadmap — last roadmap-path mention, when present.
351
+ let roadmap: string | null = null;
352
+ for (const t of transcript.turns) {
353
+ for (const m of t.text.match(ROADMAP_RE) ?? []) roadmap = m;
354
+ }
355
+
356
+ const branch = session.branch ?? transcript.branch ?? 'unknown';
357
+ const droppedTotal =
358
+ done.dropped + open.dropped + resume.dropped + errors.dropped + decisions.dropped + files.dropped;
359
+
360
+ const sections: Array<{ title: string; items: string[]; verbatim?: boolean }> = [
361
+ { title: 'User instructions (VERBATIM)', items: verbatim.kept, verbatim: true },
362
+ { title: 'Done', items: done.kept },
363
+ { title: 'Open', items: open.kept },
364
+ { title: 'Resume pointer', items: resume.kept },
365
+ { title: 'Errors + fixes', items: errors.kept },
366
+ { title: 'Key decisions', items: decisions.kept },
367
+ { title: 'Relevant files', items: files.kept },
368
+ ];
369
+ // trim lowest-priority first; the verbatim record survives longest
370
+ const trimOrder = [
371
+ 'Relevant files',
372
+ 'Key decisions',
373
+ 'Errors + fixes',
374
+ 'Done',
375
+ 'Open',
376
+ 'Resume pointer',
377
+ 'User instructions (VERBATIM)',
378
+ ];
379
+
380
+ const head = branch !== 'unknown' && branch === _current_branch(root) ? _current_head(root) : null;
381
+
382
+ const render = (): string => {
383
+ const lines: string[] = [
384
+ '# Handoff',
385
+ '',
386
+ `Generated: ${now.toISOString()}`,
387
+ `Source-Session: ${session.id}`,
388
+ `Branch: ${branch}`,
389
+ ];
390
+ if (head) lines.push(`Last commit: ${head}`);
391
+ if (roadmap) lines.push(`Roadmap: ${roadmap}`);
392
+ if (verbatim.withheldLines > 0 || droppedTotal > 0) {
393
+ lines.push(
394
+ `Privacy floor: [${verbatim.withheldLines + droppedTotal} line(s) withheld by privacy floor]`,
395
+ );
396
+ }
397
+ for (const s of sections) {
398
+ lines.push('', `## ${s.title}`, '');
399
+ if (s.items.length === 0) {
400
+ if (s.verbatim && verbatim.withheldLines > 0) {
401
+ lines.push(`- [${verbatim.withheldLines} line(s) withheld by privacy floor]`);
402
+ } else {
403
+ lines.push('- (none extracted)');
404
+ }
405
+ continue;
406
+ }
407
+ for (const item of s.items) {
408
+ if (s.verbatim) {
409
+ // verbatim: keep internal newlines, indent continuations
410
+ const [first, ...rest] = item.split('\n');
411
+ lines.push(`- ${first ?? ''}`);
412
+ for (const cont of rest) lines.push(` ${cont}`);
413
+ } else {
414
+ lines.push(`- ${item}`);
415
+ }
416
+ }
417
+ }
418
+ lines.push('');
419
+ return lines.join('\n');
420
+ };
421
+
422
+ let text = render();
423
+ for (const title of trimOrder) {
424
+ if (_word_count(text) <= wordCap) break;
425
+ const section = sections.find((s) => s.title === title);
426
+ if (!section) continue;
427
+ while (section.items.length > 0 && _word_count(text) > wordCap) {
428
+ // verbatim: drop the OLDEST prompt first (shift), keep the latest asks
429
+ if (section.verbatim) section.items.shift();
430
+ else section.items.pop();
431
+ text = render();
432
+ }
433
+ }
434
+ return text;
435
+ }
436
+
437
+ // ---------------------------------------------------------------------
438
+ // atomic write
439
+ // ---------------------------------------------------------------------
440
+
441
+ export function handoff_target_path(root: string): string {
442
+ return process.env.AGENT_HANDOFF_CONTEXT_FILE || path.join(root, HANDOFF_CONTEXT_REL);
443
+ }
444
+
445
+ export function write_handoff(text: string, options: { cwd?: string } = {}): string {
446
+ const root = options.cwd ?? process.cwd();
447
+ const target = handoff_target_path(root);
448
+ fs.mkdirSync(path.dirname(target), { recursive: true });
449
+ const tmp = `${target}.tmp-${process.pid}`;
450
+ fs.writeFileSync(tmp, text, { encoding: 'utf-8' });
451
+ fs.renameSync(tmp, target);
452
+ return target;
453
+ }