@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
@@ -5,10 +5,8 @@ import {
5
5
  Board,
6
6
  LONG_CALL_MS,
7
7
  WHAT_IT_COST,
8
- applySkills,
9
8
  buildAskUserTool,
10
9
  buildRememberTool,
11
- buildSkillTool,
12
10
  changedByMerge,
13
11
  commitFile,
14
12
  commitStep,
@@ -22,7 +20,6 @@ import {
22
20
  hasWorkAgainst,
23
21
  nextFeatureSlug,
24
22
  normalizeQuestion,
25
- placedSkills,
26
23
  readOnlyRegistry,
27
24
  refreshAfterChange,
28
25
  routeSkills,
@@ -37,20 +34,25 @@ import {
37
34
  worktreeState,
38
35
  writeFileTool,
39
36
  writerRegistry
40
- } from "./chunk-7JMWPTJ5.js";
37
+ } from "./chunk-27F44PBD.js";
41
38
  import {
42
39
  resolveMainBranch
43
40
  } from "./chunk-QF4MP6BS.js";
44
41
  import {
45
42
  askInUserLanguage,
46
43
  inUserLanguage
47
- } from "./chunk-UGESK765.js";
44
+ } from "./chunk-UTHLEW5V.js";
48
45
  import {
49
46
  clearCheckpoint,
50
47
  isContinuePrompt,
51
48
  readCheckpoint,
52
49
  writeCheckpoint
53
50
  } from "./chunk-ZSQ24YDJ.js";
51
+ import {
52
+ applySkills,
53
+ buildSkillTool,
54
+ placedSkills
55
+ } from "./chunk-YULQ4URQ.js";
54
56
  import {
55
57
  defaultGitRunner
56
58
  } from "./chunk-LPQU436C.js";
@@ -72,25 +74,27 @@ import {
72
74
  relationStrength,
73
75
  supersedes,
74
76
  verifyAnchors
75
- } from "./chunk-LLL7QWXB.js";
77
+ } from "./chunk-BFIZMM4G.js";
76
78
  import {
77
79
  ToolRegistry,
80
+ runStructuredRole
81
+ } from "./chunk-CYLPQWIF.js";
82
+ import {
78
83
  handedOver,
79
- runStructuredRole,
80
84
  runToCompletion,
81
85
  stripThinking,
82
86
  telemetry
83
- } from "./chunk-AE36LLL2.js";
87
+ } from "./chunk-JLWQCA7B.js";
84
88
  import {
85
89
  TRACE_INDEX,
86
90
  mergeTraceIndexes,
87
91
  parseTraceIndex,
88
92
  serializeTraceIndex,
89
93
  traceRootRel
90
- } from "./chunk-KAGKX2YT.js";
94
+ } from "./chunk-ZPJP2VH5.js";
91
95
  import {
92
96
  loadGraphSync
93
- } from "./chunk-XEGQT5EN.js";
97
+ } from "./chunk-4M6LXNG2.js";
94
98
  import {
95
99
  inLinkedWorktree,
96
100
  sessionBase,
@@ -3217,7 +3221,7 @@ async function runUpstream(deps, ensureWorktree, prompt, askUser, maxRounds, his
3217
3221
  emitPhase("verify");
3218
3222
  const cwd = await documentWorkdir(process.cwd(), prompt, ensureWorktree, r.title);
3219
3223
  laneCheckpoint(cwd, "verify", resume, prompt, r);
3220
- const { runVerify, describeVerify, currentBranchOf } = await import("./verify-6SC4I77M.js");
3224
+ const { runVerify, describeVerify, currentBranchOf } = await import("./verify-HWZBTK5X.js");
3221
3225
  const branch = await currentBranchOf(cwd);
3222
3226
  const res = await runVerify({
3223
3227
  deps,
@@ -3260,11 +3264,11 @@ async function runUpstream(deps, ensureWorktree, prompt, askUser, maxRounds, his
3260
3264
  if (!resume && !hasPreservedWork && routeIntent(r.intent) === "pipeline") {
3261
3265
  const cwd = workingIn?.() ?? process.cwd();
3262
3266
  emitPhase("sizing");
3263
- const { sizeRequest } = await import("./triage-FCYHD2AQ.js");
3267
+ const { sizeRequest } = await import("./triage-IFCVL5MA.js");
3264
3268
  const size = await sizeRequest(deps, cwd, r.refinedPrompt);
3265
3269
  let small = size.verdict === "small";
3266
3270
  if (size.verdict === "unsure") {
3267
- const { describeSizeDoubt } = await import("./triage-FCYHD2AQ.js");
3271
+ const { describeSizeDoubt } = await import("./triage-IFCVL5MA.js");
3268
3272
  const answer = await askInUserLanguage(
3269
3273
  deps,
3270
3274
  askUser,
@@ -3282,8 +3286,8 @@ Which is it?`,
3282
3286
  if (small) {
3283
3287
  emitPhase("small change");
3284
3288
  emit({ kind: "note", text: `\u26A1 Small change \u2014 ${size.reason}. No branch, no spec, no plan.` });
3285
- const { runSmallChange, describeSmallChange } = await import("./fix-JOIXQFVP.js");
3286
- const { currentBranchOf } = await import("./verify-6SC4I77M.js");
3289
+ const { runSmallChange, describeSmallChange } = await import("./fix-QCL5AITT.js");
3290
+ const { currentBranchOf } = await import("./verify-HWZBTK5X.js");
3287
3291
  const res = await runSmallChange(deps, cwd, r.title, r.refinedPrompt, size);
3288
3292
  return {
3289
3293
  intent: r.intent,
@@ -5,11 +5,11 @@ import {
5
5
  memoryHints,
6
6
  readFileTool,
7
7
  reinforceUsed
8
- } from "./chunk-LLL7QWXB.js";
8
+ } from "./chunk-BFIZMM4G.js";
9
9
  import {
10
10
  ToolRegistry,
11
11
  runStructuredRole
12
- } from "./chunk-AE36LLL2.js";
12
+ } from "./chunk-CYLPQWIF.js";
13
13
 
14
14
  // src/engine/triage.ts
15
15
  import { z } from "zod";
@@ -2,7 +2,7 @@ import {
2
2
  Board,
3
3
  refreshAfterChange,
4
4
  runTaskCycle
5
- } from "./chunk-7JMWPTJ5.js";
5
+ } from "./chunk-27F44PBD.js";
6
6
  import {
7
7
  defaultGitRunner
8
8
  } from "./chunk-LPQU436C.js";
@@ -300,7 +300,7 @@ function pruneTooling(doc, alsoDrop = /* @__PURE__ */ new Set()) {
300
300
  async function writtenTracePaths(cwd) {
301
301
  const out = /* @__PURE__ */ new Set();
302
302
  try {
303
- const { loadTraceIndex, traceRootRel } = await import("./trace-X6TU3AG6.js");
303
+ const { loadTraceIndex, traceRootRel } = await import("./trace-UVMZZRA5.js");
304
304
  const index = await loadTraceIndex(cwd);
305
305
  const root = traceRootRel().replace(/\\/g, "/");
306
306
  for (const [file, rec] of Object.entries(index.traces)) {
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  hashContent,
3
3
  traceDir
4
- } from "./chunk-KAGKX2YT.js";
4
+ } from "./chunk-ZPJP2VH5.js";
5
5
 
6
6
  // src/engine/project-brief.ts
7
7
  import { readFile, writeFile, mkdir } from "fs/promises";
@@ -70,7 +70,7 @@ async function gatherBriefInput(cwd, files) {
70
70
  async function ourOwnOutput(cwd) {
71
71
  const out = /* @__PURE__ */ new Set();
72
72
  try {
73
- const { loadTraceIndex, traceRootRel } = await import("./trace-X6TU3AG6.js");
73
+ const { loadTraceIndex, traceRootRel } = await import("./trace-UVMZZRA5.js");
74
74
  const root = traceRootRel().replace(/\\/g, "/");
75
75
  out.add(`${root}/PROJECT.md`);
76
76
  out.add(`${root}/PROJECT.json`);
@@ -3,20 +3,20 @@ import {
3
3
  loadMigratedSync,
4
4
  migratedNotice
5
5
  } from "./chunk-63E73TGI.js";
6
- import {
7
- telemetry
8
- } from "./chunk-AE36LLL2.js";
9
6
  import {
10
7
  readBriefSync
11
- } from "./chunk-6OSEQOYY.js";
8
+ } from "./chunk-6S4WWQMN.js";
9
+ import {
10
+ telemetry
11
+ } from "./chunk-JLWQCA7B.js";
12
12
  import {
13
13
  everTraceable,
14
14
  readTraceSync
15
- } from "./chunk-KAGKX2YT.js";
15
+ } from "./chunk-ZPJP2VH5.js";
16
16
  import {
17
17
  areaOf,
18
18
  loadGraphSync
19
- } from "./chunk-XEGQT5EN.js";
19
+ } from "./chunk-4M6LXNG2.js";
20
20
 
21
21
  // src/tools/read.ts
22
22
  import { readFile } from "fs/promises";
@@ -0,0 +1,214 @@
1
+ import {
2
+ runRoleAgent
3
+ } from "./chunk-JLWQCA7B.js";
4
+
5
+ // src/tools/registry.ts
6
+ import { z } from "zod";
7
+ var ToolRegistry = class {
8
+ tools = /* @__PURE__ */ new Map();
9
+ /**
10
+ * Registered and callable, but whose SCHEMA is withheld until something asks for it.
11
+ *
12
+ * A schema is paid for on every turn, whether or not the tool is ever used. Measured across twelve runs:
13
+ * 49 MCP tool schemas came to 86,620 characters (~21,655 tokens), 242 calls carried them, and that is
14
+ * ~5.2M of the 21.7M input tokens billed — 24% of everything — for FIVE tool calls, of two distinct tools.
15
+ * The catalogue that names them costs 900 characters (see MAX_TOOL_NOTE_CHARS); it is the schemas that are
16
+ * expensive, and a schema nobody is about to use buys nothing.
17
+ */
18
+ deferred = /* @__PURE__ */ new Set();
19
+ /** Bumped by anything that changes what `schemas()` would return, so the derivation can be cached. */
20
+ version = 0;
21
+ cached;
22
+ register(tool) {
23
+ this.tools.set(tool.name, tool);
24
+ this.deferred.delete(tool.name);
25
+ this.version++;
26
+ }
27
+ /** Callable by name from the moment it is registered; sent to the model only once {@link surface}d. */
28
+ registerDeferred(tool) {
29
+ this.tools.set(tool.name, tool);
30
+ this.deferred.add(tool.name);
31
+ this.version++;
32
+ }
33
+ /**
34
+ * Hands over the schemas for these names, from the next turn onward.
35
+ *
36
+ * Returns the ones that were actually withheld, so a caller can say what it just made available and stay
37
+ * quiet about what was already there.
38
+ */
39
+ surface(names) {
40
+ const opened = names.filter((n) => this.deferred.has(n));
41
+ for (const n of opened) this.deferred.delete(n);
42
+ if (opened.length) this.version++;
43
+ return opened;
44
+ }
45
+ /** Everything still withheld — what a search tool searches. */
46
+ deferredTools() {
47
+ return [...this.deferred].map((n) => this.tools.get(n)).filter((t) => t !== void 0);
48
+ }
49
+ /**
50
+ * A withheld tool is still CALLABLE.
51
+ *
52
+ * A model that reads the catalogue and calls the name straight off is right, and refusing it to enforce a
53
+ * search step would spend a turn teaching it a rule that exists for our benefit, not its.
54
+ */
55
+ get(name) {
56
+ return this.tools.get(name);
57
+ }
58
+ list() {
59
+ return [...this.tools.values()];
60
+ }
61
+ /**
62
+ * Tool schemas to send to the LLM: zod parameters → JSON Schema (zod 4 native). Withheld ones are omitted.
63
+ *
64
+ * …and so is a tool that has withdrawn itself. `Tool.broken` was documented as being read "where tools are
65
+ * OFFERED, so a broken one stops being handed to fresh agents" — and only `find_tool` ever read it, which
66
+ * covers the deferred tools and not the ones already on the list.
67
+ *
68
+ * Measured on one run: `mcp__angular-cli__list_projects` answered its first caller with a reply that failed
69
+ * its own declared output schema and withdrew itself. It was then offered to seventeen more agents, who
70
+ * called it twenty-eight more times. Every one of those was answered instantly, without touching the
71
+ * server — and still cost a whole model turn to learn what the run already knew.
72
+ */
73
+ schemas() {
74
+ const withdrawn = this.list().reduce((n, t) => n + (t.broken === void 0 ? 0 : 1), 0);
75
+ if (this.cached?.version === this.version && this.cached.withdrawn === withdrawn) return this.cached.schemas;
76
+ const schemas = this.list().filter((t) => !this.deferred.has(t.name) && t.broken === void 0).map((t) => ({
77
+ name: t.name,
78
+ description: t.description,
79
+ // MCP tools already carry a JSON Schema; everyone else derives it from their zod parameters.
80
+ parameters: t.rawSchema ?? z.toJSONSchema(t.parameters, { target: "draft-7" })
81
+ }));
82
+ this.cached = { version: this.version, withdrawn, schemas };
83
+ return schemas;
84
+ }
85
+ };
86
+
87
+ // src/core/types.ts
88
+ var DEADLINE_MESSAGE = "the model did not answer within its deadline";
89
+ var CHAIN_BUDGET_MESSAGE = "the chain's total budget ran out before this model was given a fair turn";
90
+
91
+ // src/agent/structured.ts
92
+ function valueAt(args, path) {
93
+ let cur = args;
94
+ for (const key of path) {
95
+ if (typeof cur !== "object" || cur === null) return void 0;
96
+ cur = cur[key];
97
+ }
98
+ return cur;
99
+ }
100
+ function whatWasWrong(issues, args) {
101
+ return issues.map((i) => {
102
+ const where = i.path.length ? i.path.join(".") : void 0;
103
+ const got = valueAt(args, i.path);
104
+ const shown = got === void 0 ? "nothing" : JSON.stringify(got);
105
+ const head = where ? `${where}: ${i.message}` : i.message;
106
+ if (got === void 0 && /received\s+(undefined|null|nothing)/i.test(i.message)) {
107
+ const parent = i.path.length > 1 ? valueAt(args, i.path.slice(0, -1)) : args;
108
+ const sent = parent && typeof parent === "object" ? Object.keys(parent) : [];
109
+ return sent.length ? `${head} \u2014 you sent only ${sent.map((k) => `\`${k}\``).join(", ")}` : head;
110
+ }
111
+ return `${head} \u2014 got ${shown.length > 120 ? `${shown.slice(0, 120)}\u2026` : shown}`;
112
+ }).join("; ");
113
+ }
114
+ function buildSubmitTool(schema) {
115
+ let box;
116
+ const tool = {
117
+ name: "submit",
118
+ description: "When you are done, submit your result in structured form with this tool.",
119
+ permissionLevel: "safe",
120
+ parameters: schema,
121
+ run: async (rawArgs) => {
122
+ const parsed = schema.safeParse(rawArgs);
123
+ if (!parsed.success) {
124
+ return { content: `submit: invalid output: ${whatWasWrong(parsed.error.issues, rawArgs)}`, isError: true };
125
+ }
126
+ box = { value: parsed.data };
127
+ return { content: "received", isError: false };
128
+ }
129
+ };
130
+ return { tool, result: () => box };
131
+ }
132
+ function extractStructured(text, schema) {
133
+ const trimmed = text.trim();
134
+ if (!trimmed) return void 0;
135
+ const candidates = [trimmed];
136
+ const block = trimmed.match(/\{[\s\S]*\}/);
137
+ if (block) candidates.push(block[0]);
138
+ for (const c of candidates) {
139
+ try {
140
+ const parsed = schema.safeParse(JSON.parse(c));
141
+ if (parsed.success) return parsed.data;
142
+ } catch {
143
+ }
144
+ }
145
+ return void 0;
146
+ }
147
+ var TURN_LIMIT_RE = /maximum turn count exceeded/i;
148
+ async function runStructuredRole(opts, schema, maxAttempts = 2) {
149
+ const handle = buildSubmitTool(schema);
150
+ const registry = new ToolRegistry();
151
+ for (const t of opts.tools.list()) registry.register(t);
152
+ registry.register(handle.tool);
153
+ const chain = [opts.model, ...opts.fallbacks ?? []];
154
+ const total = opts.totalMs ? AbortSignal.timeout(opts.totalMs) : void 0;
155
+ const outOfTime = () => total?.aborted === true;
156
+ const signalFor = () => {
157
+ const parts = [opts.signal];
158
+ if (total) parts.push(total);
159
+ if (opts.perAttemptMs) parts.push(AbortSignal.timeout(opts.perAttemptMs));
160
+ return parts.length === 1 ? opts.signal : AbortSignal.any(parts);
161
+ };
162
+ let lastError;
163
+ for (let ci = 0; ci < chain.length; ci++) {
164
+ const model = chain[ci];
165
+ if (ci > 0) opts.onFallback?.(chain[ci - 1], model, "structured: previous model returned no valid result");
166
+ const messages = [...opts.messages];
167
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
168
+ if (opts.signal.aborted) throw new Error("cancelled");
169
+ if (outOfTime()) break;
170
+ let lastText = "";
171
+ let errored;
172
+ for await (const ev of runRoleAgent({ ...opts, model, fallbacks: [], messages, tools: registry, signal: signalFor() })) {
173
+ if (ev.type === "error") {
174
+ errored = ev.message;
175
+ break;
176
+ }
177
+ if (ev.type === "abort") {
178
+ if (opts.signal.aborted) throw new Error("cancelled");
179
+ errored = total?.aborted ? CHAIN_BUDGET_MESSAGE : DEADLINE_MESSAGE;
180
+ break;
181
+ }
182
+ if (ev.type === "message.done") lastText = ev.message.content ?? lastText;
183
+ if (handle.result() !== void 0) break;
184
+ }
185
+ const r = handle.result();
186
+ if (r !== void 0) return r.value;
187
+ const salvaged = extractStructured(lastText, schema);
188
+ if (salvaged !== void 0) return salvaged;
189
+ if (errored !== void 0) {
190
+ if (TURN_LIMIT_RE.test(errored) && attempt < maxAttempts - 1) {
191
+ messages.push({ role: "assistant", content: lastText });
192
+ messages.push({ role: "user", content: "You have used your entire tool-call budget. Call `submit` NOW with the findings you already have. Do not read, grep or inspect anything else." });
193
+ continue;
194
+ }
195
+ lastError = errored;
196
+ break;
197
+ }
198
+ messages.push({ role: "assistant", content: lastText });
199
+ messages.push({
200
+ role: "user",
201
+ content: "You did not call the `submit` tool. Call `submit` now with your result as structured arguments \u2014 do not answer in prose."
202
+ });
203
+ }
204
+ if (opts.signal.aborted) throw new Error("cancelled");
205
+ if (outOfTime()) throw new Error("the model chain did not produce a result within its total budget");
206
+ opts.onStructuralFailure?.(model, "answered in prose instead of calling submit");
207
+ }
208
+ throw new Error(lastError ?? "structured role: submit was not called (whole model chain tried)");
209
+ }
210
+
211
+ export {
212
+ ToolRegistry,
213
+ runStructuredRole
214
+ };