@hizliemre/horse-code 0.2.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.
Files changed (29) hide show
  1. package/dist/{app-2GPGCDX6.js → app-4WN37LZ3.js} +282 -57
  2. package/dist/{chunk-7JMWPTJ5.js → chunk-27F44PBD.js} +221 -1363
  3. package/dist/{chunk-23CLQ2KO.js → chunk-2WXG35EM.js} +19 -15
  4. package/dist/{chunk-LNW557IO.js → chunk-372X5HHU.js} +2 -2
  5. package/dist/{chunk-5ZV42XGJ.js → chunk-3ACDNDCG.js} +1 -1
  6. package/dist/{chunk-XEGQT5EN.js → chunk-4M6LXNG2.js} +1 -1
  7. package/dist/{chunk-6OSEQOYY.js → chunk-6S4WWQMN.js} +2 -2
  8. package/dist/{chunk-LLL7QWXB.js → chunk-BFIZMM4G.js} +6 -6
  9. package/dist/chunk-CYLPQWIF.js +214 -0
  10. package/dist/chunk-G45RWL7S.js +289 -0
  11. package/dist/{chunk-AE36LLL2.js → chunk-JLWQCA7B.js} +2 -209
  12. package/dist/{chunk-KKWZBZYK.js → chunk-JR2JLRE3.js} +26 -4
  13. package/dist/{run-P6ZYL5JL.js → chunk-QJYVZPLG.js} +133 -389
  14. package/dist/{chunk-UGESK765.js → chunk-UTHLEW5V.js} +1 -1
  15. package/dist/chunk-YULQ4URQ.js +1220 -0
  16. package/dist/{chunk-KAGKX2YT.js → chunk-ZPJP2VH5.js} +10 -1
  17. package/dist/cli.js +586 -104
  18. package/dist/{fix-JOIXQFVP.js → fix-QCL5AITT.js} +10 -8
  19. package/dist/{ongoing-WHYXPW24.js → ongoing-6NUSPSCV.js} +3 -2
  20. package/dist/{project-graph-5HNPRFQG.js → project-graph-OGIM2B33.js} +1 -1
  21. package/dist/run-V5ZLZ3LS.js +274 -0
  22. package/dist/{save-skills-X7U3KCPU.js → save-skills-NPKTYNAF.js} +2 -1
  23. package/dist/{trace-X6TU3AG6.js → trace-UVMZZRA5.js} +1 -1
  24. package/dist/{trace-adopt-URECQWJV.js → trace-adopt-7HWELJFE.js} +1 -1
  25. package/dist/{trace-run-7U4WJZ3V.js → trace-run-CZWEZ4R6.js} +8 -4
  26. package/dist/{triage-FCYHD2AQ.js → triage-IFCVL5MA.js} +7 -6
  27. package/dist/{verify-6SC4I77M.js → verify-HWZBTK5X.js} +16 -12
  28. package/package.json +1 -1
  29. package/dist/chunk-MRZVA5JB.js +0 -163
@@ -10,46 +10,51 @@ import {
10
10
  restoreTerminal,
11
11
  runJob,
12
12
  sttySane
13
- } from "./chunk-23CLQ2KO.js";
13
+ } from "./chunk-2WXG35EM.js";
14
14
  import "./chunk-M2RKCIGV.js";
15
15
  import {
16
- DURABLE_ROLES,
17
- REQUIRED_ROLES,
18
- ROLE_PROFILES,
19
16
  RoleFitness,
20
- adjustRoleModels,
21
17
  asChoice,
22
- capabilityScore,
23
18
  describeUnfinished,
24
- effortFor,
25
- filterModelsForRole,
26
- isKnownModel,
27
- modelBand,
28
- mostCapable,
29
- newestPrimary,
30
- sourceOf,
31
- strongestPrimary,
32
19
  toSlug,
33
20
  unfinishedSessions
34
- } from "./chunk-7JMWPTJ5.js";
21
+ } from "./chunk-27F44PBD.js";
35
22
  import "./chunk-QF4MP6BS.js";
36
- import "./chunk-UGESK765.js";
23
+ import "./chunk-UTHLEW5V.js";
37
24
  import "./chunk-ZSQ24YDJ.js";
38
25
  import {
39
26
  saveRoleSkills
40
- } from "./chunk-KKWZBZYK.js";
27
+ } from "./chunk-JR2JLRE3.js";
41
28
  import {
42
29
  objectField,
43
30
  patchConfig
44
31
  } from "./chunk-H2FDGPVW.js";
45
- import "./chunk-MRZVA5JB.js";
32
+ import {
33
+ DURABLE_ROLES,
34
+ REQUIRED_ROLES,
35
+ ROLE_PROFILES,
36
+ adjustRoleModels,
37
+ capabilityScore,
38
+ cliFor,
39
+ effortFor,
40
+ filterModelsForRole,
41
+ isKnownModel,
42
+ modelBand,
43
+ mostCapable,
44
+ newestPrimary,
45
+ sourceOf,
46
+ strongestPrimary
47
+ } from "./chunk-YULQ4URQ.js";
48
+ import "./chunk-G45RWL7S.js";
46
49
  import "./chunk-LPQU436C.js";
47
50
  import {
48
51
  memoryNote,
49
52
  memoryState,
50
53
  walkFiles
51
- } from "./chunk-LLL7QWXB.js";
54
+ } from "./chunk-BFIZMM4G.js";
52
55
  import "./chunk-63E73TGI.js";
56
+ import "./chunk-CYLPQWIF.js";
57
+ import "./chunk-6S4WWQMN.js";
53
58
  import {
54
59
  estimateFreezeSeconds,
55
60
  fmtDuration,
@@ -59,15 +64,14 @@ import {
59
64
  stripThinking,
60
65
  telemetry,
61
66
  writeHeapSnapshot
62
- } from "./chunk-AE36LLL2.js";
63
- import "./chunk-6OSEQOYY.js";
67
+ } from "./chunk-JLWQCA7B.js";
64
68
  import {
65
69
  traceRootRel
66
- } from "./chunk-KAGKX2YT.js";
70
+ } from "./chunk-ZPJP2VH5.js";
67
71
  import {
68
72
  buildProjectGraph,
69
73
  graphStatus
70
- } from "./chunk-XEGQT5EN.js";
74
+ } from "./chunk-4M6LXNG2.js";
71
75
  import {
72
76
  writeAtomic
73
77
  } from "./chunk-6W4UH2BQ.js";
@@ -356,7 +360,7 @@ async function connectAllMcp(specs) {
356
360
 
357
361
  // src/tui/app.tsx
358
362
  import { homedir as homedir2 } from "os";
359
- import { join as join6 } from "path";
363
+ import { join as join7 } from "path";
360
364
  import { basename } from "path";
361
365
 
362
366
  // src/tui/labels.ts
@@ -1737,6 +1741,8 @@ var COMMANDS = [
1737
1741
  { name: "/memories", desc: "List remembered facts (/forget N to remove)" },
1738
1742
  { name: "/forget", desc: "Forget a remembered fact (/forget N)" },
1739
1743
  { name: "/mcp", desc: "Connected MCP servers (/mcp add <url|command> installs one and verifies it)" },
1744
+ { name: "/init", desc: "Set this project up for horse-code \u2014 commit the knowledge, keep the derived files out" },
1745
+ { name: "/models", desc: "Models each connected subscription serves, and which ones a role is running" },
1740
1746
  { name: "/sources", desc: "Show your connected model sources (/sources refresh re-detects)" },
1741
1747
  { name: "/migrate", desc: "Bring a project from Claude Code / Codex / Cursor into horse-code (rules, memory, skills)" },
1742
1748
  { name: "/continue-from-claude", desc: "Continue work started in a Claude Code worktree (/continue-from-claude <name>) \u2014 its branch becomes the base" },
@@ -1748,6 +1754,7 @@ var COMMANDS = [
1748
1754
  { name: "/monitor", desc: "Where the run's time is going (/monitor enable shows the panel, disable hides it, log shows the file, heap writes a snapshot)" },
1749
1755
  { name: "/watch", desc: "Watch any command \u2014 each line it prints becomes an event (/watch <cmd>, /watch stop N)" },
1750
1756
  { name: "/paste", desc: "Put the clipboard's image into the input (same as Ctrl+V, for terminals that swallow it)" },
1757
+ { name: "/start-smoke-test", desc: "Walk the finished feature's manual verification one step at a time, and record what you saw into its guide" },
1751
1758
  { name: "/help", desc: "List the available commands" },
1752
1759
  { name: "/clear", desc: "Clear the conversation" },
1753
1760
  { name: "/exit", desc: "Quit horse-code" }
@@ -1957,7 +1964,7 @@ function rankFiles(files, query, limit = 8) {
1957
1964
  }
1958
1965
 
1959
1966
  // src/tui/components.tsx
1960
- import { join as join2 } from "path";
1967
+ import { join as join3 } from "path";
1961
1968
  import { homedir } from "os";
1962
1969
 
1963
1970
  // src/tui/paste.ts
@@ -2288,7 +2295,130 @@ function shortName(command) {
2288
2295
  return first.split("/").pop()?.slice(0, 24) || "watch";
2289
2296
  }
2290
2297
 
2298
+ // src/engine/smoke-test.ts
2299
+ import { existsSync, readFileSync as readFileSync2, readdirSync } from "fs";
2300
+ import { join as join2 } from "path";
2301
+ function splitInstructions(paragraph) {
2302
+ return paragraph.split(/(?<=[.!?])\s+/).map((s) => s.trim()).filter((s) => s.length > 12);
2303
+ }
2304
+ function parseGuide(path, markdown) {
2305
+ const lines = markdown.split("\n");
2306
+ const title = lines.find((l) => l.startsWith("# "))?.slice(2).trim() ?? "Manual verification";
2307
+ const preconditions = [];
2308
+ const scenarios = [];
2309
+ const steps = [];
2310
+ let section = "other";
2311
+ let scenario = "";
2312
+ let buffer = [];
2313
+ const flush = () => {
2314
+ if (!scenario) {
2315
+ buffer = [];
2316
+ return;
2317
+ }
2318
+ const text = buffer.join(" ").trim();
2319
+ for (const instruction of splitInstructions(text)) {
2320
+ steps.push({ n: steps.length + 1, scenario, scenarioIndex: Math.max(0, scenarios.length - 1), instruction });
2321
+ }
2322
+ buffer = [];
2323
+ };
2324
+ for (const line of lines) {
2325
+ const h2 = /^##\s+(?!#)(.*)$/.exec(line);
2326
+ const h3 = /^###\s+(.*)$/.exec(line);
2327
+ if (h2) {
2328
+ flush();
2329
+ scenario = "";
2330
+ section = /ön koşul|precondition/i.test(h2[1]) ? "pre" : /senaryo|scenario/i.test(h2[1]) ? "scenario" : "other";
2331
+ continue;
2332
+ }
2333
+ if (h3) {
2334
+ flush();
2335
+ if (section === "scenario") {
2336
+ scenario = h3[1].trim();
2337
+ scenarios.push(scenario);
2338
+ }
2339
+ continue;
2340
+ }
2341
+ const text = line.trim();
2342
+ if (!text) {
2343
+ flush();
2344
+ continue;
2345
+ }
2346
+ if (section === "pre") {
2347
+ const item = /^\d+\.\s+(.*)$/.exec(text)?.[1] ?? /^[-*]\s+(.*)$/.exec(text)?.[1];
2348
+ if (item) preconditions.push(item);
2349
+ continue;
2350
+ }
2351
+ if (section === "scenario" && scenario) buffer.push(text);
2352
+ }
2353
+ flush();
2354
+ return { path, title, preconditions, steps, scenarios };
2355
+ }
2356
+ function renderStep(step, total) {
2357
+ return `**Ad\u0131m ${step.n}/${total}** \u2014 _${step.scenario}_
2358
+
2359
+ ${step.instruction}`;
2360
+ }
2361
+ function recordOutcome(markdown, scenarioIndex, status, evidence) {
2362
+ const want = scenarioIndex + 1;
2363
+ return markdown.split("\n").map((line) => {
2364
+ if (!line.startsWith("|")) return line;
2365
+ const cells = line.split("|");
2366
+ if (cells.length < 4) return line;
2367
+ if (Number(/^\s*(\d+)/.exec(cells[1] ?? "")?.[1]) !== want) return line;
2368
+ cells[2] = ` ${status} `;
2369
+ cells[3] = ` ${evidence.replace(/\|/g, "\\|").replace(/\s+/g, " ").trim()} `;
2370
+ return cells.join("|");
2371
+ }).join("\n");
2372
+ }
2373
+ function scenarioOutcome(results) {
2374
+ const status = results.some((r) => r.status === "ba\u015Far\u0131s\u0131z") ? "ba\u015Far\u0131s\u0131z" : results.every((r) => r.status === "\xE7al\u0131\u015Ft\u0131r\u0131lmad\u0131") ? "\xE7al\u0131\u015Ft\u0131r\u0131lmad\u0131" : "ge\xE7ti";
2375
+ const evidence = results.filter((r) => r.evidence.trim()).map((r) => `${r.step.n}. ${r.evidence.trim()}`).join(" ");
2376
+ return { status, evidence: evidence || "g\xF6zlem kaydedilmedi" };
2377
+ }
2378
+ function findGuide(projectRoot) {
2379
+ const roots = [projectRoot, ...worktreeRoots(projectRoot)];
2380
+ let best;
2381
+ for (const root of roots) {
2382
+ let names = [];
2383
+ try {
2384
+ names = readdirSync(join2(root, "specs"));
2385
+ } catch {
2386
+ continue;
2387
+ }
2388
+ for (const slug of names) {
2389
+ if (!/^\d{3}-/.test(slug)) continue;
2390
+ const path = join2(root, "specs", slug, "quickstart.md");
2391
+ if (!existsSync(path)) continue;
2392
+ if (!best || slug > best.slug) best = { root, slug, path };
2393
+ }
2394
+ }
2395
+ return best;
2396
+ }
2397
+ function worktreeRoots(projectRoot) {
2398
+ const dir = join2(projectRoot, ".horsecode", "worktrees");
2399
+ try {
2400
+ return readdirSync(dir).map((s) => join2(dir, s, "base")).filter((p) => existsSync(p));
2401
+ } catch {
2402
+ return [];
2403
+ }
2404
+ }
2405
+ function progressLine(done, total) {
2406
+ const width = 24;
2407
+ const filled = total ? Math.round(done / total * width) : 0;
2408
+ return `${"\u2588".repeat(filled)}${"\u2591".repeat(width - filled)} ${done}/${total}`;
2409
+ }
2410
+ function loadGuide(projectRoot) {
2411
+ const found = findGuide(projectRoot);
2412
+ if (!found) {
2413
+ return { error: "No `specs/NNN-\u2026/quickstart.md` in this project or any of its session worktrees \u2014 a feature run writes one when it finishes." };
2414
+ }
2415
+ const guide = parseGuide(found.path, readFileSync2(found.path, "utf8"));
2416
+ if (!guide.steps.length) return { error: `${found.path} has no scenarios to walk \u2014 it may not be a verification guide.` };
2417
+ return { ...guide, slug: found.slug };
2418
+ }
2419
+
2291
2420
  // src/tui/components.tsx
2421
+ import { readFileSync as readFileSync3, writeFileSync } from "fs";
2292
2422
  import { Fragment, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
2293
2423
  function parsePending(raw) {
2294
2424
  const t = raw.replace(/^\s+/, "");
@@ -3042,7 +3172,7 @@ function ViewportLines({ lines, height }) {
3042
3172
  lines.map((line, i) => /* @__PURE__ */ jsx4(Text4, { children: line.length === 0 ? " " : line.map((s, j) => /* @__PURE__ */ jsx4(Text4, { color: s.color, backgroundColor: s.backgroundColor, bold: s.bold, italic: s.italic, dimColor: s.dim, children: s.text }, j)) }, i))
3043
3173
  ] });
3044
3174
  }
3045
- function App({ controller, fullscreen = false, model, coachModel, refinerModel, listModels, setModel, setRoleModel, listRoles, adjustRoles, listSessions, resumeSession, listPins, addPin, removePin, listMemories, addMemory, removeMemory, listMcp, sourcesInfo, refreshSources, listSkills, updateSkills, addSkill, graphStatus: graphStatus2, buildGraph, planTraces, runTraces, cleanWorktrees, migrate, continueFromClaude, addMcp, answerByTheWay, telemetryPath, parallel, setParallel, permMode, setPermMode, cancelJob, onExit }) {
3175
+ function App({ controller, fullscreen = false, model, coachModel, refinerModel, listModels, setModel, setRoleModel, listRoles, adjustRoles, modelsPanel, initProject, listSessions, resumeSession, listPins, addPin, removePin, listMemories, addMemory, removeMemory, listMcp, sourcesInfo, refreshSources, listSkills, updateSkills, addSkill, graphStatus: graphStatus2, buildGraph, planTraces, runTraces, cleanWorktrees, migrate, continueFromClaude, addMcp, answerByTheWay, telemetryPath, parallel, setParallel, permMode, setPermMode, cancelJob, onExit }) {
3046
3176
  const [state, setState] = useState3(controller.getState());
3047
3177
  useEffect3(() => controller.subscribe(() => setState(controller.getState())), [controller]);
3048
3178
  const { stdout } = useStdout();
@@ -3255,10 +3385,10 @@ _Type \`/resume N\` to continue one._`);
3255
3385
  }
3256
3386
  try {
3257
3387
  const { writeFile: writeFile3, mkdir: mkdir3 } = await import("fs/promises");
3258
- const dir = join2(homedir(), ".horsecode", "pastes");
3388
+ const dir = join3(homedir(), ".horsecode", "pastes");
3259
3389
  await mkdir3(dir, { recursive: true });
3260
3390
  const id = ++imageIdRef.current;
3261
- const file = join2(dir, `paste-${process.pid}-${id}.png`);
3391
+ const file = join3(dir, `paste-${process.pid}-${id}.png`);
3262
3392
  await writeFile3(file, Buffer.from(uri.slice(uri.indexOf(",") + 1), "base64"));
3263
3393
  imageMapRef.current.set(id, file);
3264
3394
  const tok = imageToken(id);
@@ -3498,6 +3628,35 @@ ${rows.join("\n")}
3498
3628
 
3499
3629
  _\`/mcp add <url|command>\` to install another._`);
3500
3630
  };
3631
+ const doInit = async () => {
3632
+ if (!initProject) {
3633
+ controller.note("Project setup is not available.");
3634
+ return;
3635
+ }
3636
+ controller.note("Setting the project up\u2026");
3637
+ let report;
3638
+ try {
3639
+ report = await initProject();
3640
+ } catch (e) {
3641
+ controller.note(`init error: ${e instanceof Error ? e.message : String(e)}`);
3642
+ return;
3643
+ }
3644
+ controller.note(report.text);
3645
+ if (!report.canImport || !migrate) return;
3646
+ if (await controller.ask("Read these and import what they say?", { options: ["Import", "Skip"] }) !== "Import") {
3647
+ controller.note("Left alone. Run `/init` again, or `/migrate`, whenever you want them read.");
3648
+ return;
3649
+ }
3650
+ controller.note(await migrate(report.extra));
3651
+ };
3652
+ const doModels = () => {
3653
+ if (!modelsPanel) {
3654
+ controller.note("Models are not available.");
3655
+ return;
3656
+ }
3657
+ const inUse = [...new Set((listRoles?.() ?? []).flatMap((r) => r.models))];
3658
+ controller.note(modelsPanel(inUse));
3659
+ };
3501
3660
  const doSources = (arg) => {
3502
3661
  const info = sourcesInfo?.();
3503
3662
  if (arg.trim().toLowerCase() === "refresh") {
@@ -3561,6 +3720,56 @@ _\`/sources refresh\` to re-probe your connected subscriptions._`);
3561
3720
  }
3562
3721
  );
3563
3722
  };
3723
+ const doSmokeTest = async () => {
3724
+ const guide = loadGuide(process.cwd());
3725
+ if ("error" in guide) {
3726
+ controller.note(guide.error);
3727
+ return;
3728
+ }
3729
+ controller.note(
3730
+ `\u{1F9EA} **${guide.title}**
3731
+
3732
+ \`${guide.slug}\` \xB7 ${guide.steps.length} ad\u0131m \xB7 ${guide.scenarios.length} senaryo`
3733
+ );
3734
+ if (guide.preconditions.length) {
3735
+ controller.note(`**Ba\u015Flamadan \xF6nce**
3736
+ ${guide.preconditions.map((p) => `- ${p}`).join("\n")}`);
3737
+ }
3738
+ controller.note(
3739
+ "Her ad\u0131mda ne g\xF6rd\xFC\u011F\xFCn\xFC yap\u0131\u015Ft\u0131r \u2014 API yan\u0131t\u0131, DB sat\u0131r\u0131, log kayd\u0131. horse-code veritaban\u0131na ve Loki'ye kendisi bakmaz; kan\u0131t senin g\xF6zlemin olarak kaydedilir."
3740
+ );
3741
+ if (await controller.ask("Ortam haz\u0131r m\u0131?", { options: ["Ba\u015Fla", "\u0130ptal"] }) !== "Ba\u015Fla") {
3742
+ controller.note("B\u0131rak\u0131ld\u0131 \u2014 rehberde hi\xE7bir \u015Fey de\u011Fi\u015Fmedi.");
3743
+ return;
3744
+ }
3745
+ let markdown = readFileSync3(guide.path, "utf8");
3746
+ let batch = [];
3747
+ const flush = (index) => {
3748
+ if (!batch.length) return;
3749
+ const { status, evidence } = scenarioOutcome(batch);
3750
+ markdown = recordOutcome(markdown, index, status, evidence);
3751
+ writeFileSync(guide.path, markdown);
3752
+ controller.note(`\u{1F4DD} _${guide.scenarios[index]}_ \u2192 **${status}**`);
3753
+ batch = [];
3754
+ };
3755
+ for (const [i, step] of guide.steps.entries()) {
3756
+ const previous = guide.steps[i - 1];
3757
+ if (previous && previous.scenarioIndex !== step.scenarioIndex) flush(previous.scenarioIndex);
3758
+ controller.note(renderStep(step, guide.steps.length));
3759
+ const answer = await controller.ask("Sonu\xE7?", { options: ["ge\xE7ti", "ba\u015Far\u0131s\u0131z", "atla", "durdur"] });
3760
+ if (answer === "durdur") {
3761
+ flush(step.scenarioIndex);
3762
+ controller.note(`Durduruldu \u2014 ${step.n - 1}/${guide.steps.length} ad\u0131m kaydedildi.`);
3763
+ return;
3764
+ }
3765
+ const status = answer === "ge\xE7ti" ? "ge\xE7ti" : answer === "ba\u015Far\u0131s\u0131z" ? "ba\u015Far\u0131s\u0131z" : "\xE7al\u0131\u015Ft\u0131r\u0131lmad\u0131";
3766
+ const evidence = status === "\xE7al\u0131\u015Ft\u0131r\u0131lmad\u0131" ? "" : await controller.ask("Ne g\xF6zlemledin? (API yan\u0131t\u0131 / DB sat\u0131r\u0131 / log)");
3767
+ batch.push({ step, status, evidence });
3768
+ controller.note(progressLine(step.n, guide.steps.length));
3769
+ }
3770
+ flush(guide.steps[guide.steps.length - 1].scenarioIndex);
3771
+ controller.note(`\u2705 Bitti \u2014 ${guide.path} g\xFCncellendi.`);
3772
+ };
3564
3773
  const doGraph = (arg) => {
3565
3774
  if (!graphStatus2 || !buildGraph) {
3566
3775
  controller.note("The project graph is not available.");
@@ -3724,8 +3933,11 @@ _\`/skills add <github-url>\` to install one \xB7 \`/skills update\` re-installs
3724
3933
  else if (c.name === "/remember") doRemember("");
3725
3934
  else if (c.name === "/forget") doForget("");
3726
3935
  else if (c.name === "/mcp") doMcp("");
3936
+ else if (c.name === "/init") void doInit();
3937
+ else if (c.name === "/models") doModels();
3727
3938
  else if (c.name === "/sources") doSources("");
3728
3939
  else if (c.name === "/skills") doSkills("");
3940
+ else if (c.name === "/start-smoke-test") void doSmokeTest();
3729
3941
  else if (c.name === "/graph") doGraph("");
3730
3942
  else if (c.name === "/clean-worktrees") doCleanWorktrees("");
3731
3943
  else if (c.name === "/paste") pasteImage();
@@ -4015,9 +4227,9 @@ _\`/skills add <github-url>\` to install one \xB7 \`/skills update\` re-installs
4015
4227
  const cw = Math.max(1, size.cols - 4);
4016
4228
  const inputH = draft.split("\n").reduce((n, l) => n + Math.max(1, Math.ceil((l.length + 3) / cw)), 0);
4017
4229
  const running = mode === "running";
4018
- const progressLine = running && !state.pending;
4230
+ const progressLine2 = running && !state.pending;
4019
4231
  const doneLine = !!state.meta && !state.meta.running && !state.pending;
4020
- const showStatus = progressLine || !!state.pending || doneLine;
4232
+ const showStatus = progressLine2 || !!state.pending || doneLine;
4021
4233
  const choiceOptionsForFit = state.pending?.options ?? [];
4022
4234
  const pendingBodyMax = state.pending ? Math.max(
4023
4235
  1,
@@ -4028,7 +4240,7 @@ _\`/skills add <github-url>\` to install one \xB7 \`/skills update\` re-installs
4028
4240
  flattenMarkdown(parsePending(state.pending.question).body, pendingBodyWidth(size.cols)).length,
4029
4241
  pendingBodyMax
4030
4242
  ) : 0;
4031
- const statusH = (progressLine || doneLine ? 1 : 0) + pendingLines;
4243
+ const statusH = (progressLine2 || doneLine ? 1 : 0) + pendingLines;
4032
4244
  const inputMarginTop = showStatus ? 0 : 1;
4033
4245
  const choiceOptions = state.pending?.options ?? [];
4034
4246
  const choiceActive = choiceOptions.length > 0 && !choiceDismissed;
@@ -4075,7 +4287,7 @@ _\`/skills add <github-url>\` to install one \xB7 \`/skills update\` re-installs
4075
4287
  /* @__PURE__ */ jsx4(Box4, { paddingLeft: CHAT_INDENT, children: /* @__PURE__ */ jsx4(ViewportLines, { lines: windowed, height: viewportH }) }),
4076
4288
  /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: clamped > 0 ? ` \u2193 ${clamped} more \xB7 \u2193/PgDn to jump to bottom` : " " }),
4077
4289
  showStatus ? /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
4078
- progressLine ? /* @__PURE__ */ jsx4(Box4, { paddingLeft: 2, children: /* @__PURE__ */ jsx4(ProgressView, { phase: state.phase, detail: state.detail, refinerModel: refinerModel?.(), meta: state.meta, cols: size.cols, live: state.liveActivity }) }) : null,
4290
+ progressLine2 ? /* @__PURE__ */ jsx4(Box4, { paddingLeft: 2, children: /* @__PURE__ */ jsx4(ProgressView, { phase: state.phase, detail: state.detail, refinerModel: refinerModel?.(), meta: state.meta, cols: size.cols, live: state.liveActivity }) }) : null,
4079
4291
  doneLine ? /* @__PURE__ */ jsx4(Box4, { paddingLeft: 2, children: /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: `${donePhrase(state.phase)} for ${fmtDuration(state.meta?.durationMs ?? 0)}${state.meta && state.meta.calls > 0 ? ` \xB7 \u2191${fmtTokens(state.meta.promptTokens)} \u2193${fmtTokens(state.meta.completionTokens)} \xB7 ${state.meta.calls} call${state.meta.calls === 1 ? "" : "s"}` : ""}` }) }) : null,
4080
4292
  state.pending ? /* @__PURE__ */ jsx4(PendingQuestion, { text: state.pending.question, cols: size.cols, maxLines: pendingBodyMax }) : null
4081
4293
  ] }) : null,
@@ -4268,6 +4480,15 @@ _\`/skills add <github-url>\` to install one \xB7 \`/skills update\` re-installs
4268
4480
  doMcp(trimmed.slice("/mcp".length).trim());
4269
4481
  return;
4270
4482
  }
4483
+ if (cmd === "/start-smoke-test") {
4484
+ setScroll(0);
4485
+ setDraft("");
4486
+ setDraftCursor(0);
4487
+ usedPastes();
4488
+ controller.echoCommand(trimmed);
4489
+ void doSmokeTest();
4490
+ return;
4491
+ }
4271
4492
  if (cmd.startsWith("/graph ")) {
4272
4493
  setScroll(0);
4273
4494
  setDraft("");
@@ -4379,7 +4600,7 @@ async function saveMode(home, mode) {
4379
4600
  // src/session/store.ts
4380
4601
  import { mkdir, readFile as readFile2, readdir } from "fs/promises";
4381
4602
  import { createHash } from "crypto";
4382
- import { join as join3 } from "path";
4603
+ import { join as join4 } from "path";
4383
4604
  var SessionStore = class {
4384
4605
  activeId;
4385
4606
  dir;
@@ -4387,7 +4608,7 @@ var SessionStore = class {
4387
4608
  constructor(opts) {
4388
4609
  this.now = opts.now ?? (() => Date.now());
4389
4610
  const hash = createHash("sha256").update(opts.cwd).digest("hex").slice(0, 16);
4390
- this.dir = join3(opts.home, ".horsecode", "projects", hash, "sessions");
4611
+ this.dir = join4(opts.home, ".horsecode", "projects", hash, "sessions");
4391
4612
  this.activeId = `s${this.now()}`;
4392
4613
  }
4393
4614
  /** The id this store currently writes to (a fresh one, or a resumed session's id after setActive). */
@@ -4399,7 +4620,7 @@ var SessionStore = class {
4399
4620
  this.activeId = id;
4400
4621
  }
4401
4622
  file(id) {
4402
- return join3(this.dir, `${id}.json`);
4623
+ return join4(this.dir, `${id}.json`);
4403
4624
  }
4404
4625
  /** Overwrite the active session with the current messages (no-op on an empty transcript). */
4405
4626
  async save(messages) {
@@ -4426,7 +4647,7 @@ var SessionStore = class {
4426
4647
  for (const n of names) {
4427
4648
  if (!n.endsWith(".json")) continue;
4428
4649
  try {
4429
- const d = JSON.parse(await readFile2(join3(this.dir, n), "utf8"));
4650
+ const d = JSON.parse(await readFile2(join4(this.dir, n), "utf8"));
4430
4651
  out.push({ id: d.id, title: d.title, updatedAt: d.updatedAt, count: d.count });
4431
4652
  } catch {
4432
4653
  }
@@ -4452,7 +4673,7 @@ function titleOf(messages) {
4452
4673
  // src/session/pins.ts
4453
4674
  import { mkdir as mkdir2, readFile as readFile3 } from "fs/promises";
4454
4675
  import { createHash as createHash2 } from "crypto";
4455
- import { join as join4, dirname } from "path";
4676
+ import { join as join5, dirname } from "path";
4456
4677
  var MAX_PINS = 20;
4457
4678
  var MAX_PIN_CHARS = 500;
4458
4679
  var PinStore = class {
@@ -4460,7 +4681,7 @@ var PinStore = class {
4460
4681
  cache;
4461
4682
  constructor(opts) {
4462
4683
  const hash = createHash2("sha256").update(opts.cwd).digest("hex").slice(0, 16);
4463
- this.file = join4(opts.home, ".horsecode", "projects", hash, "pins.json");
4684
+ this.file = join5(opts.home, ".horsecode", "projects", hash, "pins.json");
4464
4685
  }
4465
4686
  /** Load pins from disk (memoized). Missing/corrupt → empty. */
4466
4687
  async load() {
@@ -4650,7 +4871,7 @@ var ModelHealth = class {
4650
4871
  }
4651
4872
  const live = all.filter((m) => !dead.has(m));
4652
4873
  const configured = new Set(this.port.registries().flatMap((r) => r.knownModels()));
4653
- const curated = live.filter((m) => configured.has(m));
4874
+ const curated = live.filter((m) => configured.has(m) || cliFor(m) !== void 0);
4654
4875
  telemetry().event("decision.pool", {
4655
4876
  "hc.decision": "pool",
4656
4877
  "hc.pool.catalog": all.length,
@@ -4995,7 +5216,7 @@ First explain your key choices in a few short sentences, including which skills
4995
5216
  // src/engine/scan-repo.ts
4996
5217
  import { spawn as spawn2 } from "child_process";
4997
5218
  import { readFile as readFile4 } from "fs/promises";
4998
- import { join as join5 } from "path";
5219
+ import { join as join6 } from "path";
4999
5220
  var MANIFESTS = ["package.json", "pyproject.toml", "requirements.txt", "go.mod", "Cargo.toml", "Gemfile", "pom.xml", "build.gradle", "composer.json"];
5000
5221
  var MAX_FILES = 2e4;
5001
5222
  function gitFiles(cwd) {
@@ -5015,7 +5236,7 @@ async function scanRepo(cwd) {
5015
5236
  for (const name of MANIFESTS) {
5016
5237
  if (!files.includes(name)) continue;
5017
5238
  try {
5018
- manifests[name] = await readFile4(join5(cwd, name), "utf8");
5239
+ manifests[name] = await readFile4(join6(cwd, name), "utf8");
5019
5240
  } catch {
5020
5241
  }
5021
5242
  }
@@ -5192,7 +5413,7 @@ function startupSummary(f) {
5192
5413
 
5193
5414
  // src/tui/app.tsx
5194
5415
  import { execFileSync } from "child_process";
5195
- import { existsSync } from "fs";
5416
+ import { existsSync as existsSync2 } from "fs";
5196
5417
  import { jsx as jsx5 } from "react/jsx-runtime";
5197
5418
  async function runTui(opts) {
5198
5419
  const controller = new TuiController();
@@ -5242,7 +5463,7 @@ async function runTuiRepl(opts) {
5242
5463
  const chain = regFor(r).chain(r);
5243
5464
  return { name: r, model: chain[0] ?? "", models: chain, council: reviewNames.has(r), decider: councilNames.includes(r) };
5244
5465
  });
5245
- const fitness = deps0.fitness ?? new RoleFitness(join6(homedir2(), ".horsecode", "model-fitness.json"));
5466
+ const fitness = deps0.fitness ?? new RoleFitness(join7(homedir2(), ".horsecode", "model-fitness.json"));
5246
5467
  for (const r of [deps0.roleRegistry, deps0.teamRegistries.spec, deps0.teamRegistries.plan, deps0.teamRegistries.code, deps0.councilRegistry]) {
5247
5468
  r.setFitness(fitness);
5248
5469
  }
@@ -5373,7 +5594,7 @@ Question: ${question}` }
5373
5594
  };
5374
5595
  const addMcp = async (input) => {
5375
5596
  const { parseCommand, parseConfigBlock, extractFromPage, verify } = await import("./install-O34KMWJB.js");
5376
- const { saveMcpServer } = await import("./save-skills-X7U3KCPU.js");
5597
+ const { saveMcpServer } = await import("./save-skills-NPKTYNAF.js");
5377
5598
  let cand = parseCommand(input) ?? parseConfigBlock(input);
5378
5599
  if (!cand) {
5379
5600
  const url = input.trim();
@@ -5442,14 +5663,15 @@ _This project has no worktrees under \`.claude/worktrees\`._`}`);
5442
5663
  controller.note(`Could not continue from that worktree: ${e instanceof Error ? e.message : String(e)}`);
5443
5664
  }
5444
5665
  };
5445
- const migrate = async () => {
5666
+ const migrate = async (extra) => {
5446
5667
  if (!opts.memStore) return "Migration needs the memory store, which is not available in this session.";
5447
5668
  const progressLines = /* @__PURE__ */ new Map();
5448
- const { runMigration, describeResult } = await import("./run-P6ZYL5JL.js");
5669
+ const { runMigration, describeResult } = await import("./run-V5ZLZ3LS.js");
5449
5670
  const r = await runMigration({
5450
5671
  cwd: process.cwd(),
5451
5672
  home: homedir2(),
5452
5673
  provider: deps.provider,
5674
+ ...extra?.length ? { extra } : {},
5453
5675
  /**
5454
5676
  * The architect's whole CHAIN, not just its head.
5455
5677
  *
@@ -5524,12 +5746,13 @@ _Restart to pick them up in already-built prompts._` : `No role carries one perm
5524
5746
  };
5525
5747
  const refreshGraphIfStale = async () => {
5526
5748
  try {
5527
- const before = await graphStatus(process.cwd());
5749
+ const dir = await (opts.derivedDir?.() ?? Promise.resolve(process.cwd()));
5750
+ const before = await graphStatus(dir);
5528
5751
  if (before.built && !before.stale) return;
5529
5752
  const first = !before.built;
5530
5753
  controller.note(first ? `\u{1F9E0} Building the code graph for the first time \u2014 this reads the whole project, so it takes a few minutes. It runs once; after this, start-up only updates what changed.` : `\u{1F504} The code graph is out of date \u2014 updating it.`);
5531
5754
  controller.startBusy(first ? "building the code graph" : "updating the code graph");
5532
- const r = await buildProjectGraph(process.cwd());
5755
+ const r = await buildProjectGraph(dir);
5533
5756
  controller.endBusy();
5534
5757
  controller.note(r.ok ? `\u2705 Code graph ready \u2014 ${(r.nodes ?? 0).toLocaleString("en-US")} symbols, ${(r.edges ?? 0).toLocaleString("en-US")} relationships.` : `\u26A0\uFE0F The code graph could not be built: ${r.message}`);
5535
5758
  } catch {
@@ -5671,7 +5894,7 @@ _${saved ? `Saved to your config \u2014 future sessions start with these. ` : ""
5671
5894
  rules: rulesFromMemory().length,
5672
5895
  memory: { total: all.length, rules: kindOf("rule"), lessons: kindOf("lesson"), facts: kindOf("fact") },
5673
5896
  skills: opts.listSkills?.().length ?? 0,
5674
- constitution: existsSync(join6(process.cwd(), ".specify", "memory", "constitution.md")),
5897
+ constitution: existsSync2(join7(process.cwd(), ".specify", "memory", "constitution.md")),
5675
5898
  graph: { built: false, nodes: 0 },
5676
5899
  traceRoot: traceRootRel(),
5677
5900
  ...(() => {
@@ -5711,7 +5934,7 @@ _${saved ? `Saved to your config \u2014 future sessions start with these. ` : ""
5711
5934
  const cwd = process.cwd();
5712
5935
  const main = await recordedMainBranch(cwd) ?? await detectMainBranch(cwd, defaultGitRunner);
5713
5936
  if (!main) return;
5714
- const { ongoingWork, chooseOngoing } = await import("./ongoing-WHYXPW24.js");
5937
+ const { ongoingWork, chooseOngoing } = await import("./ongoing-6NUSPSCV.js");
5715
5938
  const open = await ongoingWork(defaultGitRunner, cwd, main);
5716
5939
  if (!open.length) return;
5717
5940
  const picked = await chooseOngoing(deps, read, open[0]?.language, open);
@@ -5730,11 +5953,11 @@ _${saved ? `Saved to your config \u2014 future sessions start with these. ` : ""
5730
5953
  })();
5731
5954
  void (async () => {
5732
5955
  try {
5733
- const { loadTraceIndex } = await import("./trace-X6TU3AG6.js");
5956
+ const { loadTraceIndex } = await import("./trace-UVMZZRA5.js");
5734
5957
  let index = await loadTraceIndex(process.cwd());
5735
5958
  startupExtra.traces = Object.keys(index.traces).length;
5736
5959
  paintSummary();
5737
- const { graphStatus: graphStatus2 } = await import("./project-graph-5HNPRFQG.js");
5960
+ const { graphStatus: graphStatus2 } = await import("./project-graph-OGIM2B33.js");
5738
5961
  const g = await graphStatus2(process.cwd());
5739
5962
  startupExtra.graph = { built: g.built, nodes: g.nodes, stale: g.stale };
5740
5963
  if (!g.built || g.stale) {
@@ -5743,18 +5966,18 @@ _${saved ? `Saved to your config \u2014 future sessions start with these. ` : ""
5743
5966
  const after = await graphStatus2(process.cwd());
5744
5967
  startupExtra.graph = { built: after.built, nodes: after.nodes, stale: after.stale };
5745
5968
  }
5746
- const g0 = await import("./project-graph-5HNPRFQG.js").then((m) => m.loadGraphSync(process.cwd()));
5969
+ const g0 = await import("./project-graph-OGIM2B33.js").then((m) => m.loadGraphSync(process.cwd()));
5747
5970
  if (g0) {
5748
5971
  const files = /* @__PURE__ */ new Set();
5749
5972
  for (const n of g0.nodes) if (n.source_file) files.add(n.source_file);
5750
- const { adoptDocs, indexAdoption, describeAdoption } = await import("./trace-adopt-URECQWJV.js");
5973
+ const { adoptDocs, indexAdoption, describeAdoption } = await import("./trace-adopt-7HWELJFE.js");
5751
5974
  const adoption = await adoptDocs(process.cwd(), files);
5752
5975
  const { readFile: readFile5 } = await import("fs/promises");
5753
5976
  const r = await indexAdoption(
5754
5977
  process.cwd(),
5755
5978
  index,
5756
5979
  adoption,
5757
- async (f) => readFile5(join6(process.cwd(), f), "utf8").catch(() => void 0)
5980
+ async (f) => readFile5(join7(process.cwd(), f), "utf8").catch(() => void 0)
5758
5981
  );
5759
5982
  if (r.added) {
5760
5983
  index = r.index;
@@ -5763,8 +5986,8 @@ _${saved ? `Saved to your config \u2014 future sessions start with these. ` : ""
5763
5986
  }
5764
5987
  startupExtra.traces = Object.keys(index.traces).length;
5765
5988
  paintSummary();
5766
- const { traceableFiles } = await import("./trace-run-7U4WJZ3V.js");
5767
- const { traceCoverage } = await import("./trace-X6TU3AG6.js");
5989
+ const { traceableFiles } = await import("./trace-run-CZWEZ4R6.js");
5990
+ const { traceCoverage } = await import("./trace-UVMZZRA5.js");
5768
5991
  startupExtra.coverage = await traceCoverage(process.cwd(), await traceableFiles(process.cwd()), index);
5769
5992
  paintSummary();
5770
5993
  } catch {
@@ -5880,6 +6103,8 @@ _${saved ? `Saved to your config \u2014 future sessions start with these. ` : ""
5880
6103
  setRoleModel: applyChainPersisted,
5881
6104
  listRoles,
5882
6105
  adjustRoles,
6106
+ modelsPanel: opts.modelsPanel,
6107
+ initProject: opts.initProject,
5883
6108
  listSkills: opts.listSkills,
5884
6109
  updateSkills: opts.updateSkills,
5885
6110
  addSkill: opts.addSkill,