ccompactor 0.1.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 (80) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +37 -0
  3. package/README.md +101 -0
  4. package/dist/adapters/claude.d.ts +36 -0
  5. package/dist/adapters/claude.js +361 -0
  6. package/dist/adapters/claude.js.map +1 -0
  7. package/dist/adapters/codex.d.ts +11 -0
  8. package/dist/adapters/codex.js +218 -0
  9. package/dist/adapters/codex.js.map +1 -0
  10. package/dist/adapters/index.d.ts +17 -0
  11. package/dist/adapters/index.js +19 -0
  12. package/dist/adapters/index.js.map +1 -0
  13. package/dist/adapters/pi.d.ts +16 -0
  14. package/dist/adapters/pi.js +213 -0
  15. package/dist/adapters/pi.js.map +1 -0
  16. package/dist/adapters/types.d.ts +31 -0
  17. package/dist/adapters/types.js +7 -0
  18. package/dist/adapters/types.js.map +1 -0
  19. package/dist/artifact/expand.d.ts +23 -0
  20. package/dist/artifact/expand.js +71 -0
  21. package/dist/artifact/expand.js.map +1 -0
  22. package/dist/artifact/render.d.ts +59 -0
  23. package/dist/artifact/render.js +357 -0
  24. package/dist/artifact/render.js.map +1 -0
  25. package/dist/artifact/verify.d.ts +12 -0
  26. package/dist/artifact/verify.js +48 -0
  27. package/dist/artifact/verify.js.map +1 -0
  28. package/dist/bench/index.d.ts +71 -0
  29. package/dist/bench/index.js +186 -0
  30. package/dist/bench/index.js.map +1 -0
  31. package/dist/bench/run.d.ts +17 -0
  32. package/dist/bench/run.js +187 -0
  33. package/dist/bench/run.js.map +1 -0
  34. package/dist/cli.d.ts +2 -0
  35. package/dist/cli.js +322 -0
  36. package/dist/cli.js.map +1 -0
  37. package/dist/compact/engine.d.ts +69 -0
  38. package/dist/compact/engine.js +164 -0
  39. package/dist/compact/engine.js.map +1 -0
  40. package/dist/compact/shims.d.ts +24 -0
  41. package/dist/compact/shims.js +14 -0
  42. package/dist/compact/shims.js.map +1 -0
  43. package/dist/discover/index.d.ts +40 -0
  44. package/dist/discover/index.js +164 -0
  45. package/dist/discover/index.js.map +1 -0
  46. package/dist/discover/reference.d.ts +21 -0
  47. package/dist/discover/reference.js +44 -0
  48. package/dist/discover/reference.js.map +1 -0
  49. package/dist/extract.d.ts +26 -0
  50. package/dist/extract.js +103 -0
  51. package/dist/extract.js.map +1 -0
  52. package/dist/handoff/index.d.ts +12 -0
  53. package/dist/handoff/index.js +67 -0
  54. package/dist/handoff/index.js.map +1 -0
  55. package/dist/ir/tokens.d.ts +14 -0
  56. package/dist/ir/tokens.js +36 -0
  57. package/dist/ir/tokens.js.map +1 -0
  58. package/dist/ir/types.d.ts +82 -0
  59. package/dist/ir/types.js +36 -0
  60. package/dist/ir/types.js.map +1 -0
  61. package/dist/ledgers/index.d.ts +66 -0
  62. package/dist/ledgers/index.js +185 -0
  63. package/dist/ledgers/index.js.map +1 -0
  64. package/dist/llm/index.d.ts +43 -0
  65. package/dist/llm/index.js +143 -0
  66. package/dist/llm/index.js.map +1 -0
  67. package/dist/redact.d.ts +39 -0
  68. package/dist/redact.js +104 -0
  69. package/dist/redact.js.map +1 -0
  70. package/dist/skill/index.d.ts +9 -0
  71. package/dist/skill/index.js +48 -0
  72. package/dist/skill/index.js.map +1 -0
  73. package/dist/triage.d.ts +25 -0
  74. package/dist/triage.js +160 -0
  75. package/dist/triage.js.map +1 -0
  76. package/dist/tui/index.d.ts +4 -0
  77. package/dist/tui/index.js +147 -0
  78. package/dist/tui/index.js.map +1 -0
  79. package/package.json +71 -0
  80. package/skill/SKILL.md +54 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"expand.js","sourceRoot":"","sources":["../../src/artifact/expand.ts"],"names":[],"mappings":"AAqBA,MAAM,UAAU,MAAM,CACpB,EAAa,EACb,MAA+B,EAC/B,UAAyB,EAAE;IAE3B,MAAM,GAAG,GAAa,EAAE,CAAA;IACxB,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,CAAC,CAAA;IACpC,KAAK,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,MAAM,EAAE,CAAC;QAChC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,GAAG,OAAO,CAAC,CAAA;QACzC,MAAM,GAAG,GAAG,EAAE,GAAG,OAAO,CAAA;QACxB,MAAM,QAAQ,GAAG,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,IAAI,KAAK,IAAI,CAAC,CAAC,UAAU,IAAI,GAAG,CAAC,CAAA;QACxF,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC1B,GAAG,CAAC,IAAI,CAAC,WAAW,KAAK,IAAI,GAAG,mCAAmC,CAAC,CAAA;YACpE,SAAQ;QACV,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;YAClB,GAAG,CAAC,IAAI,CAAC,WAAW,KAAK,IAAI,GAAG,SAAS,QAAQ,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YAClF,SAAQ;QACV,CAAC;QACD,MAAM,KAAK,GAAG,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QACrD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;QACrE,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,GAAG,CAAC,CAAE,CAAA;QAC/B,GAAG,CAAC,IAAI,CACN,WAAW,KAAK,IAAI,GAAG,WAAW,KAAK,IAAI,KAAK,CAAC,MAAM,UAAU,KAAK,CAAC,CAAC,CAAE,CAAC,UAAU,IAAI,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAE,CAAC,UAAU,MAAM,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,eAAe,CAC1M,CAAA;QACD,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAA;QAC3C,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrB,MAAM,IAAI,GAAG,KAAK,KAAK,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAA;YACnD,GAAG,CAAC,IAAI,CAAC,cAAc,KAAK,OAAO,KAAK,CAAC,MAAM,UAAU,IAAI,iCAAiC,IAAI,KAAK,EAAE,WAAW,IAAI,QAAQ,CAAC,CAAA;QACnI,CAAC;QACD,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IACd,CAAC;IACD,OAAO,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AACvB,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,QAAQ,CACtB,QAA+B,EAC/B,MAAc;IAEd,MAAM,KAAK,GAAiC,EAAE,CAAA;IAC9C,IAAI,OAAO,GAA0B,EAAE,CAAA;IACvC,IAAI,IAAI,GAAG,CAAC,CAAA;IACZ,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;QACvD,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,GAAG,IAAI,GAAG,MAAM,EAAE,CAAC;YAC/C,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;YACnB,OAAO,GAAG,EAAE,CAAA;YACZ,IAAI,GAAG,CAAC,CAAA;QACV,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QACrB,IAAI,IAAI,IAAI,CAAA;IACd,CAAC;IACD,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;QAAE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IAC3C,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;AACxC,CAAC;AAED,SAAS,WAAW,CAAC,OAAsC;IACzD,MAAM,IAAI,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAA;IACxD,MAAM,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,IAAI,CAAA;IACnE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;IACzF,OAAO,OAAO,OAAO,CAAC,UAAU,KAAK,GAAG,KAAK,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,CAAA;AAC1F,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAA;IAC3B,MAAM,KAAK,GAAG,6BAA6B,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IACzD,IAAI,KAAK,EAAE,CAAC;QACV,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAE,EAAE,EAAE,CAAC,CAAA;QAC3C,MAAM,EAAE,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAE,EAAE,EAAE,CAAC,CAAA;QACzC,IAAI,EAAE,GAAG,IAAI;YAAE,MAAM,IAAI,KAAK,CAAC,WAAW,IAAI,mBAAmB,CAAC,CAAA;QAClE,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAA;IACnB,CAAC;IACD,IAAI,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QAC1B,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC,CAAA;QAC1C,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAA;IACvB,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,KAAK,IAAI,yDAAyD,CAAC,CAAA;AACrF,CAAC"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * The handoff artifact.
3
+ *
4
+ * Layered cheapest-first, like sctxx, because a reader should be able to stop
5
+ * as soon as it knows enough: the brief answers "what is this", the items
6
+ * answer "what do I need to know", and the retrieval layer says where the rest
7
+ * lives.
8
+ *
9
+ * Two rules are load-bearing and easy to break:
10
+ *
11
+ * 1. **Every claim carries its provenance.** A pointer is `[evt 12–19]`, and
12
+ * `ccompactor expand` turns one back into the transcript. An artifact whose
13
+ * claims cannot be checked is a summary, and a summary is what this tool
14
+ * exists to replace.
15
+ *
16
+ * 2. **Nothing mandatory is dropped to fit a budget.** The constraints block
17
+ * summarises itself rather than vanishing, because an artifact that fits its
18
+ * budget by deleting the section its own preamble calls binding is not
19
+ * smaller, it is wrong.
20
+ */
21
+ import type { Ledgers } from '../ledgers/index.js';
22
+ import type { Constraint } from '../triage.js';
23
+ import type { SessionIR } from '../ir/types.js';
24
+ export interface RenderInput {
25
+ ir: SessionIR;
26
+ ledgers: Ledgers;
27
+ constraints: Constraint[];
28
+ /** The model's continuation summary, when one was produced. */
29
+ summary?: string;
30
+ /** Which engine produced the summary, for the header. */
31
+ engine: string;
32
+ /** Optional user-supplied focus, echoed so a reader knows the lens. */
33
+ focus?: string;
34
+ }
35
+ export interface Rendered {
36
+ markdown: string;
37
+ json: Record<string, unknown>;
38
+ tokens: number;
39
+ }
40
+ export declare function render(input: RenderInput): Rendered;
41
+ interface Episode {
42
+ from: number;
43
+ to: number;
44
+ headline: string;
45
+ tokens: number;
46
+ }
47
+ /**
48
+ * The session in chunks a reader can ask for: one per human turn.
49
+ *
50
+ * A human turn is where intent changes, so it is the natural boundary for a
51
+ * range someone would want back — "the part where I asked about the parser" is
52
+ * a request a reader can actually form, and `evt 1200–1450` is how they get it.
53
+ */
54
+ export declare function episodesOf(ir: SessionIR): Episode[];
55
+ /** Drop the model's scratchpad; only the summary itself is for the reader. */
56
+ export declare function stripAnalysis(text: string): string;
57
+ /** Collapse whitespace and truncate on a word boundary. */
58
+ export declare function oneLine(text: string, max: number): string;
59
+ export {};
@@ -0,0 +1,357 @@
1
+ import { approxTokens, truncateMiddle } from '../ir/tokens.js';
2
+ /** The brief's own ceiling. A brief that does not fit a screen is not a brief. */
3
+ const BRIEF_BUDGET = 1_200;
4
+ /** Tokens the constraints block may spend before it starts counting itself. */
5
+ const CONSTRAINT_BUDGET = 700;
6
+ export function render(input) {
7
+ const { ir, ledgers, constraints } = input;
8
+ const out = [];
9
+ const lines = [];
10
+ out.push(frontMatter(input));
11
+ out.push(`\n# Handoff: ${title(input)}\n`);
12
+ out.push(preamble());
13
+ let brief = '';
14
+ let budget = BRIEF_BUDGET;
15
+ brief += `\n## L0 · Brief\n\n`;
16
+ if (ledgers.errors.length > 0) {
17
+ // Before anything else: a reader who acts on a stale premise wastes the
18
+ // whole session, and an unresolved failure is the most common stale premise.
19
+ const top = ledgers.errors.slice(0, 3);
20
+ brief += `**Known-broken at the end of the session**\n`;
21
+ for (const error of top) {
22
+ brief += `- ${oneLine(error.signature, 160)} (×${error.occurrences}) [evt ${error.evt}]\n`;
23
+ }
24
+ brief += '\n';
25
+ }
26
+ const first = ledgers.userTurns[0];
27
+ const last = ledgers.userTurns.at(-1);
28
+ if (first) {
29
+ brief += `**Goal** (from the first user message, not model-inferred): ${oneLine(first.text, 300)} [evt ${first.evt}]\n\n`;
30
+ }
31
+ // Constraints come this early on purpose: they outrank everything except the
32
+ // fact that something is broken, and they are never dropped whole.
33
+ if (constraints.length > 0) {
34
+ brief += renderConstraints(constraints);
35
+ }
36
+ if (ledgers.files.length > 0) {
37
+ const byDir = topDirectories(ledgers, 5);
38
+ brief += `**Where the work was**\n`;
39
+ for (const [dir, count] of byDir)
40
+ brief += `- \`${dir}\` — ${count} touch(es)\n`;
41
+ brief += '\n';
42
+ }
43
+ if (ledgers.commits.length > 0) {
44
+ brief += `**What it committed** (${ledgers.commits.length} total)\n`;
45
+ for (const commit of ledgers.commits.slice(-5).reverse()) {
46
+ brief += `- ${commit.subject} [evt ${commit.evt}]\n`;
47
+ }
48
+ brief += '\n';
49
+ }
50
+ if (last && last !== first) {
51
+ brief += `**Last user request**: ${oneLine(last.text, 300)} [evt ${last.evt}]\n\n`;
52
+ }
53
+ if (ledgers.commands.length > 0) {
54
+ const recent = ledgers.commands.slice(-3);
55
+ brief += `**Last commands**\n`;
56
+ for (const command of recent) {
57
+ brief += `- \`${oneLine(command.command, 120)}\` — ${command.failed ? 'FAILED' : 'ok'} [evt ${command.evt}]\n`;
58
+ }
59
+ brief += '\n';
60
+ }
61
+ if (input.focus)
62
+ brief += `**Focus requested**: ${input.focus}\n\n`;
63
+ brief += `**Verify first**\n- \`git status\`\n- \`git log --oneline -5\`\n\n`;
64
+ out.push(brief);
65
+ budget -= Math.min(approxTokens(brief), budget);
66
+ // L1 — the items layer, from the model when there is one.
67
+ if (input.summary) {
68
+ out.push(`\n## L1 · Continuation summary\n\n${stripAnalysis(input.summary)}\n`);
69
+ }
70
+ else {
71
+ out.push('\n## L1 · Continuation summary\n\n> No model ran, so there is no continuation summary. ' +
72
+ 'What follows is what deterministic passes can prove — the ledgers. A rule stated ' +
73
+ 'declaratively is absent rather than absent-minded; treat a missing fact as unknown, ' +
74
+ 'not as permission. For the summary: `ccompactor extract <ref> --llm api:<provider>`.\n');
75
+ }
76
+ // L2 — ledgers, the evidence the summary cannot replace.
77
+ out.push(renderLedgers(input));
78
+ // The recency tail was tried here and removed.
79
+ //
80
+ // Carrying the last 12,000 tokens verbatim is what sctxx does, and copying it
81
+ // seemed obviously right: a recency window is the one strategy with an
82
+ // ablation behind it (arXiv:2508.21433). Measured on the same session, same
83
+ // questions and same backend, it made retrieval *worse* — 38% to 27% across
84
+ // three runs — and tripled the tokens. More context is not more answer, and
85
+ // the arm that loses for the wrong reason is worth removing rather than
86
+ // keeping for symmetry.
87
+ // L3 — retrieval, so that everything dropped is still reachable.
88
+ out.push(renderRetrieval(input));
89
+ const markdown = out.join('');
90
+ return {
91
+ markdown,
92
+ json: {
93
+ schema: 'ccompactor.handoff/v1',
94
+ version: '0.1.0',
95
+ session: {
96
+ agent: ir.ref.agent,
97
+ id: ir.ref.id,
98
+ path: ir.ref.path,
99
+ events: ir.messages.length,
100
+ userTurns: ledgers.counts.userTurns,
101
+ compactBoundaries: ir.compactBoundaries.length,
102
+ },
103
+ engine: input.engine,
104
+ constraints,
105
+ ledgers,
106
+ provenance: { source: ir.ref.path },
107
+ },
108
+ tokens: approxTokens(markdown),
109
+ };
110
+ void lines;
111
+ }
112
+ function frontMatter(input) {
113
+ const { ir, ledgers } = input;
114
+ const cwd = ir.metadata['cwd'];
115
+ return `---
116
+ schema: ccompactor.handoff/v1
117
+ ccompactor: 0.1.0
118
+ source: {agent: ${ir.ref.agent}, session: ${ir.ref.id}, events: ${ir.messages.length}, user_turns: ${ledgers.counts.userTurns}, compact_boundaries: ${ir.compactBoundaries.length}${typeof cwd === 'string' ? `, cwd: ${cwd}` : ''}}
119
+ engine: ${input.engine}
120
+ llm: ${input.summary ? 'on' : 'none'}
121
+ constraints: {found: ${input.constraints.length}}
122
+ ${ledgers.counts.failedCommands > 0 ? `failures: {at_end: ${ledgers.errors.length}, failed_commands: ${ledgers.counts.failedCommands}}\n` : ''}---
123
+ `;
124
+ }
125
+ function preamble() {
126
+ return `
127
+ > A different coding agent worked on this task in an earlier session. What follows is a
128
+ > compressed, provenance-linked record of that session. Use it to build on the work already done
129
+ > instead of repeating it — but treat it as a map, not as ground truth. Run the verify-first
130
+ > commands before changing anything, treat "Hard constraints" as binding, and expand any
131
+ > \`[evt a–b]\` pointer you need with \`ccompactor expand\`.
132
+ `;
133
+ }
134
+ function title(input) {
135
+ const first = input.ledgers.userTurns[0];
136
+ if (!first)
137
+ return `${input.ir.ref.agent} session ${input.ir.ref.id}`;
138
+ return oneLine(first.text, 160);
139
+ }
140
+ function renderConstraints(constraints) {
141
+ let block = `**Hard constraints** (standing instructions, quoted verbatim)\n`;
142
+ let spent = approxTokens(block);
143
+ let shown = 0;
144
+ for (const constraint of constraints) {
145
+ const line = `- "${oneLine(constraint.text, 240)}" [evt ${constraint.evt}]\n`;
146
+ const cost = approxTokens(line);
147
+ if (shown > 0 && spent + cost > CONSTRAINT_BUDGET)
148
+ break;
149
+ spent += cost;
150
+ shown += 1;
151
+ block += line;
152
+ }
153
+ if (shown < constraints.length) {
154
+ block += `- … and ${constraints.length - shown} more\n`;
155
+ }
156
+ block += '\n';
157
+ return block;
158
+ }
159
+ function renderLedgers(input) {
160
+ const { ledgers } = input;
161
+ let out = `\n## L2 · Ledgers (deterministic, no model)\n\n`;
162
+ if (ledgers.files.length > 0) {
163
+ out += `### Files touched (${ledgers.files.length})\n`;
164
+ for (const file of ledgers.files.slice(0, 40)) {
165
+ const parts = [];
166
+ if (file.edits > 0)
167
+ parts.push(`edit×${file.edits}`);
168
+ if (file.writes > 0)
169
+ parts.push(`write×${file.writes}`);
170
+ if (file.reads > 0)
171
+ parts.push(`read×${file.reads}`);
172
+ out += `- \`${file.path}\` — ${parts.join(' ') || 'touched'} [evt ${file.firstEvt}–${file.lastEvt}]\n`;
173
+ }
174
+ if (ledgers.files.length > 40)
175
+ out += `- … and ${ledgers.files.length - 40} more\n`;
176
+ out += '\n';
177
+ }
178
+ if (ledgers.commands.length > 0) {
179
+ out += `### Commands (${ledgers.commands.length}, ${ledgers.counts.failedCommands} failed)\n`;
180
+ const interesting = [
181
+ ...ledgers.commands.filter((c) => c.failed).slice(-12),
182
+ ...ledgers.commands.slice(-6),
183
+ ];
184
+ const unique = [...new Map(interesting.map((c) => [`${c.evt}:${c.command}`, c])).values()];
185
+ for (const command of unique) {
186
+ out += `- \`${oneLine(command.command, 160)}\` — ${command.failed ? 'FAILED' : 'ok'} [evt ${command.evt}]\n`;
187
+ if (command.failed && command.outputHead) {
188
+ out += ` > ${oneLine(command.outputHead, 200)}\n`;
189
+ }
190
+ }
191
+ out += '\n';
192
+ }
193
+ if (ledgers.errors.length > 0) {
194
+ out += `### Error signatures (${ledgers.errors.length})\n`;
195
+ for (const error of ledgers.errors.slice(0, 12)) {
196
+ out += `- ×${error.occurrences} ${oneLine(error.signature, 200)} [evt ${error.evt}]\n`;
197
+ }
198
+ out += '\n';
199
+ }
200
+ if (ledgers.toolUse.length > 0) {
201
+ out += `### What was used\n${ledgers.toolUse
202
+ .slice(0, 8)
203
+ .map((t) => `\`${t.name}\`×${t.count}`)
204
+ .join(' · ')}\n\n`;
205
+ }
206
+ return out;
207
+ }
208
+ /**
209
+ * The newest events, verbatim, to a token budget.
210
+ *
211
+ * Verbatim is the point. A summary of the last exchange is exactly the artifact
212
+ * that loses the detail that matters — the error string, the flag, the path —
213
+ * and the recency window is cheap because it needs no model at all.
214
+ */
215
+ const TAIL_TOKENS = 12_000;
216
+ /** Per-event ceiling, so one pasted stack trace cannot spend the whole tail. */
217
+ const TAIL_EVENT_TOKENS = 400;
218
+ function renderTail(input) {
219
+ const lines = [];
220
+ let spent = 0;
221
+ for (const message of [...input.ir.messages].reverse()) {
222
+ const body = (message.text ?? '').replace(/\s+/g, ' ').trim();
223
+ if (body.length === 0)
224
+ continue;
225
+ const truncated = truncateMiddle(body, TAIL_EVENT_TOKENS);
226
+ const role = message.isHumanTurn ? 'user' : message.role;
227
+ const tag = message.toolName ? `${role}·${message.toolName}` : role;
228
+ const line = `- [evt ${message.eventIndex} ${tag}] ${truncated}`;
229
+ const cost = approxTokens(line);
230
+ if (spent + cost > TAIL_TOKENS)
231
+ break;
232
+ spent += cost;
233
+ lines.push(line);
234
+ }
235
+ if (lines.length === 0)
236
+ return '';
237
+ return `\n## L2b · Recency tail (verbatim, ${lines.length} event(s))\n\n${lines.reverse().join('\n')}\n`;
238
+ }
239
+ function renderRetrieval(input) {
240
+ const { ir, ledgers } = input;
241
+ const reference = `${ir.ref.agent}:${ir.ref.id}`;
242
+ let out = `\n## L3 · Retrieval\n\nSource: \`${ir.ref.path}\`\n\n`;
243
+ out += `Expand any pointer:\n\`\`\`sh\nccompactor expand ${reference} <a>..<b> --context 3\n\`\`\`\n\n`;
244
+ // The index is the whole point of this layer.
245
+ //
246
+ // Selection is the cheapest strategy there is, but only for a reader who can
247
+ // look again — and a reader who can look again still needs to know *what* to
248
+ // look for. Without ranges to ask for, the successor guesses, and measured
249
+ // head to head against sctxx on the same sessions that was the entire gap:
250
+ // 38% retrieval accuracy against 69%, with the deep-question class at 26%
251
+ // against 78%. Same arms, same questions, same backend. The only difference
252
+ // was that sctxx's artifact told the successor which ranges existed.
253
+ const episodes = episodesOf(ir);
254
+ const carried = new Set();
255
+ for (const file of ledgers.files.slice(0, 40))
256
+ carried.add(file.firstEvt);
257
+ if (episodes.length > 0) {
258
+ const total = episodes.reduce((n, episode) => n + episode.tokens, 0);
259
+ out += `**Not carried verbatim** — ${episodes.length} episode(s), ${total} token(s), all reachable:\n`;
260
+ // Sampled across the whole session, never truncated from the front.
261
+ //
262
+ // The first version listed episodes in order and stopped at the budget, so
263
+ // on a 341-episode session the index showed roughly the first twenty — all
264
+ // of them near the start. A question about an event at 45,000 was then not
265
+ // only unanswered but unaddressable: the reader had no range covering it and
266
+ // no way to know one existed. That single mistake was worth 45 points of
267
+ // retrieval accuracy against the same benchmark.
268
+ const budget = 1_400;
269
+ const shown = sampleAcross(episodes, budget);
270
+ for (const episode of shown) {
271
+ out += `- evt ${episode.from}–${episode.to} · ${oneLine(episode.headline, 110)} · ${episode.tokens}\n`;
272
+ }
273
+ if (shown.length < episodes.length) {
274
+ out += `- (${episodes.length - shown.length} episode(s) between these are not listed; the ranges above are contiguous, so any event index in 0–${episodes.at(-1).to} can be asked for directly)\n`;
275
+ }
276
+ out += '\n';
277
+ }
278
+ return out;
279
+ }
280
+ /**
281
+ * As many episodes as fit the budget, spread evenly across the session.
282
+ *
283
+ * Evenly, not newest-first and not from the front: the point of the index is
284
+ * coverage of the whole transcript, and the questions worth asking are the ones
285
+ * about the middle. Always includes the first and last.
286
+ */
287
+ function sampleAcross(episodes, budget) {
288
+ const costOf = (episode) => approxTokens(`- evt ${episode.from}–${episode.to} · ${oneLine(episode.headline, 110)} · ${episode.tokens}\n`);
289
+ const total = episodes.reduce((n, e) => n + costOf(e), 0);
290
+ if (total <= budget)
291
+ return episodes;
292
+ const average = Math.max(1, total / episodes.length);
293
+ const keep = Math.max(4, Math.floor(budget / average));
294
+ const stride = episodes.length / keep;
295
+ const out = [];
296
+ for (let i = 0; i < keep; i += 1) {
297
+ const episode = episodes[Math.floor(i * stride)];
298
+ if (episode)
299
+ out.push(episode);
300
+ }
301
+ const last = episodes.at(-1);
302
+ if (last && out.at(-1) !== last)
303
+ out.push(last);
304
+ return out;
305
+ }
306
+ /**
307
+ * The session in chunks a reader can ask for: one per human turn.
308
+ *
309
+ * A human turn is where intent changes, so it is the natural boundary for a
310
+ * range someone would want back — "the part where I asked about the parser" is
311
+ * a request a reader can actually form, and `evt 1200–1450` is how they get it.
312
+ */
313
+ export function episodesOf(ir) {
314
+ const boundaries = ir.messages.filter((m) => m.isHumanTurn).map((m) => m.eventIndex);
315
+ if (boundaries.length === 0)
316
+ return [];
317
+ const episodes = [];
318
+ for (const [index, from] of boundaries.entries()) {
319
+ const next = boundaries[index + 1];
320
+ const to = (next ?? ir.messages.at(-1)?.eventIndex ?? from) - 1;
321
+ const slice = ir.messages.filter((m) => m.eventIndex >= from && m.eventIndex <= Math.max(to, from));
322
+ const headline = slice.find((m) => m.isHumanTurn)?.text ?? slice[0]?.text ?? '';
323
+ episodes.push({
324
+ from,
325
+ to: Math.max(to, from),
326
+ headline,
327
+ tokens: slice.reduce((n, m) => n + approxTokens(m.text ?? ''), 0),
328
+ });
329
+ }
330
+ return episodes;
331
+ }
332
+ /** Drop the model's scratchpad; only the summary itself is for the reader. */
333
+ export function stripAnalysis(text) {
334
+ const withoutAnalysis = text.replace(/<analysis>[\s\S]*?<\/analysis>/gi, '').trim();
335
+ const summary = /<summary>([\s\S]*?)<\/summary>/i.exec(withoutAnalysis);
336
+ return (summary ? summary[1] : withoutAnalysis).trim();
337
+ }
338
+ /** Tokens per top-level directory, busiest first. */
339
+ function topDirectories(ledgers, limit) {
340
+ const counts = new Map();
341
+ for (const file of ledgers.files) {
342
+ const parts = file.path.split('/').filter(Boolean);
343
+ const dir = parts.slice(0, Math.min(2, Math.max(1, parts.length - 1))).join('/');
344
+ counts.set(dir, (counts.get(dir) ?? 0) + file.edits * 3 + file.writes * 3 + file.reads);
345
+ }
346
+ return [...counts.entries()]
347
+ .sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
348
+ .slice(0, limit);
349
+ }
350
+ /** Collapse whitespace and truncate on a word boundary. */
351
+ export function oneLine(text, max) {
352
+ const single = text.replace(/\s+/g, ' ').trim();
353
+ if (single.length <= max)
354
+ return single;
355
+ return `${single.slice(0, max - 1).replace(/\s\S*$/, '')}…`;
356
+ }
357
+ //# sourceMappingURL=render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../../src/artifact/render.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ export interface VerifyReport {
2
+ dir: string;
3
+ constraintsQuoted: number;
4
+ missingQuotes: Array<{
5
+ text: string;
6
+ evt: number;
7
+ }>;
8
+ filesClaimed: number;
9
+ missingFiles: string[];
10
+ stale: boolean;
11
+ }
12
+ export declare function verify(dir: string): Promise<VerifyReport>;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * `ccompactor verify` — is this handoff still true?
3
+ *
4
+ * An artifact ages. Two checks are cheap and catch most of it: every quoted
5
+ * constraint must still appear in the transcript it cites, and every file the
6
+ * ledger claims was touched must still exist. Neither needs a model, and a
7
+ * handoff whose quotes cannot be found is not a handoff.
8
+ */
9
+ import { readFile, stat } from 'node:fs/promises';
10
+ import { join } from 'node:path';
11
+ /** Collapse whitespace and punctuation so a quote survives reformatting. */
12
+ function normalize(text) {
13
+ return text.toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim();
14
+ }
15
+ export async function verify(dir) {
16
+ const handoff = JSON.parse(await readFile(join(dir, 'handoff.json'), 'utf8'));
17
+ const constraints = handoff.constraints ?? [];
18
+ const source = handoff.session?.path;
19
+ const transcript = source ? await readFile(source, 'utf8').catch(() => '') : '';
20
+ const missingQuotes = [];
21
+ for (const constraint of constraints) {
22
+ // The quote must be in the transcript, not merely in the artifact: a quote
23
+ // checked against its own output proves only that it was copied.
24
+ // Compared normalized, not raw. The artifact quotes the rule with its
25
+ // markdown and line breaks collapsed, so a byte comparison against the
26
+ // transcript reports every quote as missing — which the first run of this
27
+ // did, and which is worse than not checking at all.
28
+ if (transcript && !normalize(transcript).includes(normalize(constraint.text))) {
29
+ missingQuotes.push(constraint);
30
+ }
31
+ }
32
+ const files = handoff.ledgers?.files ?? [];
33
+ const missingFiles = [];
34
+ for (const file of files.slice(0, 200)) {
35
+ const info = await stat(file.path).catch(() => null);
36
+ if (!info)
37
+ missingFiles.push(file.path);
38
+ }
39
+ return {
40
+ dir,
41
+ constraintsQuoted: constraints.length,
42
+ missingQuotes,
43
+ filesClaimed: files.length,
44
+ missingFiles,
45
+ stale: missingFiles.length > 0,
46
+ };
47
+ }
48
+ //# sourceMappingURL=verify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify.js","sourceRoot":"","sources":["../../src/artifact/verify.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAA;AACjD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAEhC,4EAA4E;AAC5E,SAAS,SAAS,CAAC,IAAY;IAC7B,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAA;AAC9D,CAAC;AAWD,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,GAAW;IACtC,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,CAI3E,CAAA;IAED,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,EAAE,CAAA;IAC7C,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,IAAI,CAAA;IACpC,MAAM,UAAU,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;IAE/E,MAAM,aAAa,GAAyC,EAAE,CAAA;IAC9D,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;QACrC,2EAA2E;QAC3E,iEAAiE;QACjE,sEAAsE;QACtE,uEAAuE;QACvE,0EAA0E;QAC1E,oDAAoD;QACpD,IAAI,UAAU,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;YAC9E,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;QAChC,CAAC;IACH,CAAC;IAED,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,EAAE,KAAK,IAAI,EAAE,CAAA;IAC1C,MAAM,YAAY,GAAa,EAAE,CAAA;IACjC,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;QACvC,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAA;QACpD,IAAI,CAAC,IAAI;YAAE,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACzC,CAAC;IAED,OAAO;QACL,GAAG;QACH,iBAAiB,EAAE,WAAW,CAAC,MAAM;QACrC,aAAa;QACb,YAAY,EAAE,KAAK,CAAC,MAAM;QAC1B,YAAY;QACZ,KAAK,EAAE,YAAY,CAAC,MAAM,GAAG,CAAC;KAC/B,CAAA;AACH,CAAC"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * The handoff benchmark, ported from sctxx so the two tools' numbers are
3
+ * comparable rather than merely both large.
4
+ *
5
+ * Built the same way, and for the same reason: the survey behind sctxx found no
6
+ * published result measuring what either tool does, and the closest work
7
+ * (Handoff Debt, arXiv:2606.02875) has no arm in which the successor can ask for
8
+ * the part of the transcript it needs. That arm is the hypothesis, so it is an
9
+ * arm here:
10
+ *
11
+ * none nothing but the question — a win over nothing is not a win
12
+ * tail the recency window, the strategy the published evidence favours
13
+ * artifact the rendered handoff
14
+ * retrieval the artifact, plus `EXPAND a..b` answered with real events
15
+ *
16
+ * Questions come from the session's own events, not from the artifact, so a
17
+ * question can be asked that the artifact cannot answer. Answers are checked by
18
+ * substring against a key taken verbatim from the transcript; no model decides
19
+ * who is right.
20
+ */
21
+ import type { Ledgers } from '../ledgers/index.js';
22
+ import type { IRMessage, SessionIR } from '../ir/types.js';
23
+ export type Arm = 'none' | 'tail' | 'artifact' | 'retrieval';
24
+ export declare const ARMS: Arm[];
25
+ export type Class = 'brief' | 'deep' | 'recent';
26
+ export interface Question {
27
+ id: string;
28
+ class: Class;
29
+ prompt: string;
30
+ /** A string a correct answer must contain, verbatim from the transcript. */
31
+ key: string;
32
+ evt: number;
33
+ }
34
+ export interface Trial {
35
+ question: string;
36
+ class: Class;
37
+ arm: Arm;
38
+ correct: boolean;
39
+ tokens: number;
40
+ expansions: number;
41
+ answer: string;
42
+ }
43
+ export interface ArmScore {
44
+ asked: number;
45
+ correct: number;
46
+ tokens: number;
47
+ expansions: number;
48
+ byClass: Record<string, [number, number]>;
49
+ }
50
+ export interface BenchOptions {
51
+ brief: number;
52
+ deep: number;
53
+ recent: number;
54
+ expansions: number;
55
+ }
56
+ export declare const DEFAULT_BENCH: BenchOptions;
57
+ export declare function questions(ir: SessionIR, ledgers: Ledgers, options: BenchOptions): Question[];
58
+ /**
59
+ * The key a question is scored against.
60
+ *
61
+ * A distinctive single token, not a phrase: whole phrases do not survive
62
+ * paraphrase, and the first run of sctxx's version of this benchmark keyed on a
63
+ * tool name followed by its argument and scored zero on correct answers that
64
+ * described the call instead of reciting it.
65
+ */
66
+ export declare function keyFor(message: IRMessage): string | undefined;
67
+ export declare function normalize(text: string): string;
68
+ export declare function answeredBy(question: Question, answer: string): boolean;
69
+ /** A range a successor asked for, parsed out of its reply. */
70
+ export declare function requestedRange(reply: string): [number, number] | undefined;
71
+ export declare function score(trials: Trial[]): Record<string, ArmScore>;