@hizliemre/horse-code 0.3.0 → 0.3.1
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.
- package/dist/{app-5FXHE7GX.js → app-4WN37LZ3.js} +65 -37
- package/dist/{chunk-UEWVVN5L.js → chunk-27F44PBD.js} +196 -1126
- package/dist/{chunk-XYZVZPAY.js → chunk-2WXG35EM.js} +19 -15
- package/dist/{chunk-LNW557IO.js → chunk-372X5HHU.js} +2 -2
- package/dist/{chunk-YPZP7LYL.js → chunk-3ACDNDCG.js} +1 -1
- package/dist/{chunk-XEGQT5EN.js → chunk-4M6LXNG2.js} +1 -1
- package/dist/{chunk-6OSEQOYY.js → chunk-6S4WWQMN.js} +2 -2
- package/dist/{chunk-LLL7QWXB.js → chunk-BFIZMM4G.js} +6 -6
- package/dist/chunk-CYLPQWIF.js +214 -0
- package/dist/{chunk-AE36LLL2.js → chunk-JLWQCA7B.js} +2 -209
- package/dist/{run-P6ZYL5JL.js → chunk-QJYVZPLG.js} +133 -389
- package/dist/{chunk-UGESK765.js → chunk-UTHLEW5V.js} +1 -1
- package/dist/chunk-YULQ4URQ.js +1220 -0
- package/dist/{chunk-KAGKX2YT.js → chunk-ZPJP2VH5.js} +10 -1
- package/dist/cli.js +254 -66
- package/dist/{fix-ONLA45HD.js → fix-QCL5AITT.js} +9 -8
- package/dist/{ongoing-WHYXPW24.js → ongoing-6NUSPSCV.js} +3 -2
- package/dist/{project-graph-5HNPRFQG.js → project-graph-OGIM2B33.js} +1 -1
- package/dist/run-V5ZLZ3LS.js +274 -0
- package/dist/{save-skills-ZW5GY6KV.js → save-skills-NPKTYNAF.js} +2 -2
- package/dist/{trace-X6TU3AG6.js → trace-UVMZZRA5.js} +1 -1
- package/dist/{trace-adopt-URECQWJV.js → trace-adopt-7HWELJFE.js} +1 -1
- package/dist/{trace-run-7U4WJZ3V.js → trace-run-CZWEZ4R6.js} +8 -4
- package/dist/{triage-FCYHD2AQ.js → triage-IFCVL5MA.js} +7 -6
- package/dist/{verify-LC57A6H2.js → verify-HWZBTK5X.js} +15 -12
- package/package.json +1 -1
- package/dist/chunk-MRZVA5JB.js +0 -163
- package/dist/{chunk-EAF22QIG.js → chunk-JR2JLRE3.js} +3 -3
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MAX_RULES,
|
|
3
|
+
consolidateRules,
|
|
4
|
+
discover,
|
|
5
|
+
extractAll,
|
|
6
|
+
groupForReview,
|
|
7
|
+
hasAnything,
|
|
8
|
+
summarize
|
|
9
|
+
} from "./chunk-QJYVZPLG.js";
|
|
10
|
+
import {
|
|
11
|
+
loadMigrated,
|
|
12
|
+
recordMigrated
|
|
13
|
+
} from "./chunk-63E73TGI.js";
|
|
14
|
+
import "./chunk-6W4UH2BQ.js";
|
|
15
|
+
|
|
16
|
+
// src/migrate/run.ts
|
|
17
|
+
import { cp, lstat, mkdir, readFile, readlink, rm } from "fs/promises";
|
|
18
|
+
import { dirname, join, resolve } from "path";
|
|
19
|
+
var YES = /^(yes|import|copy|evet)/i;
|
|
20
|
+
function countKind(store, kind) {
|
|
21
|
+
return store.all().filter((m) => (m.kind ?? "fact") === kind).length;
|
|
22
|
+
}
|
|
23
|
+
var SAMPLE = 6;
|
|
24
|
+
function sample(items) {
|
|
25
|
+
return items.slice(0, SAMPLE).map((c) => `- ${c.text}`).join("\n") + (items.length > SAMPLE ? `
|
|
26
|
+
- _\u2026and ${items.length - SAMPLE} more_` : "");
|
|
27
|
+
}
|
|
28
|
+
async function runMigration(deps) {
|
|
29
|
+
const result = { rules: 0, facts: 0, skills: 0, skipped: 0, declined: [], failedBatches: 0, removed: 0 };
|
|
30
|
+
const findings = [...await discover({ cwd: deps.cwd, home: deps.home }), ...deps.extra ?? []];
|
|
31
|
+
if (!hasAnything(findings)) {
|
|
32
|
+
deps.note("No configuration from another coding tool was found here \u2014 nothing to migrate.");
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
deps.note(`**Found in this project:**
|
|
36
|
+
${summarize(findings)}`);
|
|
37
|
+
const prose = findings.filter((f) => (f.kind === "rules" || f.kind === "memory") && f.text);
|
|
38
|
+
if (prose.length) {
|
|
39
|
+
deps.note(`Reading ${prose.length} file(s) to work out which instructions still apply here. _This is the part that costs tokens; nothing is imported until you say so._`);
|
|
40
|
+
deps.busy?.("reading instructions", deps.models[0]);
|
|
41
|
+
const extraction = await extractAll({
|
|
42
|
+
provider: deps.provider,
|
|
43
|
+
models: deps.models,
|
|
44
|
+
findings: prose,
|
|
45
|
+
...deps.signal ? { signal: deps.signal } : {},
|
|
46
|
+
onProgress: (done, total, source) => deps.progress?.(
|
|
47
|
+
"read",
|
|
48
|
+
`\u{1F4D6} Reading **${done}/${total}** \u2014 _${source}_`
|
|
49
|
+
)
|
|
50
|
+
});
|
|
51
|
+
deps.idle?.();
|
|
52
|
+
result.failedBatches = extraction.failed.length;
|
|
53
|
+
if (extraction.failed.length) {
|
|
54
|
+
const reasons = [...new Set(extraction.failed.map((f) => f.error))].slice(0, 3);
|
|
55
|
+
deps.note(`\u26A0\uFE0F **${extraction.failed.length} batch(es) could not be read** \u2014 ` + reasons.map((r) => `\`${r.slice(0, 160)}\``).join("; ") + `
|
|
56
|
+
|
|
57
|
+
_Nothing from those files was imported. Re-run \`/migrate\` once the cause above is cleared._`);
|
|
58
|
+
}
|
|
59
|
+
const raw = groupForReview(extraction);
|
|
60
|
+
result.skipped = raw.skipped.length;
|
|
61
|
+
let rules = raw.rules;
|
|
62
|
+
const facts = [...raw.facts];
|
|
63
|
+
if (rules.length > MAX_RULES) {
|
|
64
|
+
deps.note(`${rules.length} rule candidates \u2014 consolidating, since every rule is inlined into every prompt forever.`);
|
|
65
|
+
deps.busy?.("consolidating rules", deps.models[0]);
|
|
66
|
+
const c = await consolidateRules({
|
|
67
|
+
provider: deps.provider,
|
|
68
|
+
models: deps.models,
|
|
69
|
+
candidates: rules,
|
|
70
|
+
...deps.signal ? { signal: deps.signal } : {}
|
|
71
|
+
});
|
|
72
|
+
deps.idle?.();
|
|
73
|
+
if (c.rules.length < rules.length) {
|
|
74
|
+
deps.note(`Consolidated **${rules.length} \u2192 ${c.rules.length}** rules${c.demoted.length ? `, kept ${c.demoted.length} as fact(s)` : ""}${c.dropped.length ? `, dropped ${c.dropped.length} describing a workflow horse-code replaces` : ""}.`);
|
|
75
|
+
rules = c.rules;
|
|
76
|
+
facts.push(...c.demoted);
|
|
77
|
+
result.skipped += c.dropped.length;
|
|
78
|
+
} else {
|
|
79
|
+
deps.note(`\u26A0\uFE0F Consolidation did not run \u2014 you are being asked about all ${rules.length} candidates.`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (rules.length) {
|
|
83
|
+
const answer = await deps.ask(
|
|
84
|
+
`**${rules.length} standing rule(s)**. A rule goes into EVERY agent's instructions, for every task, permanently.
|
|
85
|
+
|
|
86
|
+
${sample(rules)}
|
|
87
|
+
|
|
88
|
+
Import them?`,
|
|
89
|
+
{ options: [
|
|
90
|
+
{ label: "Yes \u2014 import all", description: `${rules.length} rules become permanent memory` },
|
|
91
|
+
{ label: "No", description: "Nothing is written; you can re-run /migrate later" }
|
|
92
|
+
] }
|
|
93
|
+
);
|
|
94
|
+
if (YES.test(answer.trim())) {
|
|
95
|
+
const before = countKind(deps.memStore, "rule");
|
|
96
|
+
for (const c of rules) await deps.memStore.add(c.text, "rule");
|
|
97
|
+
result.rules = countKind(deps.memStore, "rule") - before;
|
|
98
|
+
} else result.declined.push("rules");
|
|
99
|
+
}
|
|
100
|
+
if (facts.length) {
|
|
101
|
+
const answer = await deps.ask(
|
|
102
|
+
`**${facts.length} project fact(s)** were found \u2014 recalled when relevant rather than always.
|
|
103
|
+
|
|
104
|
+
${sample(facts)}
|
|
105
|
+
|
|
106
|
+
Import them?`,
|
|
107
|
+
{ options: [
|
|
108
|
+
{ label: "Yes \u2014 import all", description: `${facts.length} facts become memory` },
|
|
109
|
+
{ label: "No", description: "Nothing is written" }
|
|
110
|
+
] }
|
|
111
|
+
);
|
|
112
|
+
if (YES.test(answer.trim())) {
|
|
113
|
+
const before = countKind(deps.memStore, "fact");
|
|
114
|
+
for (const c of facts) await deps.memStore.add(c.text, "fact");
|
|
115
|
+
result.facts = countKind(deps.memStore, "fact") - before;
|
|
116
|
+
} else result.declined.push("facts");
|
|
117
|
+
}
|
|
118
|
+
if (raw.skipped.length) {
|
|
119
|
+
const skipped = raw.skipped;
|
|
120
|
+
const why = skipped.slice(0, SAMPLE).map((c) => `- ~~${c.text.slice(0, 90)}~~ \u2014 ${c.reason}`).join("\n");
|
|
121
|
+
deps.note(`**Left behind (${skipped.length})** \u2014 these only meant something in the original tool:
|
|
122
|
+
${why}` + (skipped.length > SAMPLE ? `
|
|
123
|
+
- _\u2026and ${skipped.length - SAMPLE} more_` : "") + `
|
|
124
|
+
|
|
125
|
+
_If one of these should have transferred, add it with \`/memory add\`._`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const existing = new Set(deps.existingSkills?.() ?? []);
|
|
129
|
+
const allSkills = findings.filter((f) => f.kind === "skill");
|
|
130
|
+
const skills = allSkills.filter((f) => !existing.has(f.label.split("/").pop()));
|
|
131
|
+
const alreadyHave = allSkills.length - skills.length;
|
|
132
|
+
if (alreadyHave) deps.note(`${alreadyHave} skill(s) are ones horse-code already ships \u2014 left alone.`);
|
|
133
|
+
if (skills.length) {
|
|
134
|
+
const answer = await deps.ask(
|
|
135
|
+
`**${skills.length} skill(s)** were found. The format is the same as ours, so they transfer as they are:
|
|
136
|
+
${skills.slice(0, SAMPLE).map((s) => `- \`${s.label.split("/").pop()}\``).join("\n")}${skills.length > SAMPLE ? `
|
|
137
|
+
- _\u2026and ${skills.length - SAMPLE} more_` : ""}
|
|
138
|
+
|
|
139
|
+
Copy them into this project?`,
|
|
140
|
+
{ options: [
|
|
141
|
+
{ label: "Yes \u2014 copy all", description: "Copied to .horsecode/skills/, then routed per task" },
|
|
142
|
+
{ label: "No", description: "They stay where they are" }
|
|
143
|
+
] }
|
|
144
|
+
);
|
|
145
|
+
if (YES.test(answer.trim())) {
|
|
146
|
+
const copied = [];
|
|
147
|
+
for (const [i, s] of skills.entries()) {
|
|
148
|
+
const name = s.label.split("/").pop();
|
|
149
|
+
deps.progress?.("copy", `\u{1F4E6} Copying **${i + 1}/${skills.length}** \u2014 \`${name}\``);
|
|
150
|
+
const dest = join(deps.cwd, ".horsecode", "skills", name);
|
|
151
|
+
try {
|
|
152
|
+
await mkdir(dirname(dest), { recursive: true });
|
|
153
|
+
await cp(dirname(s.path), dest, { recursive: true, dereference: true });
|
|
154
|
+
copied.push(name);
|
|
155
|
+
result.skills++;
|
|
156
|
+
} catch {
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (copied.length) {
|
|
160
|
+
const removable = [];
|
|
161
|
+
for (const s of skills) {
|
|
162
|
+
const name = s.label.split("/").pop();
|
|
163
|
+
if (!copied.includes(name)) continue;
|
|
164
|
+
const dir = dirname(s.path);
|
|
165
|
+
const paths = [dir];
|
|
166
|
+
try {
|
|
167
|
+
if ((await lstat(dir)).isSymbolicLink()) paths.push(resolve(dirname(dir), await readlink(dir)));
|
|
168
|
+
} catch {
|
|
169
|
+
}
|
|
170
|
+
removable.push({ name, paths });
|
|
171
|
+
}
|
|
172
|
+
const answer2 = await deps.ask(
|
|
173
|
+
`**${removable.length} skill(s)** are now in \`.horsecode/skills/\` AND still at their old path.
|
|
174
|
+
|
|
175
|
+
Remove the originals? horse-code reads its own directory, so the old copies are twins that will drift \u2014 but OTHER tools in this project read them too.
|
|
176
|
+
|
|
177
|
+
_Removed from: ${[...new Set(removable.flatMap((r) => r.paths.map((p) => dirname(p).replace(deps.cwd, "."))))].join(", ")}_`,
|
|
178
|
+
{ options: [
|
|
179
|
+
{ label: "No \u2014 keep them", description: "Nothing is deleted; the two copies can diverge" },
|
|
180
|
+
{ label: "Yes \u2014 remove the originals", description: `${removable.length} directory(ies) deleted from their old location` }
|
|
181
|
+
] }
|
|
182
|
+
);
|
|
183
|
+
if (YES.test(answer2.trim())) {
|
|
184
|
+
for (const [i, r] of removable.entries()) {
|
|
185
|
+
deps.progress?.("remove", `\u{1F9F9} Removing **${i + 1}/${removable.length}** \u2014 \`${r.name}\``);
|
|
186
|
+
for (const path of r.paths) {
|
|
187
|
+
try {
|
|
188
|
+
await rm(path, { recursive: true, force: true });
|
|
189
|
+
result.removed++;
|
|
190
|
+
} catch {
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
deps.note(`\u{1F9F9} Removed ${result.removed} original skill location(s). _Both skill trees are usually tracked by git \u2014 \`git checkout\` brings them back if this was wrong._`);
|
|
195
|
+
} else result.declined.push("removing the originals");
|
|
196
|
+
}
|
|
197
|
+
if (copied.length && deps.assignSkills) {
|
|
198
|
+
deps.note(`Working out which roles should carry ${copied.length} newly installed skill(s)\u2026`);
|
|
199
|
+
deps.busy?.("assigning skills", deps.models[0]);
|
|
200
|
+
try {
|
|
201
|
+
const assigned = await deps.assignSkills(copied);
|
|
202
|
+
deps.idle?.();
|
|
203
|
+
deps.note(assigned || "No role needed one of them permanently \u2014 every agent can still fetch them on demand.");
|
|
204
|
+
} catch (e) {
|
|
205
|
+
deps.idle?.();
|
|
206
|
+
deps.note(`Skill assignment did not run (${e instanceof Error ? e.message : String(e)}) \u2014 run \`/roles adjust\` to do it.`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
} else result.declined.push("skills");
|
|
210
|
+
}
|
|
211
|
+
const mcp = await mcpServers(findings);
|
|
212
|
+
if (mcp.length) {
|
|
213
|
+
deps.note(`**${mcp.length} MCP server(s)** are configured for the other tool: ${mcp.map((m) => `\`${m}\``).join(", ")}.
|
|
214
|
+
|
|
215
|
+
_These are not imported automatically \u2014 add the ones you want under \`mcp\` in your config, and mark a read-only server \`"readOnly": true\` so every agent can use it._`);
|
|
216
|
+
}
|
|
217
|
+
const migratedRules = findings.filter((f) => f.kind === "rules" && !f.own && !f.label.startsWith("~/")).map((f) => f.label);
|
|
218
|
+
if (migratedRules.length) {
|
|
219
|
+
const already = (await loadMigrated(deps.cwd))?.files ?? [];
|
|
220
|
+
const unrecorded = migratedRules.filter((f) => !already.includes(f));
|
|
221
|
+
const stop = result.rules > 0 ? unrecorded.length > 0 : unrecorded.length > 0 && countKind(deps.memStore, "rule") > 0 && /^y/i.test(await deps.ask(
|
|
222
|
+
`${unrecorded.map((f) => `\`${f}\``).join(", ")} ${unrecorded.length === 1 ? "was" : "were"} left in place, and this project already has rules in memory \u2014 so ${unrecorded.length === 1 ? "it was" : "they were"} most likely migrated already. Stop reading ${unrecorded.length === 1 ? "it" : "them"} as this project's rules?`,
|
|
223
|
+
{ options: [
|
|
224
|
+
{ label: "Yes \u2014 the rules are in memory", description: "An agent that opens the file is told where the rules went" },
|
|
225
|
+
{ label: "No \u2014 keep reading them", description: "The file stays a source of rules alongside memory" }
|
|
226
|
+
] }
|
|
227
|
+
));
|
|
228
|
+
if (stop) {
|
|
229
|
+
await recordMigrated(deps.cwd, unrecorded).catch(() => {
|
|
230
|
+
});
|
|
231
|
+
deps.note(`\u{1F512} ${unrecorded.map((f) => `\`${f}\``).join(", ")} ${unrecorded.length === 1 ? "is" : "are"} no longer read as rules \u2014 their content is in this project's memory, and an agent that opens one is told where the rules went.`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return result;
|
|
235
|
+
}
|
|
236
|
+
async function mcpServers(findings) {
|
|
237
|
+
const names = /* @__PURE__ */ new Set();
|
|
238
|
+
for (const f of findings.filter((x) => x.kind === "mcp")) {
|
|
239
|
+
try {
|
|
240
|
+
const raw = f.text ?? await readFile(f.path, "utf8");
|
|
241
|
+
const parsed = JSON.parse(raw);
|
|
242
|
+
for (const key of Object.keys(parsed.mcpServers ?? parsed.servers ?? {})) names.add(key);
|
|
243
|
+
} catch {
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return [...names];
|
|
247
|
+
}
|
|
248
|
+
function describeResult(r) {
|
|
249
|
+
const bits = [];
|
|
250
|
+
if (r.rules) bits.push(`**${r.rules} rule(s)** \u2014 now in every agent's instructions`);
|
|
251
|
+
if (r.facts) bits.push(`**${r.facts} fact(s)** \u2014 recalled when relevant`);
|
|
252
|
+
if (r.skills) {
|
|
253
|
+
bits.push(`**${r.skills} skill(s)** \u2014 copied to \`.horsecode/skills/\`` + (r.removed ? `, and ${r.removed} old location(s) removed` : ""));
|
|
254
|
+
}
|
|
255
|
+
const failed = r.failedBatches ? `
|
|
256
|
+
|
|
257
|
+
\u26A0\uFE0F **${r.failedBatches} batch(es) failed to read** \u2014 the instructions and remembered facts in them were NOT imported. Re-run \`/migrate\` once that is cleared.` : "";
|
|
258
|
+
if (!bits.length) {
|
|
259
|
+
return `Nothing was imported.${r.declined.length ? ` You declined: ${r.declined.join(", ")}.` : ""}${failed}`;
|
|
260
|
+
}
|
|
261
|
+
const declined = r.declined.length ? `
|
|
262
|
+
|
|
263
|
+
_Declined: ${r.declined.join(", ")} \u2014 re-run \`/migrate\` to revisit._` : "";
|
|
264
|
+
const left = r.skipped ? `
|
|
265
|
+
|
|
266
|
+
${r.skipped} instruction(s) were left behind as tool-specific.` : "";
|
|
267
|
+
const head = r.failedBatches ? "**Migration finished with failures.**" : "**Migration complete.**";
|
|
268
|
+
return `${head}
|
|
269
|
+
${bits.map((b) => `- ${b}`).join("\n")}${left}${declined}${failed}`;
|
|
270
|
+
}
|
|
271
|
+
export {
|
|
272
|
+
describeResult,
|
|
273
|
+
runMigration
|
|
274
|
+
};
|
|
@@ -3,9 +3,9 @@ import {
|
|
|
3
3
|
saveMcpServer,
|
|
4
4
|
saveRoleSkills,
|
|
5
5
|
saveSkillSource
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-G45RWL7S.js";
|
|
6
|
+
} from "./chunk-JR2JLRE3.js";
|
|
8
7
|
import "./chunk-H2FDGPVW.js";
|
|
8
|
+
import "./chunk-G45RWL7S.js";
|
|
9
9
|
export {
|
|
10
10
|
removeSkillSource,
|
|
11
11
|
saveMcpServer,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
SpentSources,
|
|
2
3
|
TRACE_CONCURRENCY,
|
|
3
4
|
buildBrief,
|
|
4
5
|
coverageFor,
|
|
@@ -6,13 +7,16 @@ import {
|
|
|
6
7
|
planFor,
|
|
7
8
|
runTraces,
|
|
8
9
|
traceableFiles
|
|
9
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-YULQ4URQ.js";
|
|
11
|
+
import "./chunk-G45RWL7S.js";
|
|
10
12
|
import "./chunk-LPQU436C.js";
|
|
11
|
-
import "./chunk-
|
|
12
|
-
import "./chunk-
|
|
13
|
-
import "./chunk-
|
|
13
|
+
import "./chunk-6S4WWQMN.js";
|
|
14
|
+
import "./chunk-JLWQCA7B.js";
|
|
15
|
+
import "./chunk-ZPJP2VH5.js";
|
|
16
|
+
import "./chunk-4M6LXNG2.js";
|
|
14
17
|
import "./chunk-6W4UH2BQ.js";
|
|
15
18
|
export {
|
|
19
|
+
SpentSources,
|
|
16
20
|
TRACE_CONCURRENCY,
|
|
17
21
|
buildBrief,
|
|
18
22
|
coverageFor,
|
|
@@ -8,13 +8,14 @@ import {
|
|
|
8
8
|
describeSizeDoubt,
|
|
9
9
|
sizeRequest,
|
|
10
10
|
triageFinding
|
|
11
|
-
} from "./chunk-
|
|
12
|
-
import "./chunk-
|
|
11
|
+
} from "./chunk-372X5HHU.js";
|
|
12
|
+
import "./chunk-BFIZMM4G.js";
|
|
13
13
|
import "./chunk-63E73TGI.js";
|
|
14
|
-
import "./chunk-
|
|
15
|
-
import "./chunk-
|
|
16
|
-
import "./chunk-
|
|
17
|
-
import "./chunk-
|
|
14
|
+
import "./chunk-CYLPQWIF.js";
|
|
15
|
+
import "./chunk-6S4WWQMN.js";
|
|
16
|
+
import "./chunk-JLWQCA7B.js";
|
|
17
|
+
import "./chunk-ZPJP2VH5.js";
|
|
18
|
+
import "./chunk-4M6LXNG2.js";
|
|
18
19
|
import "./chunk-6W4UH2BQ.js";
|
|
19
20
|
export {
|
|
20
21
|
RequestSizeSchema,
|
|
@@ -3,14 +3,13 @@ import {
|
|
|
3
3
|
describeFix,
|
|
4
4
|
dirtyPaths,
|
|
5
5
|
runFix
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-3ACDNDCG.js";
|
|
7
7
|
import {
|
|
8
8
|
respondIn
|
|
9
9
|
} from "./chunk-M2RKCIGV.js";
|
|
10
10
|
import {
|
|
11
11
|
buildAskUserTool,
|
|
12
12
|
buildRememberTool,
|
|
13
|
-
buildSkillTool,
|
|
14
13
|
constitutionNote,
|
|
15
14
|
createWebFetchTool,
|
|
16
15
|
deferMcp,
|
|
@@ -22,21 +21,23 @@ import {
|
|
|
22
21
|
specsDir,
|
|
23
22
|
verifyPaths,
|
|
24
23
|
writeFileTool
|
|
25
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-27F44PBD.js";
|
|
26
25
|
import {
|
|
27
26
|
askInUserLanguage,
|
|
28
27
|
inUserLanguage
|
|
29
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-UTHLEW5V.js";
|
|
30
29
|
import "./chunk-ZSQ24YDJ.js";
|
|
30
|
+
import {
|
|
31
|
+
buildSkillTool
|
|
32
|
+
} from "./chunk-YULQ4URQ.js";
|
|
31
33
|
import "./chunk-G45RWL7S.js";
|
|
32
|
-
import "./chunk-MRZVA5JB.js";
|
|
33
34
|
import {
|
|
34
35
|
defaultGitRunner
|
|
35
36
|
} from "./chunk-LPQU436C.js";
|
|
36
37
|
import {
|
|
37
38
|
describeEscalation,
|
|
38
39
|
triageFinding
|
|
39
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-372X5HHU.js";
|
|
40
41
|
import {
|
|
41
42
|
BATCH_TOOLS_NOTE,
|
|
42
43
|
contextTools,
|
|
@@ -46,18 +47,20 @@ import {
|
|
|
46
47
|
projectToolsNote,
|
|
47
48
|
readFileTool,
|
|
48
49
|
reinforceUsed
|
|
49
|
-
} from "./chunk-
|
|
50
|
+
} from "./chunk-BFIZMM4G.js";
|
|
50
51
|
import "./chunk-63E73TGI.js";
|
|
51
52
|
import {
|
|
52
|
-
ToolRegistry
|
|
53
|
+
ToolRegistry
|
|
54
|
+
} from "./chunk-CYLPQWIF.js";
|
|
55
|
+
import "./chunk-6S4WWQMN.js";
|
|
56
|
+
import {
|
|
53
57
|
handedOver,
|
|
54
58
|
runToCompletion
|
|
55
|
-
} from "./chunk-
|
|
56
|
-
import "./chunk-
|
|
57
|
-
import "./chunk-KAGKX2YT.js";
|
|
59
|
+
} from "./chunk-JLWQCA7B.js";
|
|
60
|
+
import "./chunk-ZPJP2VH5.js";
|
|
58
61
|
import {
|
|
59
62
|
loadGraphSync
|
|
60
|
-
} from "./chunk-
|
|
63
|
+
} from "./chunk-4M6LXNG2.js";
|
|
61
64
|
import {
|
|
62
65
|
sessionBase
|
|
63
66
|
} from "./chunk-6W4UH2BQ.js";
|
package/package.json
CHANGED
package/dist/chunk-MRZVA5JB.js
DELETED
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
defaultGitRunner
|
|
3
|
-
} from "./chunk-LPQU436C.js";
|
|
4
|
-
import {
|
|
5
|
-
briefForPrompt,
|
|
6
|
-
briefPrompt,
|
|
7
|
-
briefStatus,
|
|
8
|
-
gatherBriefInput,
|
|
9
|
-
saveBrief
|
|
10
|
-
} from "./chunk-6OSEQOYY.js";
|
|
11
|
-
import {
|
|
12
|
-
ensureGitignore,
|
|
13
|
-
loadTraceIndex,
|
|
14
|
-
planTraces,
|
|
15
|
-
pruneTraces,
|
|
16
|
-
saveTrace,
|
|
17
|
-
saveTraceIndex,
|
|
18
|
-
traceCoverage,
|
|
19
|
-
tracePrompt,
|
|
20
|
-
traceRootRel,
|
|
21
|
-
traceable
|
|
22
|
-
} from "./chunk-KAGKX2YT.js";
|
|
23
|
-
import {
|
|
24
|
-
loadGraph
|
|
25
|
-
} from "./chunk-XEGQT5EN.js";
|
|
26
|
-
|
|
27
|
-
// src/engine/trace-run.ts
|
|
28
|
-
async function traceableFiles(cwd, opts) {
|
|
29
|
-
const r = await defaultGitRunner(["ls-files", "--cached", "--others", "--exclude-standard"], cwd);
|
|
30
|
-
return traceable(r.stdout.split("\n").filter(Boolean), opts);
|
|
31
|
-
}
|
|
32
|
-
async function coverageFor(cwd) {
|
|
33
|
-
return traceCoverage(cwd, await traceableFiles(cwd), await loadTraceIndex(cwd));
|
|
34
|
-
}
|
|
35
|
-
var TRACE_CONCURRENCY = 6;
|
|
36
|
-
var INDEX_CHECKPOINT = 25;
|
|
37
|
-
function describePlan(plan, model) {
|
|
38
|
-
if (!plan.jobs.length) {
|
|
39
|
-
return plan.upToDate ? `All ${plan.upToDate} traces are current \u2014 nothing to write, nothing to spend.` : "No files to trace.";
|
|
40
|
-
}
|
|
41
|
-
const kIn = Math.round(plan.estimatedInputTokens / 1e3);
|
|
42
|
-
const kOut = Math.round(plan.estimatedOutputTokens / 1e3);
|
|
43
|
-
const skipped = plan.skipped.length ? `
|
|
44
|
-
|
|
45
|
-
${plan.skipped.length} file(s) skipped as too large to trace economically (e.g. ${plan.skipped.slice(0, 3).map((s) => `\`${s.file}\``).join(", ")}${plan.skipped.length > 3 ? ", \u2026" : ""}).` : "";
|
|
46
|
-
const cached = plan.upToDate ? `
|
|
47
|
-
${plan.upToDate} file(s) already have a current trace and will be left alone.` : "";
|
|
48
|
-
return `**Tracing ${plan.jobs.length} file(s)** with \`${model}\`.
|
|
49
|
-
|
|
50
|
-
This is the part of understanding your project that costs tokens \u2014 the graph was free, this is not. Each file is read once and described in ~150 words.
|
|
51
|
-
|
|
52
|
-
Rough cost: **~${kIn}k input + ~${kOut}k output tokens**.${cached}${skipped}`;
|
|
53
|
-
}
|
|
54
|
-
async function traceOne(provider, model, job, signal, brief) {
|
|
55
|
-
const req = {
|
|
56
|
-
model,
|
|
57
|
-
messages: [
|
|
58
|
-
{ role: "system", content: "You write terse, factual reference notes about source files. You never speculate." },
|
|
59
|
-
{ role: "user", content: tracePrompt(job, brief) }
|
|
60
|
-
],
|
|
61
|
-
tools: []
|
|
62
|
-
};
|
|
63
|
-
let out = "";
|
|
64
|
-
for await (const ev of provider.chat(req, signal)) {
|
|
65
|
-
if (ev.type === "text-delta") out += ev.text;
|
|
66
|
-
else if (ev.type === "error") throw new Error(ev.message);
|
|
67
|
-
}
|
|
68
|
-
const body = out.replace(/<\/?think>/gi, "").trim();
|
|
69
|
-
if (!body) throw new Error("empty response");
|
|
70
|
-
return body;
|
|
71
|
-
}
|
|
72
|
-
async function runTraces(opts) {
|
|
73
|
-
const { cwd, plan } = opts;
|
|
74
|
-
const signal = opts.signal ?? new AbortController().signal;
|
|
75
|
-
const index = await loadTraceIndex(cwd);
|
|
76
|
-
const brief = briefForPrompt(cwd);
|
|
77
|
-
const failed = [];
|
|
78
|
-
let written = 0;
|
|
79
|
-
let done = 0;
|
|
80
|
-
const queue = [...plan.jobs];
|
|
81
|
-
const worker = async () => {
|
|
82
|
-
for (; ; ) {
|
|
83
|
-
const job = queue.shift();
|
|
84
|
-
if (!job || signal.aborted) return;
|
|
85
|
-
let wroteTo;
|
|
86
|
-
let words;
|
|
87
|
-
let error;
|
|
88
|
-
try {
|
|
89
|
-
const body = await traceOne(opts.provider, opts.model, job, signal, brief);
|
|
90
|
-
const rec = await saveTrace(cwd, job, body, opts.model);
|
|
91
|
-
index.traces[job.file] = rec;
|
|
92
|
-
wroteTo = `${traceRootRel()}/${job.file}.md`;
|
|
93
|
-
words = body.split(/\s+/).filter(Boolean).length;
|
|
94
|
-
written++;
|
|
95
|
-
if (written % INDEX_CHECKPOINT === 0) await saveTraceIndex(cwd, index);
|
|
96
|
-
} catch (e) {
|
|
97
|
-
if (signal.aborted) return;
|
|
98
|
-
error = e instanceof Error ? e.message : String(e);
|
|
99
|
-
failed.push({ file: job.file, error });
|
|
100
|
-
}
|
|
101
|
-
opts.onProgress?.({
|
|
102
|
-
done: ++done,
|
|
103
|
-
total: plan.jobs.length,
|
|
104
|
-
file: job.file,
|
|
105
|
-
...wroteTo !== void 0 && { wroteTo },
|
|
106
|
-
...words !== void 0 && { words },
|
|
107
|
-
...error !== void 0 && { error }
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
};
|
|
111
|
-
await Promise.all(Array.from({ length: Math.min(TRACE_CONCURRENCY, queue.length) }, worker));
|
|
112
|
-
const pruned = opts.liveFiles ? await pruneTraces(cwd, opts.liveFiles, index) : [];
|
|
113
|
-
await saveTraceIndex(cwd, index);
|
|
114
|
-
const wroteGitignore = written > 0 && await ensureGitignore(cwd);
|
|
115
|
-
return { written, failed, pruned, upToDate: plan.upToDate, cancelled: signal.aborted, wroteGitignore };
|
|
116
|
-
}
|
|
117
|
-
async function planFor(cwd, files) {
|
|
118
|
-
return planTraces(cwd, files, await loadGraph(cwd), await loadTraceIndex(cwd));
|
|
119
|
-
}
|
|
120
|
-
async function buildBrief(opts) {
|
|
121
|
-
if (!opts.force) {
|
|
122
|
-
const st = await briefStatus(opts.cwd, opts.files);
|
|
123
|
-
if (st.built && !st.stale) {
|
|
124
|
-
return { ok: true, skipped: true, message: `Project brief is current (${st.sources.length} document(s)) \u2014 not rewritten.` };
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
const input = await gatherBriefInput(opts.cwd, opts.files);
|
|
128
|
-
if (!input) {
|
|
129
|
-
return { ok: false, message: "No documentation found (README, docs/, specs/) \u2014 traces will describe the code without product context." };
|
|
130
|
-
}
|
|
131
|
-
const signal = opts.signal ?? new AbortController().signal;
|
|
132
|
-
const req = {
|
|
133
|
-
model: opts.model,
|
|
134
|
-
messages: [
|
|
135
|
-
{ role: "system", content: "You write factual project briefings from documentation. You never invent facts the documents do not state." },
|
|
136
|
-
{ role: "user", content: briefPrompt(input) }
|
|
137
|
-
],
|
|
138
|
-
tools: []
|
|
139
|
-
};
|
|
140
|
-
let out = "";
|
|
141
|
-
try {
|
|
142
|
-
for await (const ev of opts.provider.chat(req, signal)) {
|
|
143
|
-
if (ev.type === "text-delta") out += ev.text;
|
|
144
|
-
else if (ev.type === "error") throw new Error(ev.message);
|
|
145
|
-
}
|
|
146
|
-
} catch (e) {
|
|
147
|
-
return { ok: false, message: `Project brief failed (${e instanceof Error ? e.message : String(e)}) \u2014 tracing can still run without it.` };
|
|
148
|
-
}
|
|
149
|
-
const body = out.replace(/<\/?think>/gi, "").trim();
|
|
150
|
-
if (!body) return { ok: false, message: "The brief came back empty \u2014 tracing can still run without it." };
|
|
151
|
-
await saveBrief(opts.cwd, body, { hash: input.hash, sources: input.sources.map((s) => s.file), writtenAt: Date.now(), model: opts.model });
|
|
152
|
-
return { ok: true, message: `**Project brief** written from ${input.sources.length} document(s): ${input.sources.slice(0, 6).map((s) => `\`${s.file}\``).join(", ")}` };
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
export {
|
|
156
|
-
traceableFiles,
|
|
157
|
-
coverageFor,
|
|
158
|
-
TRACE_CONCURRENCY,
|
|
159
|
-
describePlan,
|
|
160
|
-
runTraces,
|
|
161
|
-
planFor,
|
|
162
|
-
buildBrief
|
|
163
|
-
};
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
CLI_KINDS
|
|
3
|
-
} from "./chunk-G45RWL7S.js";
|
|
4
1
|
import {
|
|
5
2
|
arrayField,
|
|
6
3
|
objectField,
|
|
7
4
|
patchConfig
|
|
8
5
|
} from "./chunk-H2FDGPVW.js";
|
|
6
|
+
import {
|
|
7
|
+
CLI_KINDS
|
|
8
|
+
} from "./chunk-G45RWL7S.js";
|
|
9
9
|
|
|
10
10
|
// src/config/config.ts
|
|
11
11
|
import { z } from "zod";
|