praana 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (204) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +124 -0
  3. package/bin/praana.js +17 -0
  4. package/bin/pran.js +17 -0
  5. package/dist/app-banner.d.ts +11 -0
  6. package/dist/app-banner.js +161 -0
  7. package/dist/app-controller.d.ts +44 -0
  8. package/dist/app-controller.js +143 -0
  9. package/dist/app-identity.d.ts +18 -0
  10. package/dist/app-identity.js +52 -0
  11. package/dist/auto-compact.d.ts +16 -0
  12. package/dist/auto-compact.js +101 -0
  13. package/dist/cli-args.d.ts +14 -0
  14. package/dist/cli-args.js +69 -0
  15. package/dist/compile-classic.d.ts +21 -0
  16. package/dist/compile-classic.js +106 -0
  17. package/dist/compiler.d.ts +75 -0
  18. package/dist/compiler.js +406 -0
  19. package/dist/config.d.ts +3 -0
  20. package/dist/config.js +433 -0
  21. package/dist/context-engine/activity-log.d.ts +9 -0
  22. package/dist/context-engine/activity-log.js +109 -0
  23. package/dist/context-engine/artifact-store.d.ts +32 -0
  24. package/dist/context-engine/artifact-store.js +272 -0
  25. package/dist/context-engine/bm25.d.ts +3 -0
  26. package/dist/context-engine/bm25.js +32 -0
  27. package/dist/context-engine/checkpoint.d.ts +34 -0
  28. package/dist/context-engine/checkpoint.js +430 -0
  29. package/dist/context-engine/classify.d.ts +3 -0
  30. package/dist/context-engine/classify.js +60 -0
  31. package/dist/context-engine/db.d.ts +73 -0
  32. package/dist/context-engine/db.js +505 -0
  33. package/dist/context-engine/distiller.d.ts +30 -0
  34. package/dist/context-engine/distiller.js +67 -0
  35. package/dist/context-engine/engine-compiler.d.ts +23 -0
  36. package/dist/context-engine/engine-compiler.js +297 -0
  37. package/dist/context-engine/error-tracker.d.ts +21 -0
  38. package/dist/context-engine/error-tracker.js +74 -0
  39. package/dist/context-engine/event-lineage.d.ts +26 -0
  40. package/dist/context-engine/event-lineage.js +120 -0
  41. package/dist/context-engine/extraction.d.ts +26 -0
  42. package/dist/context-engine/extraction.js +83 -0
  43. package/dist/context-engine/index.d.ts +82 -0
  44. package/dist/context-engine/index.js +238 -0
  45. package/dist/context-engine/scoring.d.ts +13 -0
  46. package/dist/context-engine/scoring.js +47 -0
  47. package/dist/context-engine/state-snapshot.d.ts +8 -0
  48. package/dist/context-engine/state-snapshot.js +50 -0
  49. package/dist/context-engine/summarize.d.ts +6 -0
  50. package/dist/context-engine/summarize.js +32 -0
  51. package/dist/context-engine/telemetry.d.ts +25 -0
  52. package/dist/context-engine/telemetry.js +64 -0
  53. package/dist/context-engine/turn-digest.d.ts +50 -0
  54. package/dist/context-engine/turn-digest.js +250 -0
  55. package/dist/context-engine/turn-ledger.d.ts +18 -0
  56. package/dist/context-engine/turn-ledger.js +184 -0
  57. package/dist/context-engine/turn-recorder.d.ts +24 -0
  58. package/dist/context-engine/turn-recorder.js +88 -0
  59. package/dist/context-engine/types.d.ts +201 -0
  60. package/dist/context-engine/types.js +4 -0
  61. package/dist/context-pressure.d.ts +19 -0
  62. package/dist/context-pressure.js +36 -0
  63. package/dist/distillers/generic.d.ts +14 -0
  64. package/dist/distillers/generic.js +93 -0
  65. package/dist/distillers/git-diff.d.ts +8 -0
  66. package/dist/distillers/git-diff.js +119 -0
  67. package/dist/distillers/index.d.ts +2 -0
  68. package/dist/distillers/index.js +16 -0
  69. package/dist/distillers/npm-test.d.ts +8 -0
  70. package/dist/distillers/npm-test.js +50 -0
  71. package/dist/distillers/rg-results.d.ts +8 -0
  72. package/dist/distillers/rg-results.js +28 -0
  73. package/dist/distillers/tsc-errors.d.ts +8 -0
  74. package/dist/distillers/tsc-errors.js +52 -0
  75. package/dist/event-log.d.ts +56 -0
  76. package/dist/event-log.js +214 -0
  77. package/dist/llm.d.ts +29 -0
  78. package/dist/llm.js +155 -0
  79. package/dist/logger.d.ts +94 -0
  80. package/dist/logger.js +287 -0
  81. package/dist/main.d.ts +1 -0
  82. package/dist/main.js +54 -0
  83. package/dist/memory/confidence.d.ts +7 -0
  84. package/dist/memory/confidence.js +37 -0
  85. package/dist/memory/consolidation.d.ts +26 -0
  86. package/dist/memory/consolidation.js +166 -0
  87. package/dist/memory/db.d.ts +40 -0
  88. package/dist/memory/db.js +283 -0
  89. package/dist/memory/dedup.d.ts +6 -0
  90. package/dist/memory/dedup.js +50 -0
  91. package/dist/memory/embedder-factory.d.ts +3 -0
  92. package/dist/memory/embedder-factory.js +81 -0
  93. package/dist/memory/embeddings.d.ts +15 -0
  94. package/dist/memory/embeddings.js +67 -0
  95. package/dist/memory/index.d.ts +9 -0
  96. package/dist/memory/index.js +11 -0
  97. package/dist/memory/ollama-summarizer.d.ts +19 -0
  98. package/dist/memory/ollama-summarizer.js +72 -0
  99. package/dist/memory/openai-summarizer.d.ts +21 -0
  100. package/dist/memory/openai-summarizer.js +51 -0
  101. package/dist/memory/store.d.ts +61 -0
  102. package/dist/memory/store.js +502 -0
  103. package/dist/memory/summarizer-factory.d.ts +3 -0
  104. package/dist/memory/summarizer-factory.js +69 -0
  105. package/dist/memory/summarizer.d.ts +4 -0
  106. package/dist/memory/summarizer.js +112 -0
  107. package/dist/memory/types.d.ts +87 -0
  108. package/dist/memory/types.js +17 -0
  109. package/dist/model-context.d.ts +15 -0
  110. package/dist/model-context.js +212 -0
  111. package/dist/project-detector.d.ts +37 -0
  112. package/dist/project-detector.js +604 -0
  113. package/dist/render.d.ts +15 -0
  114. package/dist/render.js +46 -0
  115. package/dist/session.d.ts +118 -0
  116. package/dist/session.js +809 -0
  117. package/dist/skills/index.d.ts +69 -0
  118. package/dist/skills/index.js +885 -0
  119. package/dist/skills/types.d.ts +93 -0
  120. package/dist/skills/types.js +8 -0
  121. package/dist/slash-commands.d.ts +14 -0
  122. package/dist/slash-commands.js +301 -0
  123. package/dist/state-graph.d.ts +38 -0
  124. package/dist/state-graph.js +255 -0
  125. package/dist/status-bar.d.ts +54 -0
  126. package/dist/status-bar.js +184 -0
  127. package/dist/thinking-display.d.ts +21 -0
  128. package/dist/thinking-display.js +37 -0
  129. package/dist/tool-summary.d.ts +4 -0
  130. package/dist/tool-summary.js +67 -0
  131. package/dist/tools/index.d.ts +925 -0
  132. package/dist/tools/index.js +86 -0
  133. package/dist/tools/knowledge.d.ts +140 -0
  134. package/dist/tools/knowledge.js +260 -0
  135. package/dist/tools/memory.d.ts +39 -0
  136. package/dist/tools/memory.js +300 -0
  137. package/dist/tools/search-code.d.ts +134 -0
  138. package/dist/tools/search-code.js +390 -0
  139. package/dist/tools/system.d.ts +16 -0
  140. package/dist/tools/system.js +499 -0
  141. package/dist/tools/tool-def.d.ts +6 -0
  142. package/dist/tools/tool-def.js +3 -0
  143. package/dist/turn-control.d.ts +51 -0
  144. package/dist/turn-control.js +210 -0
  145. package/dist/turn.d.ts +20 -0
  146. package/dist/turn.js +624 -0
  147. package/dist/types.d.ts +233 -0
  148. package/dist/types.js +4 -0
  149. package/dist/ui/readline-ui.d.ts +2 -0
  150. package/dist/ui/readline-ui.js +176 -0
  151. package/dist/ui/tui/app.d.ts +13 -0
  152. package/dist/ui/tui/app.js +270 -0
  153. package/dist/ui/tui/busy-indicator.d.ts +2 -0
  154. package/dist/ui/tui/busy-indicator.js +13 -0
  155. package/dist/ui/tui/components/gutter-rule.d.ts +5 -0
  156. package/dist/ui/tui/components/gutter-rule.js +9 -0
  157. package/dist/ui/tui/components/inline-tool-row.d.ts +10 -0
  158. package/dist/ui/tui/components/inline-tool-row.js +8 -0
  159. package/dist/ui/tui/components/prompt-input.d.ts +20 -0
  160. package/dist/ui/tui/components/prompt-input.js +120 -0
  161. package/dist/ui/tui/components/system-line.d.ts +5 -0
  162. package/dist/ui/tui/components/system-line.js +6 -0
  163. package/dist/ui/tui/components/thinking-block.d.ts +11 -0
  164. package/dist/ui/tui/components/thinking-block.js +31 -0
  165. package/dist/ui/tui/components/toast-line.d.ts +4 -0
  166. package/dist/ui/tui/components/toast-line.js +8 -0
  167. package/dist/ui/tui/components/tool-result-line.d.ts +5 -0
  168. package/dist/ui/tui/components/tool-result-line.js +6 -0
  169. package/dist/ui/tui/components/turn-footer.d.ts +5 -0
  170. package/dist/ui/tui/components/turn-footer.js +7 -0
  171. package/dist/ui/tui/components/user-block.d.ts +6 -0
  172. package/dist/ui/tui/components/user-block.js +6 -0
  173. package/dist/ui/tui/logo-banner.d.ts +5 -0
  174. package/dist/ui/tui/logo-banner.js +8 -0
  175. package/dist/ui/tui/markdown-render.d.ts +16 -0
  176. package/dist/ui/tui/markdown-render.js +218 -0
  177. package/dist/ui/tui/palette.d.ts +12 -0
  178. package/dist/ui/tui/palette.js +13 -0
  179. package/dist/ui/tui/reasoning-summary.d.ts +12 -0
  180. package/dist/ui/tui/reasoning-summary.js +27 -0
  181. package/dist/ui/tui/reducer.d.ts +92 -0
  182. package/dist/ui/tui/reducer.js +260 -0
  183. package/dist/ui/tui/run.d.ts +3 -0
  184. package/dist/ui/tui/run.js +40 -0
  185. package/dist/ui/tui/sink.d.ts +4 -0
  186. package/dist/ui/tui/sink.js +89 -0
  187. package/dist/ui/tui/status-bar-view.d.ts +5 -0
  188. package/dist/ui/tui/status-bar-view.js +44 -0
  189. package/dist/ui/tui/terminal-height.d.ts +12 -0
  190. package/dist/ui/tui/terminal-height.js +20 -0
  191. package/dist/ui/tui/terminal-width.d.ts +2 -0
  192. package/dist/ui/tui/terminal-width.js +5 -0
  193. package/dist/ui/tui/tool-display.d.ts +23 -0
  194. package/dist/ui/tui/tool-display.js +217 -0
  195. package/dist/ui/tui/transcript-line.d.ts +12 -0
  196. package/dist/ui/tui/transcript-line.js +43 -0
  197. package/dist/ui/tui/transcript-replay.d.ts +12 -0
  198. package/dist/ui/tui/transcript-replay.js +117 -0
  199. package/dist/ui-events.d.ts +39 -0
  200. package/dist/ui-events.js +33 -0
  201. package/dist/ui.d.ts +77 -0
  202. package/dist/ui.js +179 -0
  203. package/package.json +73 -0
  204. package/praana.config.example.toml +231 -0
@@ -0,0 +1,297 @@
1
+ import { buildActiveState, buildCrossSessionMemory, buildPeripheralStubs, buildSystemFrame, buildStateSummary, trimAgentsContext, trimSectionToTokenBudget, } from "../compiler.js";
2
+ import { buildArtifactCard } from "./summarize.js";
3
+ import { getAppLogger } from "../logger.js";
4
+ import { estimateTokens } from "./summarize.js";
5
+ import { rankContextUnits, recencyScore, scoreContextUnit, selectUnitsWithinBudget, unitTokens, } from "./scoring.js";
6
+ const BAND_VERBATIM_TOKENS = 3000;
7
+ const BAND_SCORED_RECENT_TOKENS = 3000;
8
+ const BAND_SCORED_OLDER_TOKENS = 2000;
9
+ function estTokens(text) {
10
+ return estimateTokens(text);
11
+ }
12
+ function renderVerbatimTurn(record) {
13
+ const lines = [
14
+ `### Turn ${record.turn}`,
15
+ `User: ${record.userMessage}`,
16
+ `PRAANA: ${record.assistantMessage}`,
17
+ ];
18
+ for (const tc of record.toolCalls) {
19
+ lines.push(`Tool call: ${tc.tool}(${JSON.stringify(tc.args)})`);
20
+ if (tc.resultArtifactId) {
21
+ lines.push(`Result: [artifact: ${tc.resultArtifactId}]`);
22
+ }
23
+ else if (tc.resultText) {
24
+ lines.push(`Result: ${tc.resultText}`);
25
+ }
26
+ }
27
+ return lines.join("\n");
28
+ }
29
+ function renderTurnDigest(record) {
30
+ const toolSummary = record.toolCalls.length === 0
31
+ ? "no tools"
32
+ : [...new Set(record.toolCalls.map((tc) => tc.tool))].join(", ");
33
+ return [
34
+ `### Turn ${record.turn} digest`,
35
+ `User: ${record.userMessage}`,
36
+ `Assistant: ${record.assistantMessage.slice(0, 400)}`,
37
+ `Tools: ${toolSummary}`,
38
+ record.filesWritten.length
39
+ ? `Files: ${record.filesWritten.join(", ")}`
40
+ : null,
41
+ record.errors.length ? `Errors: ${record.errors.join("; ")}` : null,
42
+ ]
43
+ .filter(Boolean)
44
+ .join("\n");
45
+ }
46
+ function buildArtifactUnit(tc, turn) {
47
+ if (!tc.resultArtifactId)
48
+ return null;
49
+ const command = typeof tc.args.command === "string"
50
+ ? tc.args.command
51
+ : typeof tc.args.path === "string"
52
+ ? tc.args.path
53
+ : undefined;
54
+ const summary = tc.resultText?.slice(0, 600) ?? "(stored artifact)";
55
+ const content = buildArtifactCard(tc.resultArtifactId, tc.tool, command, unitTokens(summary), summary);
56
+ return {
57
+ id: tc.resultArtifactId,
58
+ type: "artifact_card",
59
+ content,
60
+ tokens: unitTokens(content),
61
+ sourceTurn: turn,
62
+ score: 0,
63
+ pinned: false,
64
+ artifactRefs: [tc.resultArtifactId],
65
+ };
66
+ }
67
+ function buildScoredUnits(records, currentTurn, activityEntries, pressureMode) {
68
+ const units = [];
69
+ for (const record of records) {
70
+ const age = currentTurn - record.turn;
71
+ if (age <= 2)
72
+ continue;
73
+ if (age >= 7)
74
+ continue;
75
+ if (pressureMode === "emergency") {
76
+ if (age > 0)
77
+ continue;
78
+ for (const tc of record.toolCalls) {
79
+ const unit = buildArtifactUnit(tc, record.turn);
80
+ if (unit)
81
+ units.push(unit);
82
+ }
83
+ continue;
84
+ }
85
+ if (pressureMode === "compact" && age > 6) {
86
+ continue;
87
+ }
88
+ if (age >= 3 && age <= 6) {
89
+ const digest = renderTurnDigest(record);
90
+ units.push({
91
+ id: `turn_${record.turn}`,
92
+ type: "turn_digest",
93
+ content: digest,
94
+ tokens: unitTokens(digest),
95
+ sourceTurn: record.turn,
96
+ score: 0,
97
+ pinned: false,
98
+ artifactRefs: record.artifactIds,
99
+ });
100
+ }
101
+ for (const tc of record.toolCalls) {
102
+ const unit = buildArtifactUnit(tc, record.turn);
103
+ if (unit)
104
+ units.push(unit);
105
+ }
106
+ }
107
+ for (const entry of activityEntries) {
108
+ const content = `[turn ${entry.turn}] ${entry.summary}`;
109
+ units.push({
110
+ id: `activity_${entry.turn}_${entry.type}_${entry.summary.slice(0, 24)}`,
111
+ type: "activity_entry",
112
+ content,
113
+ tokens: unitTokens(content),
114
+ sourceTurn: entry.turn,
115
+ score: 0,
116
+ pinned: false,
117
+ artifactRefs: entry.artifactRef ? [entry.artifactRef] : [],
118
+ });
119
+ }
120
+ return units;
121
+ }
122
+ function buildVerbatimSection(records, currentTurn) {
123
+ const recent = records
124
+ .filter((r) => currentTurn - r.turn <= 2 && currentTurn - r.turn >= 0)
125
+ .sort((a, b) => a.turn - b.turn);
126
+ if (recent.length === 0) {
127
+ return { text: "# Recent Turns\n\n(no recent turns)", tokens: estTokens("# Recent Turns") };
128
+ }
129
+ let body = recent.map(renderVerbatimTurn).join("\n\n");
130
+ let tokens = estTokens(body);
131
+ if (tokens > BAND_VERBATIM_TOKENS) {
132
+ const last = recent[recent.length - 1];
133
+ body = renderVerbatimTurn(last);
134
+ tokens = estTokens(body);
135
+ }
136
+ const text = ["# Recent Turns (verbatim)", "", body].join("\n");
137
+ return { text, tokens: estTokens(text) };
138
+ }
139
+ function resolvePressureMode(pressureRatio, config) {
140
+ if (pressureRatio > config.pressure.emergency_at)
141
+ return "emergency";
142
+ if (pressureRatio > config.pressure.compact_at)
143
+ return "compact";
144
+ return "normal";
145
+ }
146
+ export function compileEngineWithMetrics(input) {
147
+ const sections = [];
148
+ const metrics = {};
149
+ const compileTurn = input.currentTurn + 1;
150
+ const scoreRecords = [];
151
+ const reservedOutput = input.reservedOutputTokens ?? 0;
152
+ const contextWindow = input.contextWindowTokens ?? input.tokenBudget;
153
+ const usable = Math.max(0, Math.min(input.tokenBudget, contextWindow) - reservedOutput);
154
+ const pressureDenominator = Math.max(1, contextWindow - reservedOutput);
155
+ const maxMemoryTokens = Math.floor(usable * (input.memoriesBudgetRatio ?? 0.2));
156
+ const maxAgentsTokens = Math.floor(usable * (input.agentsBudgetRatio ?? 0.3));
157
+ const maxSkillsSectionTokens = Math.floor(usable * (input.skillsSectionBudgetRatio ?? 0.2));
158
+ const stateSummary = buildStateSummary(input.stateGraph);
159
+ const { text: agentsContext, truncated: agentsTruncated } = trimAgentsContext(input.agentsContext, maxAgentsTokens);
160
+ metrics.agentsContextTruncated = agentsTruncated;
161
+ const frame = buildSystemFrame(input.cwd, input.sessionId, input.toolSchemas, stateSummary, agentsContext);
162
+ sections.push(frame);
163
+ metrics.systemFrameTokens = estTokens(frame);
164
+ metrics.agentsContextTokens = agentsContext ? estTokens(agentsContext) : 0;
165
+ let skillsSection = "";
166
+ if (input.skillsPromptSection) {
167
+ const { text, truncated } = trimSectionToTokenBudget(input.skillsPromptSection, maxSkillsSectionTokens, "skills section truncated to token budget");
168
+ skillsSection = text;
169
+ metrics.skillsTruncated = truncated;
170
+ sections.push(skillsSection);
171
+ }
172
+ else {
173
+ metrics.skillsTruncated = false;
174
+ }
175
+ metrics.skillsCatalogTokens = skillsSection ? estTokens(skillsSection) : 0;
176
+ let checkpointSection = "";
177
+ if (input.checkpointSection?.trim()) {
178
+ checkpointSection = input.checkpointSection.trim();
179
+ sections.push(checkpointSection);
180
+ }
181
+ metrics.checkpointTokens = checkpointSection ? estTokens(checkpointSection) : 0;
182
+ const verbatim = buildVerbatimSection(input.turnRecords, input.currentTurn);
183
+ sections.push(verbatim.text);
184
+ metrics.recentTurnsTokens = verbatim.tokens;
185
+ metrics.recentTurnsTruncated = false;
186
+ const pinnedTokens = estTokens(sections.join("\n\n")) +
187
+ (input.userInput ? estTokens(`## Current Input\n\nUser: ${input.userInput}`) : 0);
188
+ let pressureRatio = pinnedTokens / pressureDenominator;
189
+ let pressureMode = resolvePressureMode(pressureRatio, input.engineConfig);
190
+ const activityEntries = input.activityEntries ?? [];
191
+ let scoredUnits = buildScoredUnits(input.turnRecords, input.currentTurn, activityEntries, pressureMode);
192
+ const weights = input.engineConfig.scoring;
193
+ const userInput = input.userInput ?? "";
194
+ const recentUnits = scoredUnits.filter((u) => input.currentTurn - u.sourceTurn >= 3 && input.currentTurn - u.sourceTurn <= 6);
195
+ const olderUnits = scoredUnits.filter((u) => input.currentTurn - u.sourceTurn > 6);
196
+ const rankedRecent = rankContextUnits(recentUnits, input.currentTurn, userInput, weights);
197
+ const rankedOlder = rankContextUnits(olderUnits, input.currentTurn, userInput, weights);
198
+ const recentPick = selectUnitsWithinBudget(rankedRecent, BAND_SCORED_RECENT_TOKENS);
199
+ const olderPick = selectUnitsWithinBudget(rankedOlder, BAND_SCORED_OLDER_TOKENS);
200
+ const recordScore = (unit, included, band) => {
201
+ scoreRecords.push({
202
+ turn: compileTurn,
203
+ unitId: unit.id,
204
+ type: unit.type,
205
+ score: Number(unit.score.toFixed(4)),
206
+ included,
207
+ band,
208
+ tokens: unit.tokens,
209
+ breakdown: {
210
+ pin: Number(unit.breakdown.pin.toFixed(4)),
211
+ recency: Number(unit.breakdown.recency.toFixed(4)),
212
+ relevance: Number(unit.breakdown.relevance.toFixed(4)),
213
+ },
214
+ });
215
+ };
216
+ for (const unit of rankedRecent) {
217
+ recordScore(unit, recentPick.included.some((u) => u.id === unit.id), 4);
218
+ }
219
+ for (const unit of rankedOlder) {
220
+ recordScore(unit, olderPick.included.some((u) => u.id === unit.id), 5);
221
+ }
222
+ const scoredSections = [];
223
+ const includedScored = [...recentPick.included, ...olderPick.included];
224
+ if (includedScored.length > 0) {
225
+ scoredSections.push("# Scored Context", "", ...includedScored.map((u) => u.content));
226
+ sections.push(scoredSections.join("\n"));
227
+ }
228
+ let crossSection = "";
229
+ if (input.memoryDigest && input.memoryDigest.trim()) {
230
+ const { text, truncated } = trimSectionToTokenBudget(buildCrossSessionMemory(input.memoryDigest), maxMemoryTokens);
231
+ crossSection = text;
232
+ metrics.memoryTruncated = truncated;
233
+ sections.push(crossSection);
234
+ metrics.crossSessionTokens = estTokens(crossSection);
235
+ }
236
+ else {
237
+ metrics.crossSessionTokens = 0;
238
+ metrics.memoryTruncated = false;
239
+ }
240
+ const active = buildActiveState(input.stateGraph);
241
+ sections.push(active);
242
+ metrics.activeStateTokens = estTokens(active);
243
+ metrics.activeObjectCount = input.stateGraph.getActive().length;
244
+ const peripheral = buildPeripheralStubs(input.stateGraph);
245
+ if (peripheral) {
246
+ sections.push(peripheral);
247
+ metrics.peripheralStubsTokens = estTokens(peripheral);
248
+ metrics.peripheralObjectCount = input.stateGraph.getPeripheral().length;
249
+ }
250
+ else {
251
+ metrics.peripheralStubsTokens = 0;
252
+ metrics.peripheralObjectCount = 0;
253
+ }
254
+ let currentSection = "";
255
+ if (input.userInput) {
256
+ currentSection = `## Current Input\n\nUser: ${input.userInput}`;
257
+ sections.push(currentSection);
258
+ }
259
+ metrics.currentInputTokens = estTokens(currentSection);
260
+ const fullPrompt = sections.join("\n\n");
261
+ metrics.totalTokens = estTokens(fullPrompt);
262
+ pressureRatio = metrics.totalTokens / pressureDenominator;
263
+ pressureMode = resolvePressureMode(pressureRatio, input.engineConfig);
264
+ if (metrics.totalTokens > usable) {
265
+ getAppLogger().child("compiler").warn(`Prompt estimated at ${metrics.totalTokens} tokens, exceeds usable budget of ${usable} (window ${contextWindow}).`);
266
+ }
267
+ const excludedScoredUnits = rankedRecent.length -
268
+ recentPick.included.length +
269
+ (rankedOlder.length - olderPick.included.length);
270
+ return {
271
+ prompt: fullPrompt,
272
+ metrics: metrics,
273
+ scoreRecords,
274
+ pressureRatio,
275
+ pressureMode,
276
+ excludedScoredUnits,
277
+ };
278
+ }
279
+ export function explainUnitScore(unitId, records, currentTurn, userInput, weights, bandTokenBudget, bandUsedTokens) {
280
+ const record = records.find((r) => r.unitId === unitId);
281
+ if (!record) {
282
+ return [`No score record for unit "${unitId}" on the last compile.`];
283
+ }
284
+ const lines = [
285
+ `Unit ${unitId} (${record.type}, turn ${currentTurn})`,
286
+ `Score: ${record.score} (${record.included ? "included" : "excluded"} in prompt)`,
287
+ ` pin: ${record.breakdown.pin.toFixed(2)}`,
288
+ ` recency: ${record.breakdown.recency.toFixed(2)} (weight ${weights.w_recency})`,
289
+ ` relevance: ${record.breakdown.relevance.toFixed(2)} (weight ${weights.w_relevance})`,
290
+ `Budget band ${record.band}: ${bandUsedTokens}/${bandTokenBudget} tokens used`,
291
+ ];
292
+ if (userInput.trim()) {
293
+ lines.push(`Relevance query: "${userInput.trim().slice(0, 80)}"`);
294
+ }
295
+ return lines;
296
+ }
297
+ export { recencyScore, scoreContextUnit };
@@ -0,0 +1,21 @@
1
+ import type { OpenError, TurnRecord } from "./types.js";
2
+ export declare function toolErrorKey(tool: string, args: Record<string, unknown>): string;
3
+ export declare function isTestCommand(command: string): boolean;
4
+ export declare class ErrorTracker {
5
+ private openErrors;
6
+ private testFailed;
7
+ constructor(initial?: {
8
+ openErrors?: OpenError[];
9
+ testFailed?: boolean;
10
+ });
11
+ getOpenErrors(): OpenError[];
12
+ isTestFailed(): boolean;
13
+ serialize(): {
14
+ openErrors: OpenError[];
15
+ testFailed: boolean;
16
+ };
17
+ processTurn(turn: number, record: TurnRecord): {
18
+ errorsNew: string[];
19
+ errorsFixed: string[];
20
+ };
21
+ }
@@ -0,0 +1,74 @@
1
+ export function toolErrorKey(tool, args) {
2
+ const command = typeof args.command === "string"
3
+ ? args.command
4
+ : typeof args.path === "string"
5
+ ? args.path
6
+ : JSON.stringify(args);
7
+ return `${tool}:${command}`;
8
+ }
9
+ export function isTestCommand(command) {
10
+ return /\b(npm test|pnpm test|yarn test|vitest|pytest|cargo test|go test)\b/i.test(command);
11
+ }
12
+ export class ErrorTracker {
13
+ openErrors = new Map();
14
+ testFailed = false;
15
+ constructor(initial) {
16
+ for (const err of initial?.openErrors ?? []) {
17
+ this.openErrors.set(err.key, err);
18
+ }
19
+ this.testFailed = initial?.testFailed ?? false;
20
+ }
21
+ getOpenErrors() {
22
+ return [...this.openErrors.values()];
23
+ }
24
+ isTestFailed() {
25
+ return this.testFailed;
26
+ }
27
+ serialize() {
28
+ return {
29
+ openErrors: this.getOpenErrors(),
30
+ testFailed: this.testFailed,
31
+ };
32
+ }
33
+ processTurn(turn, record) {
34
+ const errorsNew = [];
35
+ const errorsFixed = [];
36
+ for (const tc of record.toolCalls) {
37
+ const key = toolErrorKey(tc.tool, tc.args);
38
+ const command = typeof tc.args.command === "string" ? tc.args.command : undefined;
39
+ if (tc.isError || record.errors.length > 0) {
40
+ const message = record.errors.find((e) => e.length > 0) ??
41
+ tc.resultText?.slice(0, 200) ??
42
+ "tool error";
43
+ if (tc.isError && !this.openErrors.has(key)) {
44
+ this.openErrors.set(key, {
45
+ key,
46
+ message,
47
+ turn,
48
+ tool: tc.tool,
49
+ command,
50
+ });
51
+ errorsNew.push(message);
52
+ }
53
+ if (command && isTestCommand(command)) {
54
+ this.testFailed = true;
55
+ }
56
+ continue;
57
+ }
58
+ if (this.openErrors.has(key)) {
59
+ const prev = this.openErrors.get(key);
60
+ this.openErrors.delete(key);
61
+ const label = prev.command ?? prev.tool;
62
+ errorsFixed.push(label);
63
+ }
64
+ if (command && isTestCommand(command)) {
65
+ this.testFailed = false;
66
+ }
67
+ }
68
+ for (const err of record.errors) {
69
+ if (!errorsNew.includes(err))
70
+ errorsNew.push(err);
71
+ }
72
+ return { errorsNew, errorsFixed };
73
+ }
74
+ }
@@ -0,0 +1,26 @@
1
+ import type { ContextArtifact, SessionCheckpoint, TurnDigest, TurnRecord } from "./types.js";
2
+ export interface RelatedArtifactRef {
3
+ id: string;
4
+ label: string;
5
+ turn: number;
6
+ }
7
+ export interface EventLineage {
8
+ artifactId: string;
9
+ sourceTool: string;
10
+ command?: string;
11
+ contentType: string;
12
+ producedTurn: number;
13
+ producedBy: string;
14
+ relatedDecisions: string[];
15
+ relatedArtifacts: RelatedArtifactRef[];
16
+ relatedFiles: string[];
17
+ }
18
+ export declare function buildEventLineage(input: {
19
+ artifact: ContextArtifact;
20
+ turnRecord: TurnRecord | null;
21
+ turnDigest: TurnDigest | null;
22
+ checkpoint: SessionCheckpoint | null;
23
+ sessionArtifacts: ContextArtifact[];
24
+ turnRecords: TurnRecord[];
25
+ }): EventLineage;
26
+ export declare function formatEventLineage(lineage: EventLineage): string;
@@ -0,0 +1,120 @@
1
+ export function buildEventLineage(input) {
2
+ const { artifact, turnRecord, turnDigest, checkpoint, sessionArtifacts, turnRecords } = input;
3
+ const producedTurn = artifact.createdTurn;
4
+ const producedBy = describeProducer(artifact, turnRecord);
5
+ const relatedFiles = collectRelatedFiles(turnRecord, turnDigest);
6
+ const relatedDecisions = collectRelatedDecisions(turnDigest, checkpoint, producedTurn);
7
+ const relatedArtifacts = collectRelatedArtifacts(artifact, sessionArtifacts, turnRecords, relatedFiles);
8
+ return {
9
+ artifactId: artifact.id,
10
+ sourceTool: artifact.sourceTool,
11
+ command: artifact.command,
12
+ contentType: artifact.contentType,
13
+ producedTurn,
14
+ producedBy,
15
+ relatedDecisions,
16
+ relatedArtifacts,
17
+ relatedFiles,
18
+ };
19
+ }
20
+ function describeProducer(artifact, turnRecord) {
21
+ if (turnRecord) {
22
+ const toolCall = turnRecord.toolCalls.find((tc) => tc.resultArtifactId === artifact.id);
23
+ if (toolCall) {
24
+ const command = typeof toolCall.args.command === "string"
25
+ ? toolCall.args.command
26
+ : typeof toolCall.args.path === "string"
27
+ ? toolCall.args.path
28
+ : undefined;
29
+ if (command) {
30
+ return `${toolCall.tool} command "${command}" in turn ${turnRecord.turn}`;
31
+ }
32
+ return `${toolCall.tool} in turn ${turnRecord.turn}`;
33
+ }
34
+ }
35
+ if (artifact.command) {
36
+ return `${artifact.sourceTool} command "${artifact.command}" in turn ${artifact.createdTurn}`;
37
+ }
38
+ return `${artifact.sourceTool} in turn ${artifact.createdTurn}`;
39
+ }
40
+ function collectRelatedFiles(turnRecord, turnDigest) {
41
+ const files = new Set();
42
+ for (const path of turnRecord?.filesRead ?? [])
43
+ files.add(path);
44
+ for (const path of turnRecord?.filesWritten ?? [])
45
+ files.add(path);
46
+ for (const path of turnDigest?.filesChanged ?? [])
47
+ files.add(path);
48
+ return [...files];
49
+ }
50
+ function collectRelatedDecisions(turnDigest, checkpoint, producedTurn) {
51
+ const decisions = new Set();
52
+ for (const decision of turnDigest?.decisions ?? []) {
53
+ decisions.add(typeof decision === "string" ? decision : decision.summary);
54
+ }
55
+ for (const decision of checkpoint?.state.decisions ?? []) {
56
+ if (decision.turn <= producedTurn) {
57
+ decisions.add(decision.summary);
58
+ }
59
+ }
60
+ return [...decisions].slice(-10);
61
+ }
62
+ function collectRelatedArtifacts(artifact, sessionArtifacts, turnRecords, relatedFiles) {
63
+ const refs = [];
64
+ const seen = new Set([artifact.id]);
65
+ const fileSet = new Set(relatedFiles);
66
+ const add = (item) => {
67
+ if (seen.has(item.id))
68
+ return;
69
+ seen.add(item.id);
70
+ const label = item.command
71
+ ? `${item.sourceTool}: ${item.command}`
72
+ : item.sourceTool;
73
+ refs.push({ id: item.id, label, turn: item.createdTurn });
74
+ };
75
+ for (const other of sessionArtifacts) {
76
+ if (other.createdTurn === artifact.createdTurn) {
77
+ add(other);
78
+ }
79
+ }
80
+ for (const delta of [-1, 1]) {
81
+ const neighborTurn = artifact.createdTurn + delta;
82
+ for (const other of sessionArtifacts) {
83
+ if (other.createdTurn !== neighborTurn)
84
+ continue;
85
+ add(other);
86
+ }
87
+ }
88
+ if (fileSet.size > 0) {
89
+ for (const record of turnRecords) {
90
+ const recordFiles = [...record.filesRead, ...record.filesWritten];
91
+ if (!recordFiles.some((path) => fileSet.has(path)))
92
+ continue;
93
+ for (const artifactId of record.artifactIds) {
94
+ const match = sessionArtifacts.find((a) => a.id === artifactId);
95
+ if (match)
96
+ add(match);
97
+ }
98
+ }
99
+ }
100
+ return refs.sort((a, b) => a.turn - b.turn || a.id.localeCompare(b.id));
101
+ }
102
+ export function formatEventLineage(lineage) {
103
+ const header = lineage.command
104
+ ? `${lineage.artifactId} (${lineage.command}, turn ${lineage.producedTurn})`
105
+ : `${lineage.artifactId} (${lineage.sourceTool}, turn ${lineage.producedTurn})`;
106
+ const lines = [
107
+ `Artifact ${header}`,
108
+ `Produced by: ${lineage.producedBy}`,
109
+ ];
110
+ if (lineage.relatedDecisions.length > 0) {
111
+ lines.push(`Related decisions: ${lineage.relatedDecisions.join(", ")}`);
112
+ }
113
+ if (lineage.relatedArtifacts.length > 0) {
114
+ lines.push("Related artifacts:", ...lineage.relatedArtifacts.map((ref) => `- ${ref.id} (${ref.label}, turn ${ref.turn})`));
115
+ }
116
+ if (lineage.relatedFiles.length > 0) {
117
+ lines.push(`Related files: ${lineage.relatedFiles.join(", ")}`);
118
+ }
119
+ return lines.join("\n");
120
+ }
@@ -0,0 +1,26 @@
1
+ import type Database from "better-sqlite3";
2
+ import type { StateGraph } from "../state-graph.js";
3
+ import type { ContextEngineConfig } from "../types.js";
4
+ import type { ActivityEntry, CheckpointDraft, StateSnapshot, TurnDigest, TurnRecord } from "./types.js";
5
+ export declare class TurnExtraction {
6
+ private readonly db;
7
+ private readonly sessionId;
8
+ private readonly config;
9
+ private readonly errorTracker;
10
+ private readonly activityLog;
11
+ private recentDecisions;
12
+ private recentConstraints;
13
+ private lastUserIntent;
14
+ private lastDigest;
15
+ constructor(db: Database.Database, sessionId: string, config: ContextEngineConfig);
16
+ captureStateSnapshot(stateGraph: StateGraph): StateSnapshot;
17
+ processTurn(input: {
18
+ userMessage: string;
19
+ record: TurnRecord;
20
+ stateBefore: StateSnapshot;
21
+ stateGraph: StateGraph;
22
+ }): TurnDigest;
23
+ getLatestDigest(): TurnDigest | null;
24
+ getRecentActivity(): ActivityEntry[];
25
+ getCheckpointDraft(): CheckpointDraft;
26
+ }
@@ -0,0 +1,83 @@
1
+ import { ActivityLog, deriveActivityEntries } from "./activity-log.js";
2
+ import { getExtractionState, insertActivityEntries, insertTurnDigest, listActivityEntries, upsertExtractionState, } from "./db.js";
3
+ import { ErrorTracker } from "./error-tracker.js";
4
+ import { snapshotStateGraph } from "./state-snapshot.js";
5
+ import { extractTurnDigest } from "./turn-digest.js";
6
+ export class TurnExtraction {
7
+ db;
8
+ sessionId;
9
+ config;
10
+ errorTracker;
11
+ activityLog;
12
+ recentDecisions = [];
13
+ recentConstraints = [];
14
+ lastUserIntent = "";
15
+ lastDigest = null;
16
+ constructor(db, sessionId, config) {
17
+ this.db = db;
18
+ this.sessionId = sessionId;
19
+ this.config = config;
20
+ const saved = getExtractionState(db, sessionId);
21
+ this.errorTracker = new ErrorTracker({
22
+ openErrors: saved?.openErrors,
23
+ testFailed: saved?.testFailed,
24
+ });
25
+ this.activityLog = new ActivityLog(config.activity_log_max_entries, listActivityEntries(db, sessionId, config.activity_log_max_entries));
26
+ if (saved) {
27
+ this.recentDecisions = saved.recentDecisions;
28
+ this.recentConstraints = saved.recentConstraints;
29
+ this.lastUserIntent = saved.lastUserIntent;
30
+ }
31
+ }
32
+ captureStateSnapshot(stateGraph) {
33
+ return snapshotStateGraph(stateGraph);
34
+ }
35
+ processTurn(input) {
36
+ const turn = input.record.turn;
37
+ const testWasFailing = this.errorTracker.isTestFailed();
38
+ const { errorsNew, errorsFixed } = this.errorTracker.processTurn(turn, input.record);
39
+ const digest = extractTurnDigest({
40
+ turn,
41
+ userMessage: input.userMessage,
42
+ record: input.record,
43
+ stateBefore: input.stateBefore,
44
+ stateGraph: input.stateGraph,
45
+ errorsNew,
46
+ errorsFixed,
47
+ });
48
+ const activityEntries = deriveActivityEntries(turn, digest, input.record, testWasFailing);
49
+ this.lastDigest = digest;
50
+ this.lastUserIntent = digest.userIntent;
51
+ this.recentDecisions.push(...digest.decisions.map((decision) => ({
52
+ summary: typeof decision === "string" ? decision : decision.summary,
53
+ turn,
54
+ })));
55
+ this.recentConstraints.push(...digest.constraints);
56
+ this.activityLog.append(activityEntries);
57
+ insertTurnDigest(this.db, this.sessionId, digest);
58
+ insertActivityEntries(this.db, this.sessionId, activityEntries);
59
+ upsertExtractionState(this.db, this.sessionId, {
60
+ openErrors: this.errorTracker.getOpenErrors(),
61
+ testFailed: this.errorTracker.isTestFailed(),
62
+ recentDecisions: this.recentDecisions.slice(-30),
63
+ recentConstraints: this.recentConstraints.slice(-30),
64
+ lastUserIntent: this.lastUserIntent,
65
+ });
66
+ return digest;
67
+ }
68
+ getLatestDigest() {
69
+ return this.lastDigest;
70
+ }
71
+ getRecentActivity() {
72
+ return this.activityLog.list();
73
+ }
74
+ getCheckpointDraft() {
75
+ return {
76
+ lastUserIntent: this.lastUserIntent,
77
+ openErrors: this.errorTracker.getOpenErrors(),
78
+ recentDecisions: [...this.recentDecisions],
79
+ recentConstraints: [...this.recentConstraints],
80
+ recentActivity: this.activityLog.list(),
81
+ };
82
+ }
83
+ }