@hizliemre/horse-code 0.3.0 → 0.4.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.
- package/dist/{app-5FXHE7GX.js → app-KR7TAHUD.js} +82 -40
- package/dist/{chunk-LNW557IO.js → chunk-372X5HHU.js} +2 -2
- package/dist/{chunk-XEGQT5EN.js → chunk-4M6LXNG2.js} +1 -1
- package/dist/{chunk-6OSEQOYY.js → chunk-6S4WWQMN.js} +2 -2
- package/dist/chunk-ACTVFJRW.js +989 -0
- 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/{chunk-XYZVZPAY.js → chunk-KXBYRU4W.js} +66 -31
- package/dist/chunk-MZM24M5M.js +251 -0
- package/dist/chunk-NBTH2VVI.js +1945 -0
- package/dist/chunk-PIG54WFU.js +2873 -0
- package/dist/{run-P6ZYL5JL.js → chunk-QJYVZPLG.js} +133 -389
- package/dist/{chunk-YPZP7LYL.js → chunk-UANNVVIU.js} +1 -1
- package/dist/{chunk-UGESK765.js → chunk-UTHLEW5V.js} +1 -1
- package/dist/{chunk-KAGKX2YT.js → chunk-ZPJP2VH5.js} +10 -1
- package/dist/cli.js +262 -69
- package/dist/{fix-ONLA45HD.js → fix-SSDUVV4T.js} +11 -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/research-MHBNZ6SA.js +111 -0
- 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-OBOD552Q.js} +9 -4
- package/dist/{triage-FCYHD2AQ.js → triage-IFCVL5MA.js} +7 -6
- package/dist/{verify-LC57A6H2.js → verify-2DTBG6RG.js} +26 -20
- package/package.json +1 -1
- package/dist/chunk-MRZVA5JB.js +0 -163
- package/dist/chunk-UEWVVN5L.js +0 -5691
- package/dist/{chunk-EAF22QIG.js → chunk-JR2JLRE3.js} +3 -3
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createWebFetchTool,
|
|
3
|
+
readOnlyRegistry
|
|
4
|
+
} from "./chunk-NBTH2VVI.js";
|
|
5
|
+
import "./chunk-ZSQ24YDJ.js";
|
|
6
|
+
import "./chunk-MZM24M5M.js";
|
|
7
|
+
import {
|
|
8
|
+
defaultGitRunner
|
|
9
|
+
} from "./chunk-LPQU436C.js";
|
|
10
|
+
import "./chunk-BFIZMM4G.js";
|
|
11
|
+
import "./chunk-CYLPQWIF.js";
|
|
12
|
+
import "./chunk-63E73TGI.js";
|
|
13
|
+
import "./chunk-6S4WWQMN.js";
|
|
14
|
+
import {
|
|
15
|
+
runToCompletion
|
|
16
|
+
} from "./chunk-JLWQCA7B.js";
|
|
17
|
+
import "./chunk-ZPJP2VH5.js";
|
|
18
|
+
import "./chunk-4M6LXNG2.js";
|
|
19
|
+
import "./chunk-6W4UH2BQ.js";
|
|
20
|
+
|
|
21
|
+
// src/engine/research.ts
|
|
22
|
+
import { mkdir, writeFile } from "fs/promises";
|
|
23
|
+
import { dirname, join, relative } from "path";
|
|
24
|
+
import { existsSync } from "fs";
|
|
25
|
+
var RESEARCH_MAX_TURNS = 120;
|
|
26
|
+
function researchDir(trackedFiles2) {
|
|
27
|
+
const known = [/^docs\/research\//i, /^docs\/adr\//i, /^docs\/decisions\//i, /^research\//i, /^\.planning\//i];
|
|
28
|
+
for (const re of known) {
|
|
29
|
+
const hit = trackedFiles2.find((f) => re.test(f.replace(/\\/g, "/")));
|
|
30
|
+
if (hit) return dirname(hit.replace(/\\/g, "/"));
|
|
31
|
+
}
|
|
32
|
+
return "docs/research";
|
|
33
|
+
}
|
|
34
|
+
function reportName(title, now = /* @__PURE__ */ new Date()) {
|
|
35
|
+
const slug = (title || "research").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 60);
|
|
36
|
+
const day = now.toISOString().slice(0, 10);
|
|
37
|
+
return `${day}-${slug || "research"}.md`;
|
|
38
|
+
}
|
|
39
|
+
var researchRule = "You are researching a question and writing ONE document. You are not changing this project: you have no tool that writes a file, and the report is your final answer \u2014 horse-code writes it down.\n\nGround the answer in THIS repository before anything else. Read the code that the question is about; a recommendation that ignores what is already here is a recommendation for a different project.\n\nThe document must contain, in this order:\n1. The question, restated in one or two sentences as you understood it.\n2. What this project does today in the area concerned, with file paths \u2014 the starting position.\n3. The options, one section each. For every option: how it would work here, what it costs, and what it rules out. An option nobody would pick is still worth a paragraph saying why not.\n4. A comparison table across the dimensions that actually decide this one. Choose the dimensions from the question rather than from habit.\n5. A recommendation, with the condition that would change it. A recommendation that nothing could overturn is a preference wearing a suit.\n6. What you could not establish, and what it would take to settle. Say it plainly \u2014 an unexamined assumption presented as a finding is the one failure this document cannot recover from.\n\nWrite markdown. Cite file paths for claims about this project and URLs for claims about the world. Where you are reasoning from your own knowledge rather than from something you read, say so.";
|
|
40
|
+
async function runResearch(opts) {
|
|
41
|
+
const { deps, workdir } = opts;
|
|
42
|
+
const dir = researchDir(opts.trackedFiles ?? await trackedFiles(workdir));
|
|
43
|
+
const rel = join(dir, reportName(opts.title));
|
|
44
|
+
const tools = readOnlyRegistry(deps, { remember: true });
|
|
45
|
+
tools.register(createWebFetchTool());
|
|
46
|
+
const language = opts.language ? `
|
|
47
|
+
|
|
48
|
+
Write the document in ${opts.language}. Keep file paths, identifiers and URLs exactly as they are.` : "";
|
|
49
|
+
const rules = deps.roleRegistry.ruleSuffix();
|
|
50
|
+
const last = await runToCompletion({
|
|
51
|
+
...deps.roleRegistry.fallbackOpts("analyst"),
|
|
52
|
+
systemPrompt: researchRule + language + rules,
|
|
53
|
+
messages: [{ role: "user", content: opts.prompt }],
|
|
54
|
+
tools,
|
|
55
|
+
maxTurns: RESEARCH_MAX_TURNS,
|
|
56
|
+
provider: deps.provider,
|
|
57
|
+
permission: deps.permission,
|
|
58
|
+
approve: deps.approve,
|
|
59
|
+
cwd: workdir,
|
|
60
|
+
signal: deps.signal,
|
|
61
|
+
onActivity: deps.onActivity,
|
|
62
|
+
onLiveActivity: deps.onLiveActivity,
|
|
63
|
+
inbox: deps.inbox,
|
|
64
|
+
...opts.note ? { onSay: opts.note } : {}
|
|
65
|
+
});
|
|
66
|
+
const body = (last.content ?? "").trim();
|
|
67
|
+
if (!body) return { reportPath: rel, written: false, committed: false };
|
|
68
|
+
const abs = join(workdir, rel);
|
|
69
|
+
await mkdir(dirname(abs), { recursive: true });
|
|
70
|
+
await writeFile(abs, `${body}
|
|
71
|
+
`, "utf8");
|
|
72
|
+
return { reportPath: rel, written: true, committed: await commitReport(workdir, rel, opts.title) };
|
|
73
|
+
}
|
|
74
|
+
async function trackedFiles(cwd) {
|
|
75
|
+
try {
|
|
76
|
+
const r = await defaultGitRunner(["ls-files"], cwd);
|
|
77
|
+
return r.code === 0 ? r.stdout.split("\n").filter(Boolean) : [];
|
|
78
|
+
} catch {
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
async function commitReport(workdir, rel, title) {
|
|
83
|
+
if (!existsSync(join(workdir, ".git")) && !existsSync(join(workdir, rel))) return false;
|
|
84
|
+
const add = await defaultGitRunner(["add", "--", rel], workdir);
|
|
85
|
+
if (add.code !== 0) return false;
|
|
86
|
+
const staged = await defaultGitRunner(["diff", "--cached", "--quiet", "--", rel], workdir);
|
|
87
|
+
if (staged.code === 0) return false;
|
|
88
|
+
const r = await defaultGitRunner(["commit", "-m", `docs(research): ${title || "findings"}`, "--", rel], workdir);
|
|
89
|
+
return r.code === 0;
|
|
90
|
+
}
|
|
91
|
+
function describeResearch(r, branch) {
|
|
92
|
+
if (!r.written) return "The research produced no document \u2014 nothing was written.";
|
|
93
|
+
const where = r.committed ? `Committed on \`${branch}\`.` : "Written, but not committed \u2014 it is in the working tree.";
|
|
94
|
+
return `\u{1F4C4} **Research report:** \`${r.reportPath}\`
|
|
95
|
+
${where}
|
|
96
|
+
|
|
97
|
+
_No code was changed: the lane runs without a write tool at all._`;
|
|
98
|
+
}
|
|
99
|
+
function plannedPath(title, trackedFiles2) {
|
|
100
|
+
return relative(".", join(researchDir(trackedFiles2), reportName(title))).replace(/\\/g, "/");
|
|
101
|
+
}
|
|
102
|
+
export {
|
|
103
|
+
RESEARCH_MAX_TURNS,
|
|
104
|
+
commitReport,
|
|
105
|
+
describeResearch,
|
|
106
|
+
plannedPath,
|
|
107
|
+
reportName,
|
|
108
|
+
researchDir,
|
|
109
|
+
researchRule,
|
|
110
|
+
runResearch
|
|
111
|
+
};
|
|
@@ -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,17 @@ import {
|
|
|
6
7
|
planFor,
|
|
7
8
|
runTraces,
|
|
8
9
|
traceableFiles
|
|
9
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-ACTVFJRW.js";
|
|
11
|
+
import "./chunk-MZM24M5M.js";
|
|
12
|
+
import "./chunk-G45RWL7S.js";
|
|
10
13
|
import "./chunk-LPQU436C.js";
|
|
11
|
-
import "./chunk-
|
|
12
|
-
import "./chunk-
|
|
13
|
-
import "./chunk-
|
|
14
|
+
import "./chunk-6S4WWQMN.js";
|
|
15
|
+
import "./chunk-JLWQCA7B.js";
|
|
16
|
+
import "./chunk-ZPJP2VH5.js";
|
|
17
|
+
import "./chunk-4M6LXNG2.js";
|
|
14
18
|
import "./chunk-6W4UH2BQ.js";
|
|
15
19
|
export {
|
|
20
|
+
SpentSources,
|
|
16
21
|
TRACE_CONCURRENCY,
|
|
17
22
|
buildBrief,
|
|
18
23
|
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
|
+
import "./chunk-CYLPQWIF.js";
|
|
13
14
|
import "./chunk-63E73TGI.js";
|
|
14
|
-
import "./chunk-
|
|
15
|
-
import "./chunk-
|
|
16
|
-
import "./chunk-
|
|
17
|
-
import "./chunk-
|
|
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,40 +3,44 @@ import {
|
|
|
3
3
|
describeFix,
|
|
4
4
|
dirtyPaths,
|
|
5
5
|
runFix
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-UANNVVIU.js";
|
|
7
7
|
import {
|
|
8
8
|
respondIn
|
|
9
9
|
} from "./chunk-M2RKCIGV.js";
|
|
10
|
+
import {
|
|
11
|
+
askInUserLanguage,
|
|
12
|
+
inUserLanguage
|
|
13
|
+
} from "./chunk-UTHLEW5V.js";
|
|
10
14
|
import {
|
|
11
15
|
buildAskUserTool,
|
|
16
|
+
editFileTool,
|
|
17
|
+
refreshAfterChange,
|
|
18
|
+
shellTool,
|
|
19
|
+
writeFileTool
|
|
20
|
+
} from "./chunk-PIG54WFU.js";
|
|
21
|
+
import {
|
|
12
22
|
buildRememberTool,
|
|
13
|
-
buildSkillTool,
|
|
14
23
|
constitutionNote,
|
|
15
24
|
createWebFetchTool,
|
|
16
25
|
deferMcp,
|
|
17
|
-
editFileTool,
|
|
18
26
|
featureSlugFor,
|
|
19
27
|
gitTool,
|
|
20
|
-
refreshAfterChange,
|
|
21
|
-
shellTool,
|
|
22
28
|
specsDir,
|
|
23
|
-
verifyPaths
|
|
24
|
-
|
|
25
|
-
} from "./chunk-UEWVVN5L.js";
|
|
26
|
-
import {
|
|
27
|
-
askInUserLanguage,
|
|
28
|
-
inUserLanguage
|
|
29
|
-
} from "./chunk-UGESK765.js";
|
|
29
|
+
verifyPaths
|
|
30
|
+
} from "./chunk-NBTH2VVI.js";
|
|
30
31
|
import "./chunk-ZSQ24YDJ.js";
|
|
32
|
+
import "./chunk-ACTVFJRW.js";
|
|
33
|
+
import {
|
|
34
|
+
buildSkillTool
|
|
35
|
+
} from "./chunk-MZM24M5M.js";
|
|
31
36
|
import "./chunk-G45RWL7S.js";
|
|
32
|
-
import "./chunk-MRZVA5JB.js";
|
|
33
37
|
import {
|
|
34
38
|
defaultGitRunner
|
|
35
39
|
} from "./chunk-LPQU436C.js";
|
|
36
40
|
import {
|
|
37
41
|
describeEscalation,
|
|
38
42
|
triageFinding
|
|
39
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-372X5HHU.js";
|
|
40
44
|
import {
|
|
41
45
|
BATCH_TOOLS_NOTE,
|
|
42
46
|
contextTools,
|
|
@@ -46,18 +50,20 @@ import {
|
|
|
46
50
|
projectToolsNote,
|
|
47
51
|
readFileTool,
|
|
48
52
|
reinforceUsed
|
|
49
|
-
} from "./chunk-
|
|
53
|
+
} from "./chunk-BFIZMM4G.js";
|
|
54
|
+
import {
|
|
55
|
+
ToolRegistry
|
|
56
|
+
} from "./chunk-CYLPQWIF.js";
|
|
50
57
|
import "./chunk-63E73TGI.js";
|
|
58
|
+
import "./chunk-6S4WWQMN.js";
|
|
51
59
|
import {
|
|
52
|
-
ToolRegistry,
|
|
53
60
|
handedOver,
|
|
54
61
|
runToCompletion
|
|
55
|
-
} from "./chunk-
|
|
56
|
-
import "./chunk-
|
|
57
|
-
import "./chunk-KAGKX2YT.js";
|
|
62
|
+
} from "./chunk-JLWQCA7B.js";
|
|
63
|
+
import "./chunk-ZPJP2VH5.js";
|
|
58
64
|
import {
|
|
59
65
|
loadGraphSync
|
|
60
|
-
} from "./chunk-
|
|
66
|
+
} from "./chunk-4M6LXNG2.js";
|
|
61
67
|
import {
|
|
62
68
|
sessionBase
|
|
63
69
|
} from "./chunk-6W4UH2BQ.js";
|
package/package.json
CHANGED