@hizliemre/horse-code 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/dist/{app-5FXHE7GX.js → app-4WN37LZ3.js} +65 -37
  2. package/dist/{chunk-UEWVVN5L.js → chunk-27F44PBD.js} +196 -1126
  3. package/dist/{chunk-XYZVZPAY.js → chunk-2WXG35EM.js} +19 -15
  4. package/dist/{chunk-LNW557IO.js → chunk-372X5HHU.js} +2 -2
  5. package/dist/{chunk-YPZP7LYL.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-AE36LLL2.js → chunk-JLWQCA7B.js} +2 -209
  11. package/dist/{run-P6ZYL5JL.js → chunk-QJYVZPLG.js} +133 -389
  12. package/dist/{chunk-UGESK765.js → chunk-UTHLEW5V.js} +1 -1
  13. package/dist/chunk-YULQ4URQ.js +1220 -0
  14. package/dist/{chunk-KAGKX2YT.js → chunk-ZPJP2VH5.js} +10 -1
  15. package/dist/cli.js +254 -66
  16. package/dist/{fix-ONLA45HD.js → fix-QCL5AITT.js} +9 -8
  17. package/dist/{ongoing-WHYXPW24.js → ongoing-6NUSPSCV.js} +3 -2
  18. package/dist/{project-graph-5HNPRFQG.js → project-graph-OGIM2B33.js} +1 -1
  19. package/dist/run-V5ZLZ3LS.js +274 -0
  20. package/dist/{save-skills-ZW5GY6KV.js → save-skills-NPKTYNAF.js} +2 -2
  21. package/dist/{trace-X6TU3AG6.js → trace-UVMZZRA5.js} +1 -1
  22. package/dist/{trace-adopt-URECQWJV.js → trace-adopt-7HWELJFE.js} +1 -1
  23. package/dist/{trace-run-7U4WJZ3V.js → trace-run-CZWEZ4R6.js} +8 -4
  24. package/dist/{triage-FCYHD2AQ.js → triage-IFCVL5MA.js} +7 -6
  25. package/dist/{verify-LC57A6H2.js → verify-HWZBTK5X.js} +15 -12
  26. package/package.json +1 -1
  27. package/dist/chunk-MRZVA5JB.js +0 -163
  28. package/dist/{chunk-EAF22QIG.js → chunk-JR2JLRE3.js} +3 -3
@@ -298,7 +298,16 @@ var LOCAL_ONLY = [
298
298
  // The graph itself: rebuilt per checkout, inherited by copy, and unmergeable by construction — see above.
299
299
  "graphify-out/graph.json",
300
300
  // …and the commit it was built at, which describes that local copy and travels with it.
301
- "graphify-out/.graph-commit.json"
301
+ "graphify-out/.graph-commit.json",
302
+ /**
303
+ * The AST cache — which this list's own heading has always described and never actually excluded.
304
+ *
305
+ * "an AST cache keyed by local mtimes" is the sentence written above these rules, and no rule matched it.
306
+ * Measured on a real project: 711 files and 7.8 MB sitting untracked and un-ignored, so `git add -A` after
307
+ * a `/graph build` swept the whole cache into the commit. Keyed by this checkout's mtimes, it is worthless
308
+ * to anyone else and rebuilt whenever it is missing.
309
+ */
310
+ "graphify-out/cache/"
302
311
  ];
303
312
  var NESTED_CHECKOUTS = [".claude/worktrees/", ".horsecode/worktrees/"];
304
313
  function openDirectory(text, dir) {
package/dist/cli.js CHANGED
@@ -15,31 +15,25 @@ import {
15
15
  restoreTerminal,
16
16
  runJob,
17
17
  sttySane
18
- } from "./chunk-XYZVZPAY.js";
18
+ } from "./chunk-2WXG35EM.js";
19
19
  import "./chunk-M2RKCIGV.js";
20
20
  import {
21
- CODE_TEAM,
22
21
  CliProvider,
23
- DEFAULT_COUNCIL,
24
- DEFAULT_PROMPTS,
25
- DEFAULT_ROLE_SKILLS,
26
- PLAN_TEAM,
27
- REQUIRED_ROLES,
28
22
  RoleFitness,
29
- RoleRegistry,
30
23
  SHORT_CALL_MS,
31
- SPEC_TEAM,
32
24
  WorktreeManager,
33
- ZAI_MODELS,
34
25
  buildCouncilRegistry,
35
26
  buildTeamRegistry,
36
- cliCatalog,
37
27
  mainWorktreeRoot,
38
- modelsFor,
39
28
  toSlug
40
- } from "./chunk-UEWVVN5L.js";
29
+ } from "./chunk-27F44PBD.js";
41
30
  import "./chunk-QF4MP6BS.js";
42
- import "./chunk-UGESK765.js";
31
+ import {
32
+ MAX_CHUNK_CHARS,
33
+ discover,
34
+ readFinding
35
+ } from "./chunk-QJYVZPLG.js";
36
+ import "./chunk-UTHLEW5V.js";
43
37
  import "./chunk-ZSQ24YDJ.js";
44
38
  import {
45
39
  parseFrontmatter
@@ -48,58 +42,75 @@ import {
48
42
  UNSET_MODEL,
49
43
  loadConfig,
50
44
  saveSkillSource
51
- } from "./chunk-EAF22QIG.js";
52
- import {
53
- CLI_KINDS,
54
- checkProfile,
55
- runLogin
56
- } from "./chunk-G45RWL7S.js";
45
+ } from "./chunk-JR2JLRE3.js";
57
46
  import "./chunk-H2FDGPVW.js";
58
47
  import {
48
+ CODE_TEAM,
49
+ DEFAULT_COUNCIL,
50
+ DEFAULT_PROMPTS,
51
+ DEFAULT_ROLE_SKILLS,
52
+ PLAN_TEAM,
53
+ REQUIRED_ROLES,
54
+ RoleRegistry,
55
+ SPEC_TEAM,
56
+ ZAI_MODELS,
59
57
  buildBrief,
58
+ cliCatalog,
60
59
  describePlan,
60
+ modelsFor,
61
61
  planFor,
62
62
  runTraces,
63
63
  traceableFiles
64
- } from "./chunk-MRZVA5JB.js";
64
+ } from "./chunk-YULQ4URQ.js";
65
+ import {
66
+ CLI_KINDS,
67
+ checkProfile,
68
+ runLogin
69
+ } from "./chunk-G45RWL7S.js";
65
70
  import {
66
71
  defaultGitRunner
67
72
  } from "./chunk-LPQU436C.js";
68
73
  import {
69
74
  InjectionLog,
70
75
  memoryNote
71
- } from "./chunk-LLL7QWXB.js";
76
+ } from "./chunk-BFIZMM4G.js";
72
77
  import "./chunk-63E73TGI.js";
78
+ import {
79
+ ToolRegistry
80
+ } from "./chunk-CYLPQWIF.js";
81
+ import {
82
+ briefStatus
83
+ } from "./chunk-6S4WWQMN.js";
73
84
  import {
74
85
  Telemetry,
75
- ToolRegistry,
76
86
  clearPerfMarks,
77
87
  sampleMemory,
78
88
  setTelemetry,
79
89
  stripThinking,
80
90
  telemetry,
81
91
  writeHeapSnapshot
82
- } from "./chunk-AE36LLL2.js";
83
- import {
84
- briefStatus
85
- } from "./chunk-6OSEQOYY.js";
92
+ } from "./chunk-JLWQCA7B.js";
86
93
  import {
87
94
  discoverTraceRoot,
88
- setTraceRoot
89
- } from "./chunk-KAGKX2YT.js";
95
+ ensureGitignore,
96
+ localOnly,
97
+ setTraceRoot,
98
+ sharedDerived,
99
+ traceRootRel
100
+ } from "./chunk-ZPJP2VH5.js";
90
101
  import {
91
102
  buildProjectGraph,
92
103
  graphStatus,
93
104
  graphifyPython
94
- } from "./chunk-XEGQT5EN.js";
105
+ } from "./chunk-4M6LXNG2.js";
95
106
  import {
96
107
  sessionBase
97
108
  } from "./chunk-6W4UH2BQ.js";
98
109
 
99
110
  // src/cli.ts
100
111
  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";
112
+ import { readFileSync as readFileSync5, existsSync as existsSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, realpathSync, rmSync, statSync as statSync2 } from "fs";
113
+ import { join as join12, dirname as dirname5 } from "path";
103
114
  import { pathToFileURL } from "url";
104
115
 
105
116
  // src/agents/cli-accounts.ts
@@ -694,6 +705,137 @@ async function syncSkillSources(home, sources, deps = {}) {
694
705
  return { ok, failed };
695
706
  }
696
707
 
708
+ // src/engine/init-project.ts
709
+ import { existsSync as existsSync2, statSync, readdirSync } from "fs";
710
+ import { join as join7 } from "path";
711
+
712
+ // src/migrate/project-docs.ts
713
+ import { basename } from "path";
714
+ var DERIVED = [
715
+ { 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" },
716
+ { re: /^graphify-out\//, why: "generated by the graph build" },
717
+ { re: /(^|\/)(node_modules|vendor|third_party|\.git)\//, why: "not this project's writing" },
718
+ { re: /(^|\/)\.claude\/worktrees\//, why: "a nested checkout of this repository" }
719
+ ];
720
+ var HISTORY = [
721
+ { re: /(^|\/)archive(d)?\//i, why: "an archive of finished work" },
722
+ { re: /\d{4}-\d{2}-\d{2}/, why: "dated \u2014 a record of a moment, not a standing rule" },
723
+ { re: /(^|\/)(HANDOFF|RESUME|CHANGELOG|RELEASE[-_]NOTES)[^/]*$/i, why: "a record of what happened" },
724
+ { re: /(^|\/)chats?\//i, why: "a pasted conversation" },
725
+ { re: /(^|\/)chat\d*\.md$/i, why: "a pasted conversation" }
726
+ ];
727
+ function sortProjectDocs(files, known = []) {
728
+ const seen = new Set(known.map((k) => k.replace(/\\/g, "/")));
729
+ const candidates2 = [];
730
+ const skipped = [];
731
+ for (const f of files) {
732
+ const path = f.path.replace(/\\/g, "/");
733
+ if (!/\.mdx?$/i.test(path)) continue;
734
+ if (seen.has(path) || seen.has(basename(path))) continue;
735
+ const why = DERIVED.find((d) => d.re.test(path))?.why ?? HISTORY.find((h) => h.re.test(path))?.why;
736
+ if (why) skipped.push({ path, bytes: f.bytes, skipped: why });
737
+ else candidates2.push({ path, bytes: f.bytes });
738
+ }
739
+ const bySize = (a, b) => b.bytes - a.bytes;
740
+ return { candidates: candidates2.sort(bySize), skipped: skipped.sort(bySize) };
741
+ }
742
+ function totalBytes(docs) {
743
+ return docs.reduce((n, d) => n + d.bytes, 0);
744
+ }
745
+ function classifyCalls(bytes, chunk) {
746
+ return Math.max(1, Math.ceil(bytes / chunk));
747
+ }
748
+
749
+ // src/engine/init-project.ts
750
+ var UNCLAIMED_FILE_FLOOR = 50;
751
+ function countFiles(dir, cap = UNCLAIMED_FILE_FLOOR * 4) {
752
+ let n = 0;
753
+ const walk = (d) => {
754
+ if (n >= cap) return;
755
+ let entries;
756
+ try {
757
+ entries = readdirSync(d, { withFileTypes: true });
758
+ } catch {
759
+ return;
760
+ }
761
+ for (const e of entries) {
762
+ if (n >= cap) return;
763
+ if (e.isDirectory()) walk(join7(d, e.name));
764
+ else n++;
765
+ }
766
+ };
767
+ walk(dir);
768
+ return n;
769
+ }
770
+ async function initProject(cwd, isIgnored, untracked, knowledge) {
771
+ const changed = await ensureGitignore(cwd);
772
+ const unclaimed = [];
773
+ for (const rel of untracked()) {
774
+ const path = join7(cwd, rel);
775
+ let dir = false;
776
+ try {
777
+ dir = statSync(path).isDirectory();
778
+ } catch {
779
+ continue;
780
+ }
781
+ if (!dir || isIgnored(rel)) continue;
782
+ const files = countFiles(path);
783
+ if (files >= UNCLAIMED_FILE_FLOOR) unclaimed.push({ path: rel, files });
784
+ }
785
+ return {
786
+ changed,
787
+ kept: [`${traceRootRel().replace(/\\/g, "/")}/`, ...sharedDerived()].filter((p) => existsSync2(join7(cwd, p)) || p.endsWith("/")),
788
+ excluded: localOnly(),
789
+ unclaimed: unclaimed.sort((a, b) => b.files - a.files),
790
+ ...knowledge ? { knowledge } : {}
791
+ };
792
+ }
793
+ function hasKnowledge(k) {
794
+ return !!k && (k.named.length > 0 || k.docs.length > 0);
795
+ }
796
+ function size(bytes) {
797
+ return bytes >= 1024 * 1024 ? `${(bytes / 1024 / 1024).toFixed(1)} MB` : `${Math.max(1, Math.round(bytes / 1024))} KB`;
798
+ }
799
+ function describeKnowledge(k, chunk = MAX_CHUNK_CHARS) {
800
+ const lines = ["", "**This project already says things about itself.**", ""];
801
+ if (k.named.length) {
802
+ lines.push(`Files a tool is known to write \u2014 ${size(totalBytes(k.named.map((n) => ({ path: n.label, bytes: n.bytes }))))}:`);
803
+ lines.push(...k.named.map((n) => ` \xB7 \`${n.label}\` (${size(n.bytes)})`));
804
+ }
805
+ if (k.docs.length) {
806
+ const bytes = totalBytes(k.docs);
807
+ lines.push("", `The project's other markdown \u2014 ${k.docs.length} file(s), ${size(bytes)}, about ${classifyCalls(bytes, chunk)} classification call(s):`);
808
+ lines.push(...k.docs.slice(0, 8).map((d) => ` \xB7 \`${d.path}\` (${size(d.bytes)})`));
809
+ if (k.docs.length > 8) lines.push(` \xB7 \u2026and ${k.docs.length - 8} more`);
810
+ }
811
+ if (k.skipped.length) {
812
+ const reasons = /* @__PURE__ */ new Map();
813
+ for (const sk of k.skipped) reasons.set(sk.skipped ?? "", (reasons.get(sk.skipped ?? "") ?? 0) + 1);
814
+ lines.push("", `Set aside \u2014 ${k.skipped.length} file(s), ${size(totalBytes(k.skipped))}:`);
815
+ for (const [why, n] of [...reasons].sort((a, b) => b[1] - a[1])) lines.push(` \xB7 ${n} \xD7 ${why}`);
816
+ }
817
+ return lines.join("\n");
818
+ }
819
+ function describeInit(report) {
820
+ const lines = [
821
+ report.changed ? "**Project set up for horse-code** \u2014 `.gitignore` updated." : "**Project is already set up** \u2014 `.gitignore` already says everything needed.",
822
+ "",
823
+ "Committed on purpose, because a clone would otherwise pay to produce it again:",
824
+ ...report.kept.map((p) => ` \xB7 \`${p}\``),
825
+ "",
826
+ "Kept out on purpose \u2014 derived from the source, keyed to this checkout, or unmergeable:",
827
+ ...report.excluded.map((p) => ` \xB7 \`${p}\``)
828
+ ];
829
+ if (report.unclaimed.length) {
830
+ lines.push(
831
+ "",
832
+ "Untracked and covered by no rule \u2014 `git add -A` would commit these. Yours to decide:",
833
+ ...report.unclaimed.map((u) => ` \xB7 \`${u.path}\` (${u.files >= UNCLAIMED_FILE_FLOOR * 4 ? `${u.files}+` : u.files} files)`)
834
+ );
835
+ }
836
+ return lines.join("\n");
837
+ }
838
+
697
839
  // src/permission/rules.ts
698
840
  import picomatch from "picomatch";
699
841
  var DANGEROUS_PATTERNS = [
@@ -964,8 +1106,8 @@ var ProposalQueue = class {
964
1106
  };
965
1107
 
966
1108
  // 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";
1109
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
1110
+ import { join as join8, dirname as dirname3 } from "path";
969
1111
  var TEMPLATE_FILES = {
970
1112
  spec: "spec-template.md",
971
1113
  plan: "plan-template.md",
@@ -988,10 +1130,10 @@ async function loadSpecKit(opts) {
988
1130
  );
989
1131
  }
990
1132
  const fetchFn = opts.fetch ?? globalThis.fetch;
991
- const cacheDir = join7(opts.home, ".horsecode", "spec-kit", opts.version, "templates");
1133
+ const cacheDir = join8(opts.home, ".horsecode", "spec-kit", opts.version, "templates");
992
1134
  const get = async (relPath) => {
993
- const cachePath = join7(cacheDir, relPath);
994
- if (existsSync2(cachePath)) return readFileSync3(cachePath, "utf8");
1135
+ const cachePath = join8(cacheDir, relPath);
1136
+ if (existsSync3(cachePath)) return readFileSync3(cachePath, "utf8");
995
1137
  const url = `${RAW_BASE}/${opts.version}/templates/${relPath}`;
996
1138
  const res = await fetchFn(url);
997
1139
  if (!res.ok) {
@@ -1018,7 +1160,7 @@ Check your network or set specKit.version to a valid tag.`
1018
1160
 
1019
1161
  // src/wiring.ts
1020
1162
  import { homedir } from "os";
1021
- import { join as join8 } from "path";
1163
+ import { join as join9 } from "path";
1022
1164
  async function buildJobDeps(opts) {
1023
1165
  const { config } = opts;
1024
1166
  const roles = {};
@@ -1064,7 +1206,7 @@ async function buildJobDeps(opts) {
1064
1206
  const queue = new ProposalQueue();
1065
1207
  let kitPromise;
1066
1208
  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"));
1209
+ const fitness = new RoleFitness(join9(opts.home ?? homedir(), ".horsecode", "model-fitness.json"));
1068
1210
  roleRegistry.setFitness(fitness);
1069
1211
  return {
1070
1212
  provider: opts.provider,
@@ -1257,9 +1399,9 @@ function redactRecord(record) {
1257
1399
 
1258
1400
  // src/obs/sink.ts
1259
1401
  import { createWriteStream, mkdirSync as mkdirSync4 } from "fs";
1260
- import { join as join9 } from "path";
1402
+ import { join as join10 } from "path";
1261
1403
  function telemetryDir(home) {
1262
- return join9(home, ".horsecode", "telemetry");
1404
+ return join10(home, ".horsecode", "telemetry");
1263
1405
  }
1264
1406
  var MAX_QUEUE = 1e4;
1265
1407
  var FileSink = class {
@@ -1268,7 +1410,7 @@ var FileSink = class {
1268
1410
  dropped = 0;
1269
1411
  path;
1270
1412
  constructor(home, runId) {
1271
- this.path = join9(telemetryDir(home), `${runId}.jsonl`);
1413
+ this.path = join10(telemetryDir(home), `${runId}.jsonl`);
1272
1414
  try {
1273
1415
  mkdirSync4(telemetryDir(home), { recursive: true });
1274
1416
  this.stream = createWriteStream(this.path, { flags: "a" });
@@ -1328,12 +1470,12 @@ var FileSink = class {
1328
1470
 
1329
1471
  // src/cli-info.ts
1330
1472
  import { readFileSync as readFileSync4 } from "fs";
1331
- import { dirname as dirname4, join as join10 } from "path";
1473
+ import { dirname as dirname4, join as join11 } from "path";
1332
1474
  import { fileURLToPath as fileURLToPath2 } from "url";
1333
1475
  function hcodeVersion() {
1334
1476
  try {
1335
1477
  const here = dirname4(fileURLToPath2(import.meta.url));
1336
- const pkg = JSON.parse(readFileSync4(join10(here, "..", "package.json"), "utf8"));
1478
+ const pkg = JSON.parse(readFileSync4(join11(here, "..", "package.json"), "utf8"));
1337
1479
  return pkg.version ?? "unknown";
1338
1480
  } catch {
1339
1481
  return "unknown";
@@ -1719,14 +1861,43 @@ async function main(argv) {
1719
1861
  );
1720
1862
  const notSignedIn = ambientLogins.filter((a) => !a.status.loggedIn).map((a) => a.kind);
1721
1863
  const accountsNote = () => accountsLine(accounts.usage(), notSignedIn);
1864
+ const initProjectFn = async () => {
1865
+ const r = await defaultGitRunner(["ls-files", "--others", "--exclude-standard", "--directory"], cwd);
1866
+ const list = r.stdout.split("\n").map((l) => l.trim()).filter(Boolean);
1867
+ const ignored = /* @__PURE__ */ new Set();
1868
+ for (const path of list) {
1869
+ if ((await defaultGitRunner(["check-ignore", "-q", path], cwd)).code === 0) ignored.add(path);
1870
+ }
1871
+ const all = await defaultGitRunner(["ls-files", "--cached", "--others", "--exclude-standard"], cwd);
1872
+ const md = all.stdout.split("\n").map((l) => l.trim()).filter((l) => /\.mdx?$/i.test(l));
1873
+ const withSize = md.map((path) => {
1874
+ try {
1875
+ return { path, bytes: statSync2(join12(cwd, path)).size };
1876
+ } catch {
1877
+ return { path, bytes: 0 };
1878
+ }
1879
+ });
1880
+ const found = await discover({ cwd, home });
1881
+ const named = found.filter((f) => f.kind === "rules" || f.kind === "mcp").map((f) => ({ label: f.label, bytes: f.bytes }));
1882
+ const { candidates: candidates2, skipped } = sortProjectDocs(withSize, found.map((f) => f.label));
1883
+ const knowledge = { named, docs: candidates2, skipped };
1884
+ const report = await initProject(cwd, (path) => ignored.has(path), () => list, knowledge);
1885
+ const canImport = hasKnowledge(knowledge);
1886
+ const extra = [];
1887
+ for (const d of candidates2) {
1888
+ const r2 = await readFinding(join12(cwd, d.path));
1889
+ 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 });
1890
+ }
1891
+ return { text: describeInit(report) + (canImport ? describeKnowledge(knowledge) : ""), canImport, extra };
1892
+ };
1722
1893
  const modelsPanelNote = (inUse) => modelsPanel(accounts.usage(), modelsFor, CLI_KINDS, inUse);
1723
1894
  const raw = new CliProvider({ readOnly: false, accounts });
1724
1895
  const provider = config.telemetry ? telemetryProvider(raw, telemetry()) : raw;
1725
1896
  const skillRegistry = new SkillRegistry();
1726
1897
  await registerBuiltinSkills(skillRegistry);
1727
1898
  await skillRegistry.loadFromDir(externalSkillsDir(home));
1728
- const skillsDir = join11(cwd, ".horsecode", "skills");
1729
- if (existsSync3(skillsDir)) await skillRegistry.loadFromDir(skillsDir);
1899
+ const skillsDir = join12(cwd, ".horsecode", "skills");
1900
+ if (existsSync4(skillsDir)) await skillRegistry.loadFromDir(skillsDir);
1730
1901
  const worktreeHome = await mainWorktreeRoot(defaultGitRunner, cwd);
1731
1902
  if (worktreeHome !== cwd) {
1732
1903
  console.log(`\u2139\uFE0F Sessions will be opened in ${worktreeHome} \u2014 the repository's main checkout.`);
@@ -1758,7 +1929,7 @@ async function main(argv) {
1758
1929
  const useTui = shouldUseTui(!!process.stdin.isTTY, !!process.stdout.isTTY, !!args.noTui);
1759
1930
  if (!args.prompt) {
1760
1931
  if (useTui) {
1761
- const { runTuiRepl } = await import("./app-5FXHE7GX.js");
1932
+ const { runTuiRepl } = await import("./app-4WN37LZ3.js");
1762
1933
  const { fetchCatalog, makeProbe, discoverSources } = await import("./discover-G2Z6XC3O.js");
1763
1934
  const { loadSourceCache, saveSourceCache } = await import("./source-cache-XEK5WN7I.js");
1764
1935
  const manualSources = config.modelSources.length > 0;
@@ -1809,8 +1980,8 @@ _Expected something like \`https://github.com/owner/repo\` or a link to the dire
1809
1980
  _Discoverable by every agent. \`/roles adjust\` assigns it to the roles it fits; \`/skills update\` re-installs it from upstream._`;
1810
1981
  };
1811
1982
  const reloadProjectSkills = async () => {
1812
- const dir = join11(cwd, ".horsecode", "skills");
1813
- if (existsSync3(dir)) await skillRegistry.loadFromDir(dir);
1983
+ const dir = join12(cwd, ".horsecode", "skills");
1984
+ if (existsSync4(dir)) await skillRegistry.loadFromDir(dir);
1814
1985
  };
1815
1986
  const updateSkills = async () => {
1816
1987
  if (!config.skillSources.length) {
@@ -1829,7 +2000,7 @@ _Discoverable by every agent. \`/roles adjust\` assigns it to the roles it fits;
1829
2000
  ${lines.join("\n")}${tail}`;
1830
2001
  };
1831
2002
  const graphStatusText = async () => {
1832
- const st = await graphStatus(cwd);
2003
+ const st = await graphStatus((await derivedWorkdir(false)).dir);
1833
2004
  if (!st.built) {
1834
2005
  const py = await graphifyPython();
1835
2006
  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 +2019,7 @@ ${lines.join("\n")}${tail}`;
1848
2019
 
1849
2020
  _Every agent can query it: \`graph_impact\` (blast radius), \`graph_trace\`, \`graph_find\`, \`graph_context\`, \`graph_overview\`._`;
1850
2021
  };
1851
- const buildGraphText = async () => (await buildProjectGraph(cwd)).message;
2022
+ const buildGraphText = async () => (await buildProjectGraph((await derivedWorkdir(true)).dir)).message;
1852
2023
  const cleanWorktreesText = async (apply, branch) => {
1853
2024
  const { surveySessions, cleanSessions, describeSurvey, describeClean } = await import("./clean-YOQATBMZ.js");
1854
2025
  const target = branch?.trim() || fromBranch;
@@ -1858,31 +2029,42 @@ _Every agent can query it: \`graph_impact\` (blast radius), \`graph_trace\`, \`g
1858
2029
  return describeClean(await cleanSessions(defaultGitRunner, cwd, target), target);
1859
2030
  };
1860
2031
  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 = () => {
2032
+ const traceableDocs = async (dir = cwd) => traceableFiles(dir, { code: false });
2033
+ const traceableFiles2 = async (dir = cwd) => traceableFiles(dir);
2034
+ const tracerChain = () => {
1864
2035
  for (const role of ["tracer", "architect", "senior-coder", "judge"]) {
1865
- const m = config.roles[role]?.models[0];
1866
- if (m && m !== "default") return m;
2036
+ const chain = (config.roles[role]?.models ?? []).filter((m) => m && m !== "default");
2037
+ if (chain.length) return chain;
1867
2038
  }
1868
- return config.model;
2039
+ return [config.model];
2040
+ };
2041
+ const derivedWorkdir = async (create) => {
2042
+ const inSession = sessionBase(cwd);
2043
+ if (inSession) return { dir: inSession };
2044
+ const standing = join12(cwd, ".horsecode", "worktrees", "traces", "base");
2045
+ if (!create) return { dir: existsSync4(standing) ? standing : cwd };
2046
+ const session = await manager.openFixed(fromBranch, "traces");
2047
+ return { dir: session.baseWorktree, session };
1869
2048
  };
1870
2049
  const planTracesFn = async () => {
1871
- const plan = await planFor(cwd, await traceableFiles2());
1872
- return { summary: describePlan(plan, tracerModel()), jobs: plan.jobs.length };
2050
+ const { dir } = await derivedWorkdir(false);
2051
+ const plan = await planFor(dir, await traceableFiles2(dir));
2052
+ return { summary: describePlan(plan, tracerChain()), jobs: plan.jobs.length };
1873
2053
  };
1874
2054
  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);
2055
+ const { dir, session } = await derivedWorkdir(true);
2056
+ const files = await traceableFiles2(dir);
2057
+ const brief = await buildBrief({ cwd: dir, provider: metered ?? provider, models: tracerChain(), files: await traceableDocs(dir) });
2058
+ const plan = await planFor(dir, files);
1878
2059
  const res = await runTraces({
1879
- cwd,
2060
+ cwd: dir,
1880
2061
  provider: metered ?? provider,
1881
- model: tracerModel(),
2062
+ models: tracerChain(),
1882
2063
  plan,
1883
2064
  liveFiles: new Set(files),
1884
2065
  ...onProgress ? { onProgress } : {}
1885
2066
  });
2067
+ if (session) await manager.preserveSession(session, `docs(traces): ${res.written} file(s) described`);
1886
2068
  const bits = [`${brief.message}
1887
2069
 
1888
2070
  **Traces written: ${res.written}**`];
@@ -1890,9 +2072,13 @@ _Every agent can query it: \`graph_impact\` (blast radius), \`graph_trace\`, \`g
1890
2072
  if (res.pruned.length) bits.push(`${res.pruned.length} removed for deleted files`);
1891
2073
  if (res.wroteGitignore) bits.push("\n\n_Added .gitignore rules: traces are committed, the AST cache is not._");
1892
2074
  const failures = describeTraceFailures(res.failed);
2075
+ const landed = session ? `
2076
+
2077
+ _Written and committed on \`${session.baseBranch}\`, so your working tree is untouched._
2078
+ _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
2079
  return `${bits.join(" \xB7 ")}${failures ? `
1894
2080
 
1895
- ${failures}` : ""}
2081
+ ${failures}` : ""}${landed}
1896
2082
 
1897
2083
  _Committed with the repo, so every clone starts with them. Agents read one with \`graph_trace\`._`;
1898
2084
  };
@@ -1901,11 +2087,13 @@ _Committed with the repo, so every clone starts with them. Agents read one with
1901
2087
  memStore,
1902
2088
  accountsNote,
1903
2089
  modelsPanel: modelsPanelNote,
2090
+ initProject: initProjectFn,
1904
2091
  listSkills,
1905
2092
  updateSkills,
1906
2093
  addSkill,
1907
2094
  reloadProjectSkills,
1908
2095
  graphStatus: graphStatusText,
2096
+ derivedDir: async () => (await derivedWorkdir(true)).dir,
1909
2097
  buildGraph: buildGraphText,
1910
2098
  cleanWorktrees: cleanWorktreesText,
1911
2099
  planTraces: planTracesFn,
@@ -1938,7 +2126,7 @@ _Committed with the repo, so every clone starts with them. Agents read one with
1938
2126
  ...args.revisionRounds !== void 0 && { revisionRounds: args.revisionRounds }
1939
2127
  };
1940
2128
  if (useTui) {
1941
- const { runTui } = await import("./app-5FXHE7GX.js");
2129
+ const { runTui } = await import("./app-4WN37LZ3.js");
1942
2130
  const res = await runTui({ buildDeps, job });
1943
2131
  console.log(renderResult(res));
1944
2132
  return;
@@ -8,18 +8,19 @@ import {
8
8
  dirtyPaths,
9
9
  runFix,
10
10
  runSmallChange
11
- } from "./chunk-YPZP7LYL.js";
12
- import "./chunk-UEWVVN5L.js";
11
+ } from "./chunk-3ACDNDCG.js";
12
+ import "./chunk-27F44PBD.js";
13
13
  import "./chunk-ZSQ24YDJ.js";
14
+ import "./chunk-YULQ4URQ.js";
14
15
  import "./chunk-G45RWL7S.js";
15
- import "./chunk-MRZVA5JB.js";
16
16
  import "./chunk-LPQU436C.js";
17
- import "./chunk-LLL7QWXB.js";
17
+ import "./chunk-BFIZMM4G.js";
18
18
  import "./chunk-63E73TGI.js";
19
- import "./chunk-AE36LLL2.js";
20
- import "./chunk-6OSEQOYY.js";
21
- import "./chunk-KAGKX2YT.js";
22
- import "./chunk-XEGQT5EN.js";
19
+ import "./chunk-CYLPQWIF.js";
20
+ import "./chunk-6S4WWQMN.js";
21
+ import "./chunk-JLWQCA7B.js";
22
+ import "./chunk-ZPJP2VH5.js";
23
+ import "./chunk-4M6LXNG2.js";
23
24
  import "./chunk-6W4UH2BQ.js";
24
25
  export {
25
26
  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,