u-foo 2.5.13 → 2.5.15

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 (39) hide show
  1. package/package.json +1 -1
  2. package/src/agents/prompts/native/environment.js +20 -8
  3. package/src/code/agent.js +339 -24
  4. package/src/code/commands.js +61 -0
  5. package/src/code/context/artifactGc.js +292 -0
  6. package/src/code/context/artifactIndex.js +161 -0
  7. package/src/code/context/artifacts.js +183 -0
  8. package/src/code/context/assembler.js +698 -0
  9. package/src/code/context/executionSegment.js +314 -0
  10. package/src/code/context/featureFlag.js +13 -0
  11. package/src/code/context/index.js +18 -0
  12. package/src/code/context/projectSnapshot.js +201 -0
  13. package/src/code/context/promptLayers.js +159 -0
  14. package/src/code/context/reducers.js +328 -0
  15. package/src/code/context/stableJson.js +29 -0
  16. package/src/code/context/stateCommit.js +412 -0
  17. package/src/code/context/transcript.js +182 -0
  18. package/src/code/context/transcriptSync.js +106 -0
  19. package/src/code/context/workingSet.js +323 -0
  20. package/src/code/dispatch.js +4 -1
  21. package/src/code/index.js +6 -0
  22. package/src/code/modelCommand.js +87 -0
  23. package/src/code/nativeRunner.js +187 -31
  24. package/src/code/repl.js +36 -32
  25. package/src/code/sessionStore.js +227 -15
  26. package/src/code/skills/index.js +10 -0
  27. package/src/code/skills/injection.js +65 -3
  28. package/src/code/skills/loader.js +21 -0
  29. package/src/code/skills/manifest.js +87 -0
  30. package/src/code/skills/render.js +15 -1
  31. package/src/code/taskDecomposer.js +32 -2
  32. package/src/code/tools/artifactRead.js +40 -0
  33. package/src/code/tui.js +2 -0
  34. package/src/code/usageStore.js +15 -0
  35. package/src/ui/format/index.js +260 -44
  36. package/src/ui/format/markdownRenderer.js +215 -72
  37. package/src/ui/ink/ChatApp.js +39 -8
  38. package/src/ui/ink/UcodeApp.js +408 -55
  39. package/src/ui/ink/chatLogModel.js +102 -21
@@ -0,0 +1,328 @@
1
+ "use strict";
2
+
3
+ const PREVIEW_MAX_CHARS = 600;
4
+ const MODEL_PAYLOAD_MAX_CHARS = 4000;
5
+
6
+ function clipText(value = "", maxChars = PREVIEW_MAX_CHARS) {
7
+ const text = String(value || "");
8
+ if (text.length <= maxChars) return text;
9
+ return `${text.slice(0, maxChars)}\n...[truncated]`;
10
+ }
11
+
12
+ function tailLines(text = "", count = 20) {
13
+ const lines = String(text || "").split(/\r?\n/);
14
+ if (lines.length <= count) return lines.join("\n");
15
+ return lines.slice(-count).join("\n");
16
+ }
17
+
18
+ function isTestCommand(command = "", stdout = "") {
19
+ return /\b(npm test|pnpm test|yarn test|npx jest|jest|vitest|mocha|pytest|cargo test|go test)\b/i.test(command)
20
+ || /\b\d+\s+(passed|failed)\b/i.test(stdout)
21
+ || /FAIL|PASS|Tests:/i.test(stdout);
22
+ }
23
+
24
+ function isGitDiffCommand(command = "") {
25
+ return /\bgit\s+(?:diff|show)\b/i.test(String(command || ""));
26
+ }
27
+
28
+ function isSearchCommand(command = "") {
29
+ return /\b(rg|ripgrep|grep|ag|ack)\b/i.test(String(command || ""));
30
+ }
31
+
32
+ function extractTestFailures(stdout = "", stderr = "") {
33
+ const text = `${stdout}\n${stderr}`;
34
+ const failures = [];
35
+ const patterns = [
36
+ /●\s+([^\n]+)/g,
37
+ /FAIL\s+([^\n]+)/g,
38
+ /(?:AssertionError|Error):\s*([^\n]+)/g,
39
+ /FAILED\s+([^\n]+)/g,
40
+ ];
41
+ for (const re of patterns) {
42
+ let match;
43
+ while ((match = re.exec(text))) {
44
+ const title = String(match[1] || "").trim();
45
+ if (!title || failures.some((item) => item.title === title)) continue;
46
+ failures.push({ title: title.slice(0, 240) });
47
+ if (failures.length >= 12) return failures;
48
+ }
49
+ }
50
+ return failures;
51
+ }
52
+
53
+ function parseGitDiffFiles(stdout = "") {
54
+ const files = [];
55
+ const lines = String(stdout || "").split(/\r?\n/);
56
+ for (const line of lines) {
57
+ const diffMatch = line.match(/^diff --git a\/(.+?) b\/(.+)$/);
58
+ if (diffMatch) {
59
+ const pathText = String(diffMatch[2] || diffMatch[1] || "").trim();
60
+ if (pathText && !files.includes(pathText)) files.push(pathText);
61
+ continue;
62
+ }
63
+ const statusMatch = line.match(/^[AMD]\t(.+)$/);
64
+ if (statusMatch) {
65
+ const pathText = String(statusMatch[1] || "").trim();
66
+ if (pathText && !files.includes(pathText)) files.push(pathText);
67
+ }
68
+ }
69
+ return files.slice(0, 80);
70
+ }
71
+
72
+ function parseSearchMatches(stdout = "") {
73
+ const matches = [];
74
+ const lines = String(stdout || "").split(/\r?\n/).filter(Boolean);
75
+ for (const line of lines) {
76
+ const match = line.match(/^([^:]+):(\d+)(?::(\d+))?:(.*)$/);
77
+ if (!match) continue;
78
+ matches.push({
79
+ path: match[1],
80
+ line: Number(match[2]),
81
+ column: match[3] ? Number(match[3]) : undefined,
82
+ text: String(match[4] || "").trim().slice(0, 200),
83
+ });
84
+ if (matches.length >= 40) break;
85
+ }
86
+ return matches;
87
+ }
88
+
89
+ function reduceReadResult(raw = {}, artifactId = "") {
90
+ const source = raw && typeof raw === "object" ? raw : {};
91
+ const content = String(source.content || "");
92
+ const preview = clipText(content, PREVIEW_MAX_CHARS);
93
+ const modelPayload = {
94
+ ok: source.ok !== false,
95
+ artifactId,
96
+ path: source.path || "",
97
+ startLine: source.startLine,
98
+ endLine: source.endLine,
99
+ totalLines: source.totalLines,
100
+ truncated: Boolean(source.truncated),
101
+ fileHash: source.fileHash || "",
102
+ preview,
103
+ };
104
+ if (content && content.length <= MODEL_PAYLOAD_MAX_CHARS) {
105
+ modelPayload.content = content;
106
+ } else if (content) {
107
+ modelPayload.content = clipText(content, MODEL_PAYLOAD_MAX_CHARS);
108
+ modelPayload.contentTruncated = true;
109
+ }
110
+ if (source.error) modelPayload.error = String(source.error);
111
+ return {
112
+ preview,
113
+ summary: `read ${source.path || "file"} (${source.totalLines || "?"} lines)`,
114
+ modelPayload,
115
+ };
116
+ }
117
+
118
+ function reduceTestResult(raw = {}, artifactId = "", args = {}) {
119
+ const source = raw && typeof raw === "object" ? raw : {};
120
+ const stdout = String(source.stdout || "");
121
+ const stderr = String(source.stderr || "");
122
+ const failedMatch = stdout.match(/(\d+)\s+failed/i) || stderr.match(/(\d+)\s+failed/i);
123
+ const passedMatch = stdout.match(/(\d+)\s+passed/i) || stderr.match(/(\d+)\s+passed/i);
124
+ const failures = extractTestFailures(stdout, stderr);
125
+ const preview = clipText(
126
+ [stdout ? `stdout:\n${tailLines(stdout, 12)}` : "", stderr ? `stderr:\n${tailLines(stderr, 8)}` : ""]
127
+ .filter(Boolean)
128
+ .join("\n"),
129
+ PREVIEW_MAX_CHARS,
130
+ );
131
+ return {
132
+ preview,
133
+ summary: `test exit=${source.code ?? source.exitCode ?? "?"} failed=${failedMatch ? failedMatch[1] : failures.length}`,
134
+ modelPayload: {
135
+ ok: source.ok !== false,
136
+ artifactId,
137
+ kind: "test",
138
+ exitCode: source.code ?? source.exitCode ?? null,
139
+ passed: passedMatch ? Number(passedMatch[1]) : undefined,
140
+ failed: failedMatch ? Number(failedMatch[1]) : failures.length || undefined,
141
+ failures,
142
+ stdoutTail: tailLines(stdout, 24),
143
+ stderrTail: tailLines(stderr, 12),
144
+ error: source.error ? String(source.error) : undefined,
145
+ },
146
+ };
147
+ }
148
+
149
+ function reduceGitDiffResult(raw = {}, artifactId = "", args = {}) {
150
+ const source = raw && typeof raw === "object" ? raw : {};
151
+ const stdout = String(source.stdout || "");
152
+ const files = parseGitDiffFiles(stdout);
153
+ const preview = clipText(
154
+ files.length > 0
155
+ ? `git diff files (${files.length}):\n${files.slice(0, 20).join("\n")}`
156
+ : tailLines(stdout, 20),
157
+ PREVIEW_MAX_CHARS,
158
+ );
159
+ return {
160
+ preview,
161
+ summary: `git diff files=${files.length}`,
162
+ modelPayload: {
163
+ ok: source.ok !== false,
164
+ artifactId,
165
+ kind: "git_diff",
166
+ exitCode: source.code ?? source.exitCode ?? null,
167
+ files,
168
+ // Current diff stays complete in artifact; model gets file list + short preview.
169
+ stdoutTail: tailLines(stdout, 40),
170
+ stderrTail: tailLines(String(source.stderr || ""), 8),
171
+ error: source.error ? String(source.error) : undefined,
172
+ },
173
+ };
174
+ }
175
+
176
+ function reduceSearchResult(raw = {}, artifactId = "", args = {}) {
177
+ const source = raw && typeof raw === "object" ? raw : {};
178
+ const stdout = String(source.stdout || "");
179
+ const matches = parseSearchMatches(stdout);
180
+ const preview = clipText(
181
+ matches.length > 0
182
+ ? matches.slice(0, 12).map((m) => `${m.path}:${m.line}: ${m.text}`).join("\n")
183
+ : tailLines(stdout, 20),
184
+ PREVIEW_MAX_CHARS,
185
+ );
186
+ return {
187
+ preview,
188
+ summary: `search matches=${matches.length}`,
189
+ modelPayload: {
190
+ ok: source.ok !== false,
191
+ artifactId,
192
+ kind: "search",
193
+ exitCode: source.code ?? source.exitCode ?? null,
194
+ matchCount: matches.length,
195
+ matches,
196
+ stdoutTail: tailLines(stdout, 20),
197
+ stderrTail: tailLines(String(source.stderr || ""), 8),
198
+ error: source.error ? String(source.error) : undefined,
199
+ },
200
+ };
201
+ }
202
+
203
+ function reduceBashResult(raw = {}, artifactId = "", args = {}) {
204
+ const source = raw && typeof raw === "object" ? raw : {};
205
+ const stdout = String(source.stdout || "");
206
+ const stderr = String(source.stderr || "");
207
+ const command = String((args && args.command) || source.command || "");
208
+
209
+ if (isTestCommand(command, stdout)) {
210
+ return reduceTestResult(raw, artifactId, args);
211
+ }
212
+ if (isGitDiffCommand(command)) {
213
+ return reduceGitDiffResult(raw, artifactId, args);
214
+ }
215
+ if (isSearchCommand(command)) {
216
+ return reduceSearchResult(raw, artifactId, args);
217
+ }
218
+
219
+ const preview = clipText(
220
+ [stdout ? `stdout:\n${tailLines(stdout, 8)}` : "", stderr ? `stderr:\n${tailLines(stderr, 8)}` : ""]
221
+ .filter(Boolean)
222
+ .join("\n"),
223
+ PREVIEW_MAX_CHARS,
224
+ );
225
+ return {
226
+ preview,
227
+ summary: `bash exit=${source.code ?? source.exitCode ?? "?"}`,
228
+ modelPayload: {
229
+ ok: source.ok !== false,
230
+ artifactId,
231
+ exitCode: source.code ?? source.exitCode ?? null,
232
+ stdoutTail: tailLines(stdout, 20),
233
+ stderrTail: tailLines(stderr, 20),
234
+ error: source.error ? String(source.error) : undefined,
235
+ },
236
+ };
237
+ }
238
+
239
+ function reduceWriteResult(raw = {}, artifactId = "") {
240
+ const source = raw && typeof raw === "object" ? raw : {};
241
+ const preview = `write ${source.path || "file"} (${source.bytes || 0} bytes)`;
242
+ return {
243
+ preview,
244
+ summary: preview,
245
+ modelPayload: {
246
+ ok: source.ok !== false,
247
+ artifactId,
248
+ path: source.path || "",
249
+ mode: source.mode,
250
+ bytes: source.bytes,
251
+ error: source.error ? String(source.error) : undefined,
252
+ },
253
+ };
254
+ }
255
+
256
+ function reduceEditResult(raw = {}, artifactId = "") {
257
+ const source = raw && typeof raw === "object" ? raw : {};
258
+ const preview = `edit ${source.path || "file"} changed=${Boolean(source.changed)}`;
259
+ return {
260
+ preview,
261
+ summary: preview,
262
+ modelPayload: {
263
+ ok: source.ok !== false,
264
+ artifactId,
265
+ path: source.path || "",
266
+ changed: Boolean(source.changed),
267
+ replacements: source.replacements,
268
+ error: source.error ? String(source.error) : undefined,
269
+ },
270
+ };
271
+ }
272
+
273
+ function reduceArtifactReadResult(raw = {}, artifactId = "") {
274
+ const source = raw && typeof raw === "object" ? raw : {};
275
+ const content = String(source.content || "");
276
+ return {
277
+ preview: clipText(content, PREVIEW_MAX_CHARS),
278
+ summary: `artifact_read ${source.artifactId || artifactId}`,
279
+ modelPayload: {
280
+ ok: source.ok !== false,
281
+ artifactId: source.artifactId || artifactId,
282
+ content: clipText(content, MODEL_PAYLOAD_MAX_CHARS),
283
+ range: source.range,
284
+ truncated: Boolean(source.truncated),
285
+ error: source.error ? String(source.error) : undefined,
286
+ },
287
+ };
288
+ }
289
+
290
+ function reduceToolResult(tool = "", raw = {}, artifactId = "", args = {}) {
291
+ const name = String(tool || "").trim().toLowerCase();
292
+ if (name === "read") return reduceReadResult(raw, artifactId);
293
+ if (name === "bash") return reduceBashResult(raw, artifactId, args);
294
+ if (name === "write") return reduceWriteResult(raw, artifactId);
295
+ if (name === "edit") return reduceEditResult(raw, artifactId);
296
+ if (name === "artifact_read") return reduceArtifactReadResult(raw, artifactId);
297
+ const text = typeof raw === "string" ? raw : JSON.stringify(raw);
298
+ return {
299
+ preview: clipText(text, PREVIEW_MAX_CHARS),
300
+ summary: `${name || "tool"} result`,
301
+ modelPayload: {
302
+ ok: raw && raw.ok !== false,
303
+ artifactId,
304
+ preview: clipText(text, MODEL_PAYLOAD_MAX_CHARS),
305
+ },
306
+ };
307
+ }
308
+
309
+ module.exports = {
310
+ PREVIEW_MAX_CHARS,
311
+ MODEL_PAYLOAD_MAX_CHARS,
312
+ clipText,
313
+ isTestCommand,
314
+ isGitDiffCommand,
315
+ isSearchCommand,
316
+ extractTestFailures,
317
+ parseGitDiffFiles,
318
+ parseSearchMatches,
319
+ reduceToolResult,
320
+ reduceReadResult,
321
+ reduceBashResult,
322
+ reduceTestResult,
323
+ reduceGitDiffResult,
324
+ reduceSearchResult,
325
+ reduceWriteResult,
326
+ reduceEditResult,
327
+ reduceArtifactReadResult,
328
+ };
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+
3
+ function stableValue(value) {
4
+ if (value === null || typeof value !== "object") return value;
5
+ if (Array.isArray(value)) return value.map((item) => stableValue(item));
6
+ const keys = Object.keys(value).sort();
7
+ const out = {};
8
+ for (const key of keys) {
9
+ out[key] = stableValue(value[key]);
10
+ }
11
+ return out;
12
+ }
13
+
14
+ function stableStringify(value) {
15
+ try {
16
+ return JSON.stringify(stableValue(value));
17
+ } catch {
18
+ try {
19
+ return JSON.stringify(value);
20
+ } catch {
21
+ return String(value || "");
22
+ }
23
+ }
24
+ }
25
+
26
+ module.exports = {
27
+ stableValue,
28
+ stableStringify,
29
+ };