@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.
Files changed (32) hide show
  1. package/dist/{app-5FXHE7GX.js → app-KR7TAHUD.js} +82 -40
  2. package/dist/{chunk-LNW557IO.js → chunk-372X5HHU.js} +2 -2
  3. package/dist/{chunk-XEGQT5EN.js → chunk-4M6LXNG2.js} +1 -1
  4. package/dist/{chunk-6OSEQOYY.js → chunk-6S4WWQMN.js} +2 -2
  5. package/dist/chunk-ACTVFJRW.js +989 -0
  6. package/dist/{chunk-LLL7QWXB.js → chunk-BFIZMM4G.js} +6 -6
  7. package/dist/chunk-CYLPQWIF.js +214 -0
  8. package/dist/{chunk-AE36LLL2.js → chunk-JLWQCA7B.js} +2 -209
  9. package/dist/{chunk-XYZVZPAY.js → chunk-KXBYRU4W.js} +66 -31
  10. package/dist/chunk-MZM24M5M.js +251 -0
  11. package/dist/chunk-NBTH2VVI.js +1945 -0
  12. package/dist/chunk-PIG54WFU.js +2873 -0
  13. package/dist/{run-P6ZYL5JL.js → chunk-QJYVZPLG.js} +133 -389
  14. package/dist/{chunk-YPZP7LYL.js → chunk-UANNVVIU.js} +1 -1
  15. package/dist/{chunk-UGESK765.js → chunk-UTHLEW5V.js} +1 -1
  16. package/dist/{chunk-KAGKX2YT.js → chunk-ZPJP2VH5.js} +10 -1
  17. package/dist/cli.js +262 -69
  18. package/dist/{fix-ONLA45HD.js → fix-SSDUVV4T.js} +11 -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/research-MHBNZ6SA.js +111 -0
  22. package/dist/run-V5ZLZ3LS.js +274 -0
  23. package/dist/{save-skills-ZW5GY6KV.js → save-skills-NPKTYNAF.js} +2 -2
  24. package/dist/{trace-X6TU3AG6.js → trace-UVMZZRA5.js} +1 -1
  25. package/dist/{trace-adopt-URECQWJV.js → trace-adopt-7HWELJFE.js} +1 -1
  26. package/dist/{trace-run-7U4WJZ3V.js → trace-run-OBOD552Q.js} +9 -4
  27. package/dist/{triage-FCYHD2AQ.js → triage-IFCVL5MA.js} +7 -6
  28. package/dist/{verify-LC57A6H2.js → verify-2DTBG6RG.js} +26 -20
  29. package/package.json +1 -1
  30. package/dist/chunk-MRZVA5JB.js +0 -163
  31. package/dist/chunk-UEWVVN5L.js +0 -5691
  32. package/dist/{chunk-EAF22QIG.js → chunk-JR2JLRE3.js} +3 -3
package/dist/cli.js CHANGED
@@ -15,32 +15,28 @@ import {
15
15
  restoreTerminal,
16
16
  runJob,
17
17
  sttySane
18
- } from "./chunk-XYZVZPAY.js";
18
+ } from "./chunk-KXBYRU4W.js";
19
19
  import "./chunk-M2RKCIGV.js";
20
+ import "./chunk-UTHLEW5V.js";
20
21
  import {
21
- CODE_TEAM,
22
22
  CliProvider,
23
- DEFAULT_COUNCIL,
24
- DEFAULT_PROMPTS,
25
- DEFAULT_ROLE_SKILLS,
26
- PLAN_TEAM,
27
- REQUIRED_ROLES,
28
23
  RoleFitness,
29
- RoleRegistry,
30
24
  SHORT_CALL_MS,
31
- SPEC_TEAM,
32
- WorktreeManager,
33
- ZAI_MODELS,
34
25
  buildCouncilRegistry,
35
- buildTeamRegistry,
36
- cliCatalog,
26
+ buildTeamRegistry
27
+ } from "./chunk-PIG54WFU.js";
28
+ import {
29
+ WorktreeManager,
37
30
  mainWorktreeRoot,
38
- modelsFor,
39
31
  toSlug
40
- } from "./chunk-UEWVVN5L.js";
41
- import "./chunk-QF4MP6BS.js";
42
- import "./chunk-UGESK765.js";
32
+ } from "./chunk-NBTH2VVI.js";
43
33
  import "./chunk-ZSQ24YDJ.js";
34
+ import "./chunk-QF4MP6BS.js";
35
+ import {
36
+ MAX_CHUNK_CHARS,
37
+ discover,
38
+ readFinding
39
+ } from "./chunk-QJYVZPLG.js";
44
40
  import {
45
41
  parseFrontmatter
46
42
  } from "./chunk-BY4DP7IE.js";
@@ -48,58 +44,77 @@ import {
48
44
  UNSET_MODEL,
49
45
  loadConfig,
50
46
  saveSkillSource
51
- } from "./chunk-EAF22QIG.js";
52
- import {
53
- CLI_KINDS,
54
- checkProfile,
55
- runLogin
56
- } from "./chunk-G45RWL7S.js";
47
+ } from "./chunk-JR2JLRE3.js";
57
48
  import "./chunk-H2FDGPVW.js";
58
49
  import {
50
+ RoleRegistry,
51
+ ZAI_MODELS,
59
52
  buildBrief,
53
+ cliCatalog,
60
54
  describePlan,
55
+ modelsFor,
61
56
  planFor,
62
57
  runTraces,
63
58
  traceableFiles
64
- } from "./chunk-MRZVA5JB.js";
59
+ } from "./chunk-ACTVFJRW.js";
60
+ import {
61
+ CODE_TEAM,
62
+ DEFAULT_COUNCIL,
63
+ DEFAULT_PROMPTS,
64
+ DEFAULT_ROLE_SKILLS,
65
+ PLAN_TEAM,
66
+ REQUIRED_ROLES,
67
+ SPEC_TEAM
68
+ } from "./chunk-MZM24M5M.js";
69
+ import {
70
+ CLI_KINDS,
71
+ checkProfile,
72
+ runLogin
73
+ } from "./chunk-G45RWL7S.js";
65
74
  import {
66
75
  defaultGitRunner
67
76
  } from "./chunk-LPQU436C.js";
68
77
  import {
69
78
  InjectionLog,
70
79
  memoryNote
71
- } from "./chunk-LLL7QWXB.js";
80
+ } from "./chunk-BFIZMM4G.js";
81
+ import {
82
+ ToolRegistry
83
+ } from "./chunk-CYLPQWIF.js";
72
84
  import "./chunk-63E73TGI.js";
85
+ import {
86
+ briefStatus
87
+ } from "./chunk-6S4WWQMN.js";
73
88
  import {
74
89
  Telemetry,
75
- ToolRegistry,
76
90
  clearPerfMarks,
77
91
  sampleMemory,
78
92
  setTelemetry,
79
93
  stripThinking,
80
94
  telemetry,
81
95
  writeHeapSnapshot
82
- } from "./chunk-AE36LLL2.js";
83
- import {
84
- briefStatus
85
- } from "./chunk-6OSEQOYY.js";
96
+ } from "./chunk-JLWQCA7B.js";
86
97
  import {
87
98
  discoverTraceRoot,
88
- setTraceRoot
89
- } from "./chunk-KAGKX2YT.js";
99
+ ensureGitignore,
100
+ localOnly,
101
+ setTraceRoot,
102
+ sharedDerived,
103
+ traceRootRel
104
+ } from "./chunk-ZPJP2VH5.js";
90
105
  import {
91
106
  buildProjectGraph,
92
107
  graphStatus,
93
108
  graphifyPython
94
- } from "./chunk-XEGQT5EN.js";
109
+ } from "./chunk-4M6LXNG2.js";
95
110
  import {
96
111
  sessionBase
97
112
  } from "./chunk-6W4UH2BQ.js";
98
113
 
99
114
  // src/cli.ts
100
115
  import { execFileSync } from "child_process";
101
- import { readFileSync as readFileSync5, existsSync as existsSync3, writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, realpathSync, rmSync } from "fs";
102
- import { join as join11, dirname as dirname5 } from "path";
116
+ import { readFileSync as readFileSync5, existsSync as existsSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, realpathSync, rmSync, statSync as statSync2 } from "fs";
117
+ import { join as join12, dirname as dirname5 } from "path";
103
118
  import { pathToFileURL } from "url";
104
119
 
105
120
  // src/agents/cli-accounts.ts
@@ -694,6 +709,137 @@ async function syncSkillSources(home, sources, deps = {}) {
694
709
  return { ok, failed };
695
710
  }
696
711
 
712
+ // src/engine/init-project.ts
713
+ import { existsSync as existsSync2, statSync, readdirSync } from "fs";
714
+ import { join as join7 } from "path";
715
+
716
+ // src/migrate/project-docs.ts
717
+ import { basename } from "path";
718
+ var DERIVED = [
719
+ { re: /^\.horsecode\//, why: "horse-code's own state \u2014 traces describe the code, so rules mined from them would be the code talking back to itself" },
720
+ { re: /^graphify-out\//, why: "generated by the graph build" },
721
+ { re: /(^|\/)(node_modules|vendor|third_party|\.git)\//, why: "not this project's writing" },
722
+ { re: /(^|\/)\.claude\/worktrees\//, why: "a nested checkout of this repository" }
723
+ ];
724
+ var HISTORY = [
725
+ { re: /(^|\/)archive(d)?\//i, why: "an archive of finished work" },
726
+ { re: /\d{4}-\d{2}-\d{2}/, why: "dated \u2014 a record of a moment, not a standing rule" },
727
+ { re: /(^|\/)(HANDOFF|RESUME|CHANGELOG|RELEASE[-_]NOTES)[^/]*$/i, why: "a record of what happened" },
728
+ { re: /(^|\/)chats?\//i, why: "a pasted conversation" },
729
+ { re: /(^|\/)chat\d*\.md$/i, why: "a pasted conversation" }
730
+ ];
731
+ function sortProjectDocs(files, known = []) {
732
+ const seen = new Set(known.map((k) => k.replace(/\\/g, "/")));
733
+ const candidates2 = [];
734
+ const skipped = [];
735
+ for (const f of files) {
736
+ const path = f.path.replace(/\\/g, "/");
737
+ if (!/\.mdx?$/i.test(path)) continue;
738
+ if (seen.has(path) || seen.has(basename(path))) continue;
739
+ const why = DERIVED.find((d) => d.re.test(path))?.why ?? HISTORY.find((h) => h.re.test(path))?.why;
740
+ if (why) skipped.push({ path, bytes: f.bytes, skipped: why });
741
+ else candidates2.push({ path, bytes: f.bytes });
742
+ }
743
+ const bySize = (a, b) => b.bytes - a.bytes;
744
+ return { candidates: candidates2.sort(bySize), skipped: skipped.sort(bySize) };
745
+ }
746
+ function totalBytes(docs) {
747
+ return docs.reduce((n, d) => n + d.bytes, 0);
748
+ }
749
+ function classifyCalls(bytes, chunk) {
750
+ return Math.max(1, Math.ceil(bytes / chunk));
751
+ }
752
+
753
+ // src/engine/init-project.ts
754
+ var UNCLAIMED_FILE_FLOOR = 50;
755
+ function countFiles(dir, cap = UNCLAIMED_FILE_FLOOR * 4) {
756
+ let n = 0;
757
+ const walk = (d) => {
758
+ if (n >= cap) return;
759
+ let entries;
760
+ try {
761
+ entries = readdirSync(d, { withFileTypes: true });
762
+ } catch {
763
+ return;
764
+ }
765
+ for (const e of entries) {
766
+ if (n >= cap) return;
767
+ if (e.isDirectory()) walk(join7(d, e.name));
768
+ else n++;
769
+ }
770
+ };
771
+ walk(dir);
772
+ return n;
773
+ }
774
+ async function initProject(cwd, isIgnored, untracked, knowledge) {
775
+ const changed = await ensureGitignore(cwd);
776
+ const unclaimed = [];
777
+ for (const rel of untracked()) {
778
+ const path = join7(cwd, rel);
779
+ let dir = false;
780
+ try {
781
+ dir = statSync(path).isDirectory();
782
+ } catch {
783
+ continue;
784
+ }
785
+ if (!dir || isIgnored(rel)) continue;
786
+ const files = countFiles(path);
787
+ if (files >= UNCLAIMED_FILE_FLOOR) unclaimed.push({ path: rel, files });
788
+ }
789
+ return {
790
+ changed,
791
+ kept: [`${traceRootRel().replace(/\\/g, "/")}/`, ...sharedDerived()].filter((p) => existsSync2(join7(cwd, p)) || p.endsWith("/")),
792
+ excluded: localOnly(),
793
+ unclaimed: unclaimed.sort((a, b) => b.files - a.files),
794
+ ...knowledge ? { knowledge } : {}
795
+ };
796
+ }
797
+ function hasKnowledge(k) {
798
+ return !!k && (k.named.length > 0 || k.docs.length > 0);
799
+ }
800
+ function size(bytes) {
801
+ return bytes >= 1024 * 1024 ? `${(bytes / 1024 / 1024).toFixed(1)} MB` : `${Math.max(1, Math.round(bytes / 1024))} KB`;
802
+ }
803
+ function describeKnowledge(k, chunk = MAX_CHUNK_CHARS) {
804
+ const lines = ["", "**This project already says things about itself.**", ""];
805
+ if (k.named.length) {
806
+ lines.push(`Files a tool is known to write \u2014 ${size(totalBytes(k.named.map((n) => ({ path: n.label, bytes: n.bytes }))))}:`);
807
+ lines.push(...k.named.map((n) => ` \xB7 \`${n.label}\` (${size(n.bytes)})`));
808
+ }
809
+ if (k.docs.length) {
810
+ const bytes = totalBytes(k.docs);
811
+ lines.push("", `The project's other markdown \u2014 ${k.docs.length} file(s), ${size(bytes)}, about ${classifyCalls(bytes, chunk)} classification call(s):`);
812
+ lines.push(...k.docs.slice(0, 8).map((d) => ` \xB7 \`${d.path}\` (${size(d.bytes)})`));
813
+ if (k.docs.length > 8) lines.push(` \xB7 \u2026and ${k.docs.length - 8} more`);
814
+ }
815
+ if (k.skipped.length) {
816
+ const reasons = /* @__PURE__ */ new Map();
817
+ for (const sk of k.skipped) reasons.set(sk.skipped ?? "", (reasons.get(sk.skipped ?? "") ?? 0) + 1);
818
+ lines.push("", `Set aside \u2014 ${k.skipped.length} file(s), ${size(totalBytes(k.skipped))}:`);
819
+ for (const [why, n] of [...reasons].sort((a, b) => b[1] - a[1])) lines.push(` \xB7 ${n} \xD7 ${why}`);
820
+ }
821
+ return lines.join("\n");
822
+ }
823
+ function describeInit(report) {
824
+ const lines = [
825
+ report.changed ? "**Project set up for horse-code** \u2014 `.gitignore` updated." : "**Project is already set up** \u2014 `.gitignore` already says everything needed.",
826
+ "",
827
+ "Committed on purpose, because a clone would otherwise pay to produce it again:",
828
+ ...report.kept.map((p) => ` \xB7 \`${p}\``),
829
+ "",
830
+ "Kept out on purpose \u2014 derived from the source, keyed to this checkout, or unmergeable:",
831
+ ...report.excluded.map((p) => ` \xB7 \`${p}\``)
832
+ ];
833
+ if (report.unclaimed.length) {
834
+ lines.push(
835
+ "",
836
+ "Untracked and covered by no rule \u2014 `git add -A` would commit these. Yours to decide:",
837
+ ...report.unclaimed.map((u) => ` \xB7 \`${u.path}\` (${u.files >= UNCLAIMED_FILE_FLOOR * 4 ? `${u.files}+` : u.files} files)`)
838
+ );
839
+ }
840
+ return lines.join("\n");
841
+ }
842
+
697
843
  // src/permission/rules.ts
698
844
  import picomatch from "picomatch";
699
845
  var DANGEROUS_PATTERNS = [
@@ -964,8 +1110,8 @@ var ProposalQueue = class {
964
1110
  };
965
1111
 
966
1112
  // src/speckit/templates.ts
967
- import { existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
968
- import { join as join7, dirname as dirname3 } from "path";
1113
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
1114
+ import { join as join8, dirname as dirname3 } from "path";
969
1115
  var TEMPLATE_FILES = {
970
1116
  spec: "spec-template.md",
971
1117
  plan: "plan-template.md",
@@ -988,10 +1134,10 @@ async function loadSpecKit(opts) {
988
1134
  );
989
1135
  }
990
1136
  const fetchFn = opts.fetch ?? globalThis.fetch;
991
- const cacheDir = join7(opts.home, ".horsecode", "spec-kit", opts.version, "templates");
1137
+ const cacheDir = join8(opts.home, ".horsecode", "spec-kit", opts.version, "templates");
992
1138
  const get = async (relPath) => {
993
- const cachePath = join7(cacheDir, relPath);
994
- if (existsSync2(cachePath)) return readFileSync3(cachePath, "utf8");
1139
+ const cachePath = join8(cacheDir, relPath);
1140
+ if (existsSync3(cachePath)) return readFileSync3(cachePath, "utf8");
995
1141
  const url = `${RAW_BASE}/${opts.version}/templates/${relPath}`;
996
1142
  const res = await fetchFn(url);
997
1143
  if (!res.ok) {
@@ -1018,7 +1164,7 @@ Check your network or set specKit.version to a valid tag.`
1018
1164
 
1019
1165
  // src/wiring.ts
1020
1166
  import { homedir } from "os";
1021
- import { join as join8 } from "path";
1167
+ import { join as join9 } from "path";
1022
1168
  async function buildJobDeps(opts) {
1023
1169
  const { config } = opts;
1024
1170
  const roles = {};
@@ -1064,7 +1210,7 @@ async function buildJobDeps(opts) {
1064
1210
  const queue = new ProposalQueue();
1065
1211
  let kitPromise;
1066
1212
  const specKit = () => kitPromise ??= loadSpecKit({ version: config.specKit.version, home: opts.home, fetch: opts.fetch });
1067
- const fitness = new RoleFitness(join8(opts.home ?? homedir(), ".horsecode", "model-fitness.json"));
1213
+ const fitness = new RoleFitness(join9(opts.home ?? homedir(), ".horsecode", "model-fitness.json"));
1068
1214
  roleRegistry.setFitness(fitness);
1069
1215
  return {
1070
1216
  provider: opts.provider,
@@ -1257,9 +1403,9 @@ function redactRecord(record) {
1257
1403
 
1258
1404
  // src/obs/sink.ts
1259
1405
  import { createWriteStream, mkdirSync as mkdirSync4 } from "fs";
1260
- import { join as join9 } from "path";
1406
+ import { join as join10 } from "path";
1261
1407
  function telemetryDir(home) {
1262
- return join9(home, ".horsecode", "telemetry");
1408
+ return join10(home, ".horsecode", "telemetry");
1263
1409
  }
1264
1410
  var MAX_QUEUE = 1e4;
1265
1411
  var FileSink = class {
@@ -1268,7 +1414,7 @@ var FileSink = class {
1268
1414
  dropped = 0;
1269
1415
  path;
1270
1416
  constructor(home, runId) {
1271
- this.path = join9(telemetryDir(home), `${runId}.jsonl`);
1417
+ this.path = join10(telemetryDir(home), `${runId}.jsonl`);
1272
1418
  try {
1273
1419
  mkdirSync4(telemetryDir(home), { recursive: true });
1274
1420
  this.stream = createWriteStream(this.path, { flags: "a" });
@@ -1328,12 +1474,12 @@ var FileSink = class {
1328
1474
 
1329
1475
  // src/cli-info.ts
1330
1476
  import { readFileSync as readFileSync4 } from "fs";
1331
- import { dirname as dirname4, join as join10 } from "path";
1477
+ import { dirname as dirname4, join as join11 } from "path";
1332
1478
  import { fileURLToPath as fileURLToPath2 } from "url";
1333
1479
  function hcodeVersion() {
1334
1480
  try {
1335
1481
  const here = dirname4(fileURLToPath2(import.meta.url));
1336
- const pkg = JSON.parse(readFileSync4(join10(here, "..", "package.json"), "utf8"));
1482
+ const pkg = JSON.parse(readFileSync4(join11(here, "..", "package.json"), "utf8"));
1337
1483
  return pkg.version ?? "unknown";
1338
1484
  } catch {
1339
1485
  return "unknown";
@@ -1499,6 +1645,7 @@ function renderResult(res) {
1499
1645
  if (res.kind === "rejected") return `Not approved (stopped at the ${res.stage} stage).`;
1500
1646
  if (res.kind === "undone") return res.report;
1501
1647
  if (res.kind === "verified") return res.report;
1648
+ if (res.kind === "researched") return res.report;
1502
1649
  if (res.kind === "tweaked") return res.report;
1503
1650
  if (res.kind === "governed") {
1504
1651
  return res.written ? `**Constitution written** \u2014 \`${res.path}\`
@@ -1719,14 +1866,43 @@ async function main(argv) {
1719
1866
  );
1720
1867
  const notSignedIn = ambientLogins.filter((a) => !a.status.loggedIn).map((a) => a.kind);
1721
1868
  const accountsNote = () => accountsLine(accounts.usage(), notSignedIn);
1869
+ const initProjectFn = async () => {
1870
+ const r = await defaultGitRunner(["ls-files", "--others", "--exclude-standard", "--directory"], cwd);
1871
+ const list = r.stdout.split("\n").map((l) => l.trim()).filter(Boolean);
1872
+ const ignored = /* @__PURE__ */ new Set();
1873
+ for (const path of list) {
1874
+ if ((await defaultGitRunner(["check-ignore", "-q", path], cwd)).code === 0) ignored.add(path);
1875
+ }
1876
+ const all = await defaultGitRunner(["ls-files", "--cached", "--others", "--exclude-standard"], cwd);
1877
+ const md = all.stdout.split("\n").map((l) => l.trim()).filter((l) => /\.mdx?$/i.test(l));
1878
+ const withSize = md.map((path) => {
1879
+ try {
1880
+ return { path, bytes: statSync2(join12(cwd, path)).size };
1881
+ } catch {
1882
+ return { path, bytes: 0 };
1883
+ }
1884
+ });
1885
+ const found = await discover({ cwd, home });
1886
+ const named = found.filter((f) => f.kind === "rules" || f.kind === "mcp").map((f) => ({ label: f.label, bytes: f.bytes }));
1887
+ const { candidates: candidates2, skipped } = sortProjectDocs(withSize, found.map((f) => f.label));
1888
+ const knowledge = { named, docs: candidates2, skipped };
1889
+ const report = await initProject(cwd, (path) => ignored.has(path), () => list, knowledge);
1890
+ const canImport = hasKnowledge(knowledge);
1891
+ const extra = [];
1892
+ for (const d of candidates2) {
1893
+ const r2 = await readFinding(join12(cwd, d.path));
1894
+ if (r2?.text) extra.push({ kind: "rules", tool: "project document", path: join12(cwd, d.path), label: d.path, bytes: r2.bytes, own: true, text: r2.text });
1895
+ }
1896
+ return { text: describeInit(report) + (canImport ? describeKnowledge(knowledge) : ""), canImport, extra };
1897
+ };
1722
1898
  const modelsPanelNote = (inUse) => modelsPanel(accounts.usage(), modelsFor, CLI_KINDS, inUse);
1723
1899
  const raw = new CliProvider({ readOnly: false, accounts });
1724
1900
  const provider = config.telemetry ? telemetryProvider(raw, telemetry()) : raw;
1725
1901
  const skillRegistry = new SkillRegistry();
1726
1902
  await registerBuiltinSkills(skillRegistry);
1727
1903
  await skillRegistry.loadFromDir(externalSkillsDir(home));
1728
- const skillsDir = join11(cwd, ".horsecode", "skills");
1729
- if (existsSync3(skillsDir)) await skillRegistry.loadFromDir(skillsDir);
1904
+ const skillsDir = join12(cwd, ".horsecode", "skills");
1905
+ if (existsSync4(skillsDir)) await skillRegistry.loadFromDir(skillsDir);
1730
1906
  const worktreeHome = await mainWorktreeRoot(defaultGitRunner, cwd);
1731
1907
  if (worktreeHome !== cwd) {
1732
1908
  console.log(`\u2139\uFE0F Sessions will be opened in ${worktreeHome} \u2014 the repository's main checkout.`);
@@ -1758,7 +1934,7 @@ async function main(argv) {
1758
1934
  const useTui = shouldUseTui(!!process.stdin.isTTY, !!process.stdout.isTTY, !!args.noTui);
1759
1935
  if (!args.prompt) {
1760
1936
  if (useTui) {
1761
- const { runTuiRepl } = await import("./app-5FXHE7GX.js");
1937
+ const { runTuiRepl } = await import("./app-KR7TAHUD.js");
1762
1938
  const { fetchCatalog, makeProbe, discoverSources } = await import("./discover-G2Z6XC3O.js");
1763
1939
  const { loadSourceCache, saveSourceCache } = await import("./source-cache-XEK5WN7I.js");
1764
1940
  const manualSources = config.modelSources.length > 0;
@@ -1809,8 +1985,8 @@ _Expected something like \`https://github.com/owner/repo\` or a link to the dire
1809
1985
  _Discoverable by every agent. \`/roles adjust\` assigns it to the roles it fits; \`/skills update\` re-installs it from upstream._`;
1810
1986
  };
1811
1987
  const reloadProjectSkills = async () => {
1812
- const dir = join11(cwd, ".horsecode", "skills");
1813
- if (existsSync3(dir)) await skillRegistry.loadFromDir(dir);
1988
+ const dir = join12(cwd, ".horsecode", "skills");
1989
+ if (existsSync4(dir)) await skillRegistry.loadFromDir(dir);
1814
1990
  };
1815
1991
  const updateSkills = async () => {
1816
1992
  if (!config.skillSources.length) {
@@ -1829,7 +2005,7 @@ _Discoverable by every agent. \`/roles adjust\` assigns it to the roles it fits;
1829
2005
  ${lines.join("\n")}${tail}`;
1830
2006
  };
1831
2007
  const graphStatusText = async () => {
1832
- const st = await graphStatus(cwd);
2008
+ const st = await graphStatus((await derivedWorkdir(false)).dir);
1833
2009
  if (!st.built) {
1834
2010
  const py = await graphifyPython();
1835
2011
  return py ? "No code graph yet. `/graph build` \u2014 AST parsing only, no tokens, a few seconds.\n\n_Without it every agent works blind: it can grep for a name but cannot tell what calls it._" : "No code graph, and graphify is not installed.\n\n`uv tool install graphifyy` (or `pipx install graphifyy`), then `/graph build`.\n\n_MIT, pure tree-sitter AST parsing \u2014 no API key, no tokens._";
@@ -1848,7 +2024,7 @@ ${lines.join("\n")}${tail}`;
1848
2024
 
1849
2025
  _Every agent can query it: \`graph_impact\` (blast radius), \`graph_trace\`, \`graph_find\`, \`graph_context\`, \`graph_overview\`._`;
1850
2026
  };
1851
- const buildGraphText = async () => (await buildProjectGraph(cwd)).message;
2027
+ const buildGraphText = async () => (await buildProjectGraph((await derivedWorkdir(true)).dir)).message;
1852
2028
  const cleanWorktreesText = async (apply, branch) => {
1853
2029
  const { surveySessions, cleanSessions, describeSurvey, describeClean } = await import("./clean-YOQATBMZ.js");
1854
2030
  const target = branch?.trim() || fromBranch;
@@ -1858,31 +2034,42 @@ _Every agent can query it: \`graph_impact\` (blast radius), \`graph_trace\`, \`g
1858
2034
  return describeClean(await cleanSessions(defaultGitRunner, cwd, target), target);
1859
2035
  };
1860
2036
  const gitFiles = async () => (await defaultGitRunner(["ls-files", "--cached", "--others", "--exclude-standard"], cwd)).stdout.split("\n").filter(Boolean);
1861
- const traceableDocs = async () => traceableFiles(cwd, { code: false });
1862
- const traceableFiles2 = async () => traceableFiles(cwd);
1863
- const tracerModel = () => {
2037
+ const traceableDocs = async (dir = cwd) => traceableFiles(dir, { code: false });
2038
+ const traceableFiles2 = async (dir = cwd) => traceableFiles(dir);
2039
+ const tracerChain = () => {
1864
2040
  for (const role of ["tracer", "architect", "senior-coder", "judge"]) {
1865
- const m = config.roles[role]?.models[0];
1866
- if (m && m !== "default") return m;
2041
+ const chain = (config.roles[role]?.models ?? []).filter((m) => m && m !== "default");
2042
+ if (chain.length) return chain;
1867
2043
  }
1868
- return config.model;
2044
+ return [config.model];
2045
+ };
2046
+ const derivedWorkdir = async (create) => {
2047
+ const inSession = sessionBase(cwd);
2048
+ if (inSession) return { dir: inSession };
2049
+ const standing = join12(cwd, ".horsecode", "worktrees", "traces", "base");
2050
+ if (!create) return { dir: existsSync4(standing) ? standing : cwd };
2051
+ const session = await manager.openFixed(fromBranch, "traces");
2052
+ return { dir: session.baseWorktree, session };
1869
2053
  };
1870
2054
  const planTracesFn = async () => {
1871
- const plan = await planFor(cwd, await traceableFiles2());
1872
- return { summary: describePlan(plan, tracerModel()), jobs: plan.jobs.length };
2055
+ const { dir } = await derivedWorkdir(false);
2056
+ const plan = await planFor(dir, await traceableFiles2(dir));
2057
+ return { summary: describePlan(plan, tracerChain()), jobs: plan.jobs.length };
1873
2058
  };
1874
2059
  const runTracesFn = async (onProgress, metered) => {
1875
- const files = await traceableFiles2();
1876
- const brief = await buildBrief({ cwd, provider: metered ?? provider, model: tracerModel(), files: await traceableDocs() });
1877
- const plan = await planFor(cwd, files);
2060
+ const { dir, session } = await derivedWorkdir(true);
2061
+ const files = await traceableFiles2(dir);
2062
+ const brief = await buildBrief({ cwd: dir, provider: metered ?? provider, models: tracerChain(), files: await traceableDocs(dir) });
2063
+ const plan = await planFor(dir, files);
1878
2064
  const res = await runTraces({
1879
- cwd,
2065
+ cwd: dir,
1880
2066
  provider: metered ?? provider,
1881
- model: tracerModel(),
2067
+ models: tracerChain(),
1882
2068
  plan,
1883
2069
  liveFiles: new Set(files),
1884
2070
  ...onProgress ? { onProgress } : {}
1885
2071
  });
2072
+ if (session) await manager.preserveSession(session, `docs(traces): ${res.written} file(s) described`);
1886
2073
  const bits = [`${brief.message}
1887
2074
 
1888
2075
  **Traces written: ${res.written}**`];
@@ -1890,9 +2077,13 @@ _Every agent can query it: \`graph_impact\` (blast radius), \`graph_trace\`, \`g
1890
2077
  if (res.pruned.length) bits.push(`${res.pruned.length} removed for deleted files`);
1891
2078
  if (res.wroteGitignore) bits.push("\n\n_Added .gitignore rules: traces are committed, the AST cache is not._");
1892
2079
  const failures = describeTraceFailures(res.failed);
2080
+ const landed = session ? `
2081
+
2082
+ _Written and committed on \`${session.baseBranch}\`, so your working tree is untouched._
2083
+ _Bring them in with \`git merge ${session.baseBranch}\`, or browse them at \`${session.baseWorktree}\`._` : "\n\n_Written in this session's worktree, on its own branch._";
1893
2084
  return `${bits.join(" \xB7 ")}${failures ? `
1894
2085
 
1895
- ${failures}` : ""}
2086
+ ${failures}` : ""}${landed}
1896
2087
 
1897
2088
  _Committed with the repo, so every clone starts with them. Agents read one with \`graph_trace\`._`;
1898
2089
  };
@@ -1901,11 +2092,13 @@ _Committed with the repo, so every clone starts with them. Agents read one with
1901
2092
  memStore,
1902
2093
  accountsNote,
1903
2094
  modelsPanel: modelsPanelNote,
2095
+ initProject: initProjectFn,
1904
2096
  listSkills,
1905
2097
  updateSkills,
1906
2098
  addSkill,
1907
2099
  reloadProjectSkills,
1908
2100
  graphStatus: graphStatusText,
2101
+ derivedDir: async () => (await derivedWorkdir(true)).dir,
1909
2102
  buildGraph: buildGraphText,
1910
2103
  cleanWorktrees: cleanWorktreesText,
1911
2104
  planTraces: planTracesFn,
@@ -1938,7 +2131,7 @@ _Committed with the repo, so every clone starts with them. Agents read one with
1938
2131
  ...args.revisionRounds !== void 0 && { revisionRounds: args.revisionRounds }
1939
2132
  };
1940
2133
  if (useTui) {
1941
- const { runTui } = await import("./app-5FXHE7GX.js");
2134
+ const { runTui } = await import("./app-KR7TAHUD.js");
1942
2135
  const res = await runTui({ buildDeps, job });
1943
2136
  console.log(renderResult(res));
1944
2137
  return;
@@ -8,18 +8,21 @@ import {
8
8
  dirtyPaths,
9
9
  runFix,
10
10
  runSmallChange
11
- } from "./chunk-YPZP7LYL.js";
12
- import "./chunk-UEWVVN5L.js";
11
+ } from "./chunk-UANNVVIU.js";
12
+ import "./chunk-PIG54WFU.js";
13
+ import "./chunk-NBTH2VVI.js";
13
14
  import "./chunk-ZSQ24YDJ.js";
15
+ import "./chunk-ACTVFJRW.js";
16
+ import "./chunk-MZM24M5M.js";
14
17
  import "./chunk-G45RWL7S.js";
15
- import "./chunk-MRZVA5JB.js";
16
18
  import "./chunk-LPQU436C.js";
17
- import "./chunk-LLL7QWXB.js";
19
+ import "./chunk-BFIZMM4G.js";
20
+ import "./chunk-CYLPQWIF.js";
18
21
  import "./chunk-63E73TGI.js";
19
- import "./chunk-AE36LLL2.js";
20
- import "./chunk-6OSEQOYY.js";
21
- import "./chunk-KAGKX2YT.js";
22
- import "./chunk-XEGQT5EN.js";
22
+ import "./chunk-6S4WWQMN.js";
23
+ import "./chunk-JLWQCA7B.js";
24
+ import "./chunk-ZPJP2VH5.js";
25
+ import "./chunk-4M6LXNG2.js";
23
26
  import "./chunk-6W4UH2BQ.js";
24
27
  export {
25
28
  FIX_ATTEMPTS,
@@ -3,12 +3,13 @@ import {
3
3
  } from "./chunk-YBWTCXUS.js";
4
4
  import {
5
5
  askInUserLanguage
6
- } from "./chunk-UGESK765.js";
6
+ } from "./chunk-UTHLEW5V.js";
7
7
  import {
8
8
  checkpointMtime,
9
9
  readCheckpoint
10
10
  } from "./chunk-ZSQ24YDJ.js";
11
- import "./chunk-AE36LLL2.js";
11
+ import "./chunk-CYLPQWIF.js";
12
+ import "./chunk-JLWQCA7B.js";
12
13
  import "./chunk-6W4UH2BQ.js";
13
14
 
14
15
  // src/engine/ongoing.ts
@@ -24,7 +24,7 @@ import {
24
24
  pruneTooling,
25
25
  readStamp,
26
26
  stampPath
27
- } from "./chunk-XEGQT5EN.js";
27
+ } from "./chunk-4M6LXNG2.js";
28
28
  import "./chunk-6W4UH2BQ.js";
29
29
  export {
30
30
  BUILD_IDLE_TIMEOUT_MS,