@hizliemre/horse-code 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -1
- package/dist/{app-SB2L34JW.js → app-LAJN3TWC.js} +9 -9
- package/dist/{chunk-F2IALVBU.js → chunk-3UYA3KUG.js} +3 -3
- package/dist/chunk-4EWK7HWQ.js +177 -0
- package/dist/{chunk-TOPZL5SU.js → chunk-DRZSUQ7Q.js} +24 -3
- package/dist/{chunk-HBSC2HT2.js → chunk-EQX7BQYN.js} +1 -1
- package/dist/{chunk-2DGO2BUB.js → chunk-K2VERI5Q.js} +87 -20
- package/dist/{chunk-JWAEW7AJ.js → chunk-KOWMHL23.js} +1 -1
- package/dist/{chunk-3XVZXTB6.js → chunk-RPVAIS3P.js} +54 -38
- package/dist/{chunk-YILDXPSI.js → chunk-VPAWRRHL.js} +18 -4
- package/dist/{chunk-7TBYMFMG.js → chunk-WYQBRCKY.js} +2 -2
- package/dist/cli.js +57 -135
- package/dist/{fix-HBBOTUWM.js → fix-CMARU6JR.js} +4 -4
- package/dist/{ongoing-OV5XROTU.js → ongoing-XP6WXNI7.js} +2 -2
- package/dist/{save-skills-OHYGVTQ4.js → save-skills-NSLBU33X.js} +1 -1
- package/dist/{triage-2J3T5PVQ.js → triage-ES5OHZOS.js} +3 -3
- package/dist/{verify-WQ3GHION.js → verify-3R7DGUSI.js} +7 -7
- package/package.json +3 -2
- package/skills/README.md +123 -0
- package/skills/brainstorming/SKILL.md +159 -0
- package/skills/frontend-design/LICENSE.txt +177 -0
- package/skills/frontend-design/SKILL.md +55 -0
- package/skills/systematic-debugging/SKILL.md +296 -0
- package/skills/test-driven-development/SKILL.md +371 -0
- package/skills/ui-ux-pro-max/LICENSE.txt +21 -0
- package/skills/ui-ux-pro-max/SKILL.md +659 -0
- package/skills/writing-plans/SKILL.md +174 -0
- package/dist/chunk-DKVIN43T.js +0 -54
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
readFileTool,
|
|
23
23
|
reinforceTouched,
|
|
24
24
|
reinforceUsed
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-DRZSUQ7Q.js";
|
|
26
26
|
import {
|
|
27
27
|
ToolRegistry,
|
|
28
28
|
handedOver,
|
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
runToCompletion,
|
|
31
31
|
telemetry,
|
|
32
32
|
truncateSafe
|
|
33
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-VPAWRRHL.js";
|
|
34
34
|
import {
|
|
35
35
|
loadTraceIndex,
|
|
36
36
|
pruneTraces,
|
|
@@ -262,6 +262,43 @@ function sessionName(now, taken) {
|
|
|
262
262
|
}
|
|
263
263
|
return `${day}_${Date.now()}`;
|
|
264
264
|
}
|
|
265
|
+
var FORBIDDEN_AT_ROOT = /* @__PURE__ */ new Set([
|
|
266
|
+
"merge",
|
|
267
|
+
"rebase",
|
|
268
|
+
"cherry-pick",
|
|
269
|
+
"revert",
|
|
270
|
+
"reset",
|
|
271
|
+
"checkout",
|
|
272
|
+
"switch",
|
|
273
|
+
"restore",
|
|
274
|
+
"commit",
|
|
275
|
+
"am",
|
|
276
|
+
"apply",
|
|
277
|
+
"stash",
|
|
278
|
+
"pull",
|
|
279
|
+
"clean"
|
|
280
|
+
]);
|
|
281
|
+
var TAKES_A_VALUE = /* @__PURE__ */ new Set(["-c", "-C", "--git-dir", "--work-tree", "--namespace", "--exec-path", "--config-env"]);
|
|
282
|
+
function gitVerb(args) {
|
|
283
|
+
for (let i = 0; i < args.length; i++) {
|
|
284
|
+
const a = args[i];
|
|
285
|
+
if (a === void 0) continue;
|
|
286
|
+
if (!a.startsWith("-")) return a;
|
|
287
|
+
if (TAKES_A_VALUE.has(a)) i++;
|
|
288
|
+
}
|
|
289
|
+
return void 0;
|
|
290
|
+
}
|
|
291
|
+
function guardRoot(run, repoRoot) {
|
|
292
|
+
return async (args, cwd) => {
|
|
293
|
+
const verb = gitVerb(args);
|
|
294
|
+
if (cwd === repoRoot && verb !== void 0 && FORBIDDEN_AT_ROOT.has(verb)) {
|
|
295
|
+
throw new Error(
|
|
296
|
+
`refusing to run \`git ${verb}\` in the project checkout (${repoRoot}). A session's work stays on its own branch and in its own worktree; bringing it in is the user's decision, taken in their own time.`
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
return run(args, cwd);
|
|
300
|
+
};
|
|
301
|
+
}
|
|
265
302
|
var WorktreeManager = class {
|
|
266
303
|
repoRoot;
|
|
267
304
|
/** The project checkout this manager was built for — where per-project settings and the remote live. */
|
|
@@ -281,12 +318,23 @@ var WorktreeManager = class {
|
|
|
281
318
|
*/
|
|
282
319
|
worktreeHome;
|
|
283
320
|
git;
|
|
321
|
+
/**
|
|
322
|
+
* The unguarded runner, for the one case that legitimately needs a forbidden verb: turning a directory
|
|
323
|
+
* that is not a repository into one.
|
|
324
|
+
*
|
|
325
|
+
* `git worktree add` needs a commit to branch from, and an empty repository has none — so a first commit
|
|
326
|
+
* is not delivery, it is the precondition for ever leaving the root alone again. It runs only when there
|
|
327
|
+
* is no HEAD, so there is no branch to disturb and no work to overwrite. Named rather than flagged, so
|
|
328
|
+
* grepping for it finds every use.
|
|
329
|
+
*/
|
|
330
|
+
rawGit;
|
|
284
331
|
/** Injectable clock: a session's NAME is the day it opened, so a test has to be able to say which day. */
|
|
285
332
|
now;
|
|
286
333
|
constructor(deps) {
|
|
287
334
|
this.repoRoot = deps.repoRoot;
|
|
288
335
|
this.worktreeHome = deps.worktreeHome ?? deps.repoRoot;
|
|
289
|
-
this.
|
|
336
|
+
this.rawGit = deps.runGit ?? defaultGitRunner;
|
|
337
|
+
this.git = guardRoot(this.rawGit, deps.repoRoot);
|
|
290
338
|
this.now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
291
339
|
}
|
|
292
340
|
/** Runs git; nonzero exit → throws a clear error. Returns output (stdout). */
|
|
@@ -318,7 +366,8 @@ var WorktreeManager = class {
|
|
|
318
366
|
await this.ensureRepo();
|
|
319
367
|
const head = await this.git(["rev-parse", "--verify", "--quiet", "HEAD"], this.repoRoot);
|
|
320
368
|
if (head.code === 0) return;
|
|
321
|
-
await this.
|
|
369
|
+
const r = await this.rawGit(["commit", "--allow-empty", "-m", "hc: initial commit"], this.repoRoot);
|
|
370
|
+
if (r.code !== 0) throw new Error(`git commit --allow-empty failed (${r.code}): ${(r.stderr || r.stdout).trim()}`);
|
|
322
371
|
}
|
|
323
372
|
/**
|
|
324
373
|
* The ref to base the session's worktree on. Uses `fromBranch` when it resolves; otherwise falls back to
|
|
@@ -602,39 +651,6 @@ ${out.slice(0, MAX_DIFF_CHARS)}`;
|
|
|
602
651
|
if (check.code !== 0) return;
|
|
603
652
|
await this.run(["push", remote, session.baseBranch], session.baseWorktree);
|
|
604
653
|
}
|
|
605
|
-
/**
|
|
606
|
-
* Lands the finished work on the branch the job started from, in the main working copy.
|
|
607
|
-
*
|
|
608
|
-
* Without this, a project with no git remote gets nothing: `push` is a no-op and a pull request has
|
|
609
|
-
* nowhere to go, so every completed task sits on `hc/<job>/base` — invisible from the repository root.
|
|
610
|
-
* A user who watched thirty tasks succeed then finds an empty directory and cannot run the project.
|
|
611
|
-
*
|
|
612
|
-
* A pull request is delivery when there is a remote to open it against. When there is not, merging is.
|
|
613
|
-
*
|
|
614
|
-
* Refuses rather than forces. A dirty working copy or a checkout on some other branch means the user has
|
|
615
|
-
* something in progress, and overwriting that to deliver would be a worse failure than not delivering:
|
|
616
|
-
* the branch still exists and the caller reports how to merge it by hand.
|
|
617
|
-
*/
|
|
618
|
-
async deliverLocally(session, targetBranch) {
|
|
619
|
-
const dirty = await this.git(["status", "--porcelain"], this.repoRoot);
|
|
620
|
-
if (dirty.code !== 0) return { ok: false, why: "the repository could not be read" };
|
|
621
|
-
if (dirty.stdout.split("\n").some((l) => l.trim() && !l.startsWith("??"))) {
|
|
622
|
-
return { ok: false, why: "the working copy has uncommitted changes" };
|
|
623
|
-
}
|
|
624
|
-
const head = await this.git(["symbolic-ref", "--short", "HEAD"], this.repoRoot);
|
|
625
|
-
const current = head.stdout.trim();
|
|
626
|
-
if (head.code !== 0 || !current) return { ok: false, why: "the repository is not on a branch" };
|
|
627
|
-
if (current !== targetBranch) return { ok: false, why: `the repository is on \`${current}\`, not \`${targetBranch}\`` };
|
|
628
|
-
const count = await this.git(["rev-list", "--count", `${targetBranch}..${session.baseBranch}`], this.repoRoot);
|
|
629
|
-
const commits = Number(count.stdout.trim()) || 0;
|
|
630
|
-
if (!commits) return { ok: true, commits: 0 };
|
|
631
|
-
const merged = await this.git(
|
|
632
|
-
["merge", "--no-ff", "-m", `hc: ${session.jobSlug}`, session.baseBranch],
|
|
633
|
-
this.repoRoot
|
|
634
|
-
);
|
|
635
|
-
if (merged.code !== 0) return { ok: false, why: "the merge did not apply cleanly" };
|
|
636
|
-
return { ok: true, commits };
|
|
637
|
-
}
|
|
638
654
|
async openPR(session, adapter, input) {
|
|
639
655
|
const res = await adapter.createPR({
|
|
640
656
|
branch: session.baseBranch,
|
|
@@ -2148,7 +2164,7 @@ function destroysWork(command) {
|
|
|
2148
2164
|
for (const seg of command.split(/&&|\|\||;|\|/)) {
|
|
2149
2165
|
const m = /^\s*git\s+(.*)$/.exec(seg.trim());
|
|
2150
2166
|
if (!m) continue;
|
|
2151
|
-
const rest = (m[1] ?? "").replace(/^(
|
|
2167
|
+
const rest = (m[1] ?? "").replace(/^(?:(?:-[cC]|--(?:git-dir|work-tree|namespace|exec-path|config-env))\s+\S+|-\S+)\s+/g, "").trim();
|
|
2152
2168
|
const hit = DESTROYS_WORK.find((d) => d.re.test(rest));
|
|
2153
2169
|
if (hit) return hit.what;
|
|
2154
2170
|
}
|
|
@@ -1241,6 +1241,23 @@ var ToolRegistry = class {
|
|
|
1241
1241
|
};
|
|
1242
1242
|
|
|
1243
1243
|
// src/agent/structured.ts
|
|
1244
|
+
function valueAt(args, path) {
|
|
1245
|
+
let cur = args;
|
|
1246
|
+
for (const key of path) {
|
|
1247
|
+
if (typeof cur !== "object" || cur === null) return void 0;
|
|
1248
|
+
cur = cur[key];
|
|
1249
|
+
}
|
|
1250
|
+
return cur;
|
|
1251
|
+
}
|
|
1252
|
+
function whatWasWrong(issues, args) {
|
|
1253
|
+
return issues.map((i) => {
|
|
1254
|
+
const where = i.path.length ? i.path.join(".") : void 0;
|
|
1255
|
+
const got = valueAt(args, i.path);
|
|
1256
|
+
const shown = got === void 0 ? "nothing" : JSON.stringify(got);
|
|
1257
|
+
const head = where ? `${where}: ${i.message}` : i.message;
|
|
1258
|
+
return `${head} \u2014 got ${shown.length > 120 ? `${shown.slice(0, 120)}\u2026` : shown}`;
|
|
1259
|
+
}).join("; ");
|
|
1260
|
+
}
|
|
1244
1261
|
function buildSubmitTool(schema) {
|
|
1245
1262
|
let box;
|
|
1246
1263
|
const tool = {
|
|
@@ -1251,10 +1268,7 @@ function buildSubmitTool(schema) {
|
|
|
1251
1268
|
run: async (rawArgs) => {
|
|
1252
1269
|
const parsed = schema.safeParse(rawArgs);
|
|
1253
1270
|
if (!parsed.success) {
|
|
1254
|
-
return {
|
|
1255
|
-
content: `submit: invalid output: ${parsed.error.issues.map((i) => i.message).join("; ")}`,
|
|
1256
|
-
isError: true
|
|
1257
|
-
};
|
|
1271
|
+
return { content: `submit: invalid output: ${whatWasWrong(parsed.error.issues, rawArgs)}`, isError: true };
|
|
1258
1272
|
}
|
|
1259
1273
|
box = { value: parsed.data };
|
|
1260
1274
|
return { content: "received", isError: false };
|
|
@@ -5,11 +5,11 @@ import {
|
|
|
5
5
|
memoryHints,
|
|
6
6
|
readFileTool,
|
|
7
7
|
reinforceUsed
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-DRZSUQ7Q.js";
|
|
9
9
|
import {
|
|
10
10
|
ToolRegistry,
|
|
11
11
|
runStructuredRole
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-VPAWRRHL.js";
|
|
13
13
|
|
|
14
14
|
// src/engine/triage.ts
|
|
15
15
|
import { z } from "zod";
|
package/dist/cli.js
CHANGED
|
@@ -14,11 +14,11 @@ import {
|
|
|
14
14
|
restoreTerminal,
|
|
15
15
|
runJob,
|
|
16
16
|
sttySane
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-K2VERI5Q.js";
|
|
18
18
|
import {
|
|
19
19
|
listOmniRouteModels
|
|
20
20
|
} from "./chunk-O74BDQKS.js";
|
|
21
|
-
import "./chunk-
|
|
21
|
+
import "./chunk-3UYA3KUG.js";
|
|
22
22
|
import {
|
|
23
23
|
CODE_TEAM,
|
|
24
24
|
DEFAULT_COUNCIL,
|
|
@@ -34,16 +34,18 @@ import {
|
|
|
34
34
|
buildTeamRegistry,
|
|
35
35
|
mainWorktreeRoot,
|
|
36
36
|
toSlug
|
|
37
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-RPVAIS3P.js";
|
|
38
38
|
import "./chunk-QF4MP6BS.js";
|
|
39
|
-
import "./chunk-
|
|
39
|
+
import "./chunk-EQX7BQYN.js";
|
|
40
40
|
import "./chunk-FGVJFMK5.js";
|
|
41
41
|
import {
|
|
42
42
|
parseFrontmatter
|
|
43
43
|
} from "./chunk-BY4DP7IE.js";
|
|
44
44
|
import {
|
|
45
|
+
UNSET_MODEL,
|
|
46
|
+
loadConfig,
|
|
45
47
|
saveSkillSource
|
|
46
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-4EWK7HWQ.js";
|
|
47
49
|
import "./chunk-H2FDGPVW.js";
|
|
48
50
|
import {
|
|
49
51
|
buildBrief,
|
|
@@ -58,7 +60,7 @@ import {
|
|
|
58
60
|
import {
|
|
59
61
|
InjectionLog,
|
|
60
62
|
memoryNote
|
|
61
|
-
} from "./chunk-
|
|
63
|
+
} from "./chunk-DRZSUQ7Q.js";
|
|
62
64
|
import "./chunk-2SVAHH5N.js";
|
|
63
65
|
import {
|
|
64
66
|
Telemetry,
|
|
@@ -68,7 +70,7 @@ import {
|
|
|
68
70
|
stripThinking,
|
|
69
71
|
telemetry,
|
|
70
72
|
writeHeapSnapshot
|
|
71
|
-
} from "./chunk-
|
|
73
|
+
} from "./chunk-VPAWRRHL.js";
|
|
72
74
|
import {
|
|
73
75
|
briefStatus
|
|
74
76
|
} from "./chunk-DTWKSZXY.js";
|
|
@@ -92,114 +94,6 @@ import { readFileSync as readFileSync3, existsSync as existsSync3, writeFileSync
|
|
|
92
94
|
import { join as join8, dirname as dirname4 } from "path";
|
|
93
95
|
import { pathToFileURL } from "url";
|
|
94
96
|
|
|
95
|
-
// src/config/config.ts
|
|
96
|
-
import { z } from "zod";
|
|
97
|
-
var UNSET_MODEL = "default";
|
|
98
|
-
var DEFAULT_CONFIG = {
|
|
99
|
-
baseUrl: "http://localhost:20128",
|
|
100
|
-
model: UNSET_MODEL,
|
|
101
|
-
// acceptEdits: auto-approve file writes/edits (the pipeline builds in an isolated worktree → reviewed as a
|
|
102
|
-
// PR), still prompt for shell/exec. Keeps the automated build flowing without an approval per file.
|
|
103
|
-
mode: "acceptEdits",
|
|
104
|
-
allowlist: [],
|
|
105
|
-
roles: {},
|
|
106
|
-
specKit: { version: "v0.13.2" },
|
|
107
|
-
mcp: {},
|
|
108
|
-
modelSources: [],
|
|
109
|
-
traceDir: "",
|
|
110
|
-
skillSources: [],
|
|
111
|
-
maxParallel: 8,
|
|
112
|
-
telemetry: true
|
|
113
|
-
};
|
|
114
|
-
var reviewerSchema = z.object({ name: z.string(), perspective: z.string(), models: z.array(z.string()) });
|
|
115
|
-
var fileSchema = z.object({
|
|
116
|
-
apiKey: z.string().optional(),
|
|
117
|
-
baseUrl: z.string().optional(),
|
|
118
|
-
model: z.string().optional(),
|
|
119
|
-
mode: z.enum(["ask", "acceptEdits", "auto"]).optional(),
|
|
120
|
-
allowlist: z.array(z.string()).optional(),
|
|
121
|
-
roles: z.record(
|
|
122
|
-
z.string(),
|
|
123
|
-
z.object({
|
|
124
|
-
models: z.array(z.string()),
|
|
125
|
-
systemPrompt: z.string().optional(),
|
|
126
|
-
skills: z.array(z.string()).optional(),
|
|
127
|
-
// Anthropic models only — see RoleConfig.effort. Unknown keys are stripped, so without this line a
|
|
128
|
-
// level written into the config would be silently discarded on the way in.
|
|
129
|
-
effort: z.enum(["low", "medium", "high", "xhigh", "max"]).optional()
|
|
130
|
-
})
|
|
131
|
-
).optional(),
|
|
132
|
-
// The review team's finder lenses, one set per stage (any omitted set falls back to the built-in default).
|
|
133
|
-
team: z.object({
|
|
134
|
-
spec: z.array(reviewerSchema).optional(),
|
|
135
|
-
plan: z.array(reviewerSchema).optional(),
|
|
136
|
-
code: z.array(reviewerSchema).optional()
|
|
137
|
-
}).optional(),
|
|
138
|
-
council: z.object({ members: z.array(reviewerSchema) }).optional(),
|
|
139
|
-
specKit: z.object({ version: z.string() }).optional(),
|
|
140
|
-
modelSources: z.array(z.string()).optional(),
|
|
141
|
-
traceDir: z.string().optional(),
|
|
142
|
-
// where /graph trace writes; empty = .horsecode/traces
|
|
143
|
-
mainBranch: z.string().optional(),
|
|
144
|
-
// the branch a resumed session syncs from; asked once, then remembered
|
|
145
|
-
// Bounded: below 1 nothing runs; above 32 the git merge lock, not the models, becomes the limit.
|
|
146
|
-
maxParallel: z.number().int().min(1).max(32).optional(),
|
|
147
|
-
telemetry: z.boolean().optional(),
|
|
148
|
-
skillSources: z.array(z.object({
|
|
149
|
-
name: z.string(),
|
|
150
|
-
repo: z.string(),
|
|
151
|
-
path: z.string().optional(),
|
|
152
|
-
ref: z.string().optional()
|
|
153
|
-
})).optional(),
|
|
154
|
-
mcp: z.record(
|
|
155
|
-
z.string(),
|
|
156
|
-
z.union([
|
|
157
|
-
z.object({ command: z.array(z.string()).min(1), env: z.record(z.string(), z.string()).optional(), readOnly: z.boolean().optional() }),
|
|
158
|
-
z.object({ url: z.string(), headers: z.record(z.string(), z.string()).optional(), readOnly: z.boolean().optional() })
|
|
159
|
-
])
|
|
160
|
-
).optional()
|
|
161
|
-
}).partial();
|
|
162
|
-
function parseFile(raw) {
|
|
163
|
-
if (!raw) return {};
|
|
164
|
-
try {
|
|
165
|
-
const parsed = fileSchema.safeParse(JSON.parse(raw));
|
|
166
|
-
return parsed.success ? parsed.data : {};
|
|
167
|
-
} catch {
|
|
168
|
-
return {};
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
function loadConfig(opts) {
|
|
172
|
-
const global = parseFile(opts.readFile(`${opts.home}/.horsecode/config.json`));
|
|
173
|
-
const project = parseFile(opts.readFile(`${opts.cwd}/.horsecode/config.json`));
|
|
174
|
-
const { apiKey: _leak, ...projectSafe } = project;
|
|
175
|
-
const merged = {
|
|
176
|
-
...DEFAULT_CONFIG,
|
|
177
|
-
...global,
|
|
178
|
-
...projectSafe
|
|
179
|
-
};
|
|
180
|
-
merged.allowlist = projectSafe.allowlist ?? global.allowlist ?? [];
|
|
181
|
-
merged.roles = { ...global.roles ?? {}, ...projectSafe.roles ?? {} };
|
|
182
|
-
merged.mcp = { ...global.mcp ?? {}, ...projectSafe.mcp ?? {} };
|
|
183
|
-
merged.modelSources = projectSafe.modelSources ?? global.modelSources ?? [];
|
|
184
|
-
merged.maxParallel = projectSafe.maxParallel ?? global.maxParallel ?? DEFAULT_CONFIG.maxParallel;
|
|
185
|
-
merged.telemetry = projectSafe.telemetry ?? global.telemetry ?? DEFAULT_CONFIG.telemetry;
|
|
186
|
-
const byName = new Map((global.skillSources ?? []).map((s) => [s.name, s]));
|
|
187
|
-
for (const s of projectSafe.skillSources ?? []) byName.set(s.name, s);
|
|
188
|
-
merged.skillSources = [...byName.values()];
|
|
189
|
-
merged.specKit = projectSafe.specKit ?? global.specKit ?? DEFAULT_CONFIG.specKit;
|
|
190
|
-
const team = {
|
|
191
|
-
spec: projectSafe.team?.spec ?? global.team?.spec,
|
|
192
|
-
plan: projectSafe.team?.plan ?? global.team?.plan,
|
|
193
|
-
code: projectSafe.team?.code ?? global.team?.code
|
|
194
|
-
};
|
|
195
|
-
merged.team = team.spec || team.plan || team.code ? team : void 0;
|
|
196
|
-
const councilMembers = projectSafe.council?.members ?? global.council?.members;
|
|
197
|
-
merged.council = councilMembers ? { members: councilMembers } : void 0;
|
|
198
|
-
if (opts.env.OMNIROUTE_API_KEY) merged.apiKey = opts.env.OMNIROUTE_API_KEY;
|
|
199
|
-
if (opts.env.OMNIROUTE_BASE_URL) merged.baseUrl = opts.env.OMNIROUTE_BASE_URL;
|
|
200
|
-
return merged;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
97
|
// src/skills/registry.ts
|
|
204
98
|
import { readdir, readFile } from "fs/promises";
|
|
205
99
|
import { join } from "path";
|
|
@@ -931,16 +825,18 @@ var FileSink = class {
|
|
|
931
825
|
return this.dropped;
|
|
932
826
|
}
|
|
933
827
|
/**
|
|
934
|
-
*
|
|
828
|
+
* `flush()` is awaited as a run finishes, so whatever happens here decides whether the run can end.
|
|
935
829
|
*
|
|
936
|
-
* `createWriteStream` does not throw when the path cannot be opened — it emits `error` on a later tick
|
|
830
|
+
* `createWriteStream` does not throw when the path cannot be opened — it emits `error` on a later tick, so
|
|
937
831
|
* the constructor's `catch` does not run, `stream` is set, and whether the error handler has cleared it by
|
|
938
|
-
* the time anything flushes is a race with the event loop.
|
|
939
|
-
*
|
|
832
|
+
* the time anything flushes is a race with the event loop. Losing that race means `end(cb)` is called on a
|
|
833
|
+
* stream that never opened.
|
|
940
834
|
*
|
|
941
|
-
* The
|
|
942
|
-
*
|
|
943
|
-
*
|
|
835
|
+
* The `error` listener is a second exit for that case. It was added believing the callback would otherwise
|
|
836
|
+
* never fire and hang the run — an honest guess that has since been TESTED and did not hold: with the
|
|
837
|
+
* listener removed, an EISDIR stream still resolves (test/obs/telemetry.test.ts). It stays because it
|
|
838
|
+
* costs one line and closes a path nothing else covers, not because a hang was ever reproduced through it.
|
|
839
|
+
* The 3h42m CI hang this was written during had a different cause, in the tests themselves.
|
|
944
840
|
*/
|
|
945
841
|
async flush() {
|
|
946
842
|
const s = this.stream;
|
|
@@ -1065,12 +961,8 @@ function whereItLanded(path) {
|
|
|
1065
961
|
return `Written on ${branch ? `branch \`${branch}\`` : "its own branch"}, in the worktree at \`${base}\`. Review it there, then merge it in.`;
|
|
1066
962
|
}
|
|
1067
963
|
function describeDelivery(d) {
|
|
1068
|
-
if (d.mergedInto) {
|
|
1069
|
-
return `Merged into \`${d.mergedInto}\` \u2014 the files are in your working copy.`;
|
|
1070
|
-
}
|
|
1071
964
|
return [
|
|
1072
965
|
`**The work is on branch \`${d.branch}\`** \u2014 not in your working copy yet.`,
|
|
1073
|
-
d.notMerged ? `Not merged: ${d.notMerged}.` : "",
|
|
1074
966
|
"",
|
|
1075
967
|
"To bring it in:",
|
|
1076
968
|
"```",
|
|
@@ -1079,6 +971,36 @@ function describeDelivery(d) {
|
|
|
1079
971
|
`Or inspect it first: \`git diff HEAD...${d.branch}\` \xB7 worktree: \`${d.worktree}\``
|
|
1080
972
|
].filter((l) => l !== void 0).join("\n");
|
|
1081
973
|
}
|
|
974
|
+
var DELIVERED_SHARE = 0.5;
|
|
975
|
+
function describeOutcome(w) {
|
|
976
|
+
if (w.status === "completed") return w.pr ? `PR: ${w.pr.url}` : "all tasks merged";
|
|
977
|
+
const stuck = w.failed.length + w.skipped.length;
|
|
978
|
+
const total = w.waves.flat().length || stuck;
|
|
979
|
+
const merged = Math.max(0, total - stuck);
|
|
980
|
+
const parts = [
|
|
981
|
+
w.failed.length ? `${w.failed.length} failed` : "",
|
|
982
|
+
w.skipped.length ? `${w.skipped.length} blocked behind them` : ""
|
|
983
|
+
].filter(Boolean).join(", ");
|
|
984
|
+
const head = `${merged} of ${total} tasks merged`;
|
|
985
|
+
return merged / total < DELIVERED_SHARE ? `\u26A0\uFE0F ${head} \u2014 ${parts}. Most of the plan did not land; the feature is not built.` : `${head} \u2014 ${parts}.`;
|
|
986
|
+
}
|
|
987
|
+
function describeTraceFailures(failed) {
|
|
988
|
+
if (!failed.length) return "";
|
|
989
|
+
const byError = /* @__PURE__ */ new Map();
|
|
990
|
+
for (const f of failed) byError.set(f.error, [...byError.get(f.error) ?? [], f.file]);
|
|
991
|
+
if (byError.size === 1) {
|
|
992
|
+
const [error, files] = [...byError][0];
|
|
993
|
+
const shown = files.slice(0, 3).map((f) => `- \`${f}\``);
|
|
994
|
+
if (files.length > shown.length) shown.push(`- \u2026and ${files.length - shown.length} more`);
|
|
995
|
+
return `\u26A0\uFE0F ${failed.length} failed, all for the same reason \u2014 ${error}
|
|
996
|
+
${shown.join("\n")}`;
|
|
997
|
+
}
|
|
998
|
+
const rows = [...byError].sort((a, b) => b[1].length - a[1].length).slice(0, 5).map(([error, files]) => `- ${files.length}\xD7 ${error} (e.g. \`${files[0]}\`)`);
|
|
999
|
+
const rest = byError.size - rows.length;
|
|
1000
|
+
if (rest > 0) rows.push(`- \u2026and ${rest} further cause(s)`);
|
|
1001
|
+
return `\u26A0\uFE0F ${failed.length} failed:
|
|
1002
|
+
${rows.join("\n")}`;
|
|
1003
|
+
}
|
|
1082
1004
|
function renderResult(res) {
|
|
1083
1005
|
if (res.kind === "chat") return stripThinking(res.response);
|
|
1084
1006
|
if (res.kind === "rejected") return `Not approved (stopped at the ${res.stage} stage).`;
|
|
@@ -1090,7 +1012,7 @@ function renderResult(res) {
|
|
|
1090
1012
|
|
|
1091
1013
|
_${whereItLanded(res.path)}_` : `The constitution phase finished without writing \`${res.path}\` \u2014 nothing was changed.`;
|
|
1092
1014
|
}
|
|
1093
|
-
const outcome = res.wave
|
|
1015
|
+
const outcome = describeOutcome(res.wave);
|
|
1094
1016
|
const rev = res.revision ? `
|
|
1095
1017
|
revision: ${res.revision.status}` : "";
|
|
1096
1018
|
return `${stripThinking(res.report)}
|
|
@@ -1256,7 +1178,7 @@ async function main(argv) {
|
|
|
1256
1178
|
const useTui = shouldUseTui(!!process.stdin.isTTY, !!process.stdout.isTTY, !!args.noTui);
|
|
1257
1179
|
if (!args.prompt) {
|
|
1258
1180
|
if (useTui) {
|
|
1259
|
-
const { runTuiRepl } = await import("./app-
|
|
1181
|
+
const { runTuiRepl } = await import("./app-LAJN3TWC.js");
|
|
1260
1182
|
const { fetchCatalog, makeProbe, discoverSources } = await import("./discover-5URG7C4J.js");
|
|
1261
1183
|
const { loadSourceCache, saveSourceCache } = await import("./source-cache-XEK5WN7I.js");
|
|
1262
1184
|
const manualSources = config.modelSources.length > 0;
|
|
@@ -1390,13 +1312,10 @@ _Every agent can query it: \`graph_impact\` (blast radius), \`graph_trace\`, \`g
|
|
|
1390
1312
|
if (res.upToDate) bits.push(`${res.upToDate} already current`);
|
|
1391
1313
|
if (res.pruned.length) bits.push(`${res.pruned.length} removed for deleted files`);
|
|
1392
1314
|
if (res.wroteGitignore) bits.push("\n\n_Added .gitignore rules: traces are committed, the AST cache is not._");
|
|
1393
|
-
|
|
1394
|
-
|
|
1315
|
+
const failures = describeTraceFailures(res.failed);
|
|
1316
|
+
return `${bits.join(" \xB7 ")}${failures ? `
|
|
1395
1317
|
|
|
1396
|
-
|
|
1397
|
-
${res.failed.slice(0, 5).map((f) => `- \`${f.file}\` \u2014 ${f.error}`).join("\n")}`);
|
|
1398
|
-
}
|
|
1399
|
-
return `${bits.join(" \xB7 ")}
|
|
1318
|
+
${failures}` : ""}
|
|
1400
1319
|
|
|
1401
1320
|
_Committed with the repo, so every clone starts with them. Agents read one with \`graph_trace\`._`;
|
|
1402
1321
|
};
|
|
@@ -1439,7 +1358,7 @@ _Committed with the repo, so every clone starts with them. Agents read one with
|
|
|
1439
1358
|
...args.revisionRounds !== void 0 && { revisionRounds: args.revisionRounds }
|
|
1440
1359
|
};
|
|
1441
1360
|
if (useTui) {
|
|
1442
|
-
const { runTui } = await import("./app-
|
|
1361
|
+
const { runTui } = await import("./app-LAJN3TWC.js");
|
|
1443
1362
|
const res = await runTui({ buildDeps, job });
|
|
1444
1363
|
console.log(renderResult(res));
|
|
1445
1364
|
return;
|
|
@@ -1486,7 +1405,10 @@ if (isMainModule()) {
|
|
|
1486
1405
|
});
|
|
1487
1406
|
}
|
|
1488
1407
|
export {
|
|
1408
|
+
DELIVERED_SHARE,
|
|
1489
1409
|
describeDelivery,
|
|
1410
|
+
describeOutcome,
|
|
1411
|
+
describeTraceFailures,
|
|
1490
1412
|
main,
|
|
1491
1413
|
parseArgs,
|
|
1492
1414
|
renderResult,
|
|
@@ -8,14 +8,14 @@ import {
|
|
|
8
8
|
dirtyPaths,
|
|
9
9
|
runFix,
|
|
10
10
|
runSmallChange
|
|
11
|
-
} from "./chunk-
|
|
12
|
-
import "./chunk-
|
|
11
|
+
} from "./chunk-KOWMHL23.js";
|
|
12
|
+
import "./chunk-RPVAIS3P.js";
|
|
13
13
|
import "./chunk-FGVJFMK5.js";
|
|
14
14
|
import "./chunk-NNTIACT4.js";
|
|
15
15
|
import "./chunk-IW2KBAVZ.js";
|
|
16
|
-
import "./chunk-
|
|
16
|
+
import "./chunk-DRZSUQ7Q.js";
|
|
17
17
|
import "./chunk-2SVAHH5N.js";
|
|
18
|
-
import "./chunk-
|
|
18
|
+
import "./chunk-VPAWRRHL.js";
|
|
19
19
|
import "./chunk-DTWKSZXY.js";
|
|
20
20
|
import "./chunk-FFYBY2NA.js";
|
|
21
21
|
import "./chunk-PGOYDOI4.js";
|
|
@@ -3,12 +3,12 @@ import {
|
|
|
3
3
|
} from "./chunk-YBWTCXUS.js";
|
|
4
4
|
import {
|
|
5
5
|
askInUserLanguage
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-EQX7BQYN.js";
|
|
7
7
|
import {
|
|
8
8
|
checkpointMtime,
|
|
9
9
|
readCheckpoint
|
|
10
10
|
} from "./chunk-FGVJFMK5.js";
|
|
11
|
-
import "./chunk-
|
|
11
|
+
import "./chunk-VPAWRRHL.js";
|
|
12
12
|
import "./chunk-B67BK5GQ.js";
|
|
13
13
|
|
|
14
14
|
// src/engine/ongoing.ts
|
|
@@ -8,10 +8,10 @@ import {
|
|
|
8
8
|
describeSizeDoubt,
|
|
9
9
|
sizeRequest,
|
|
10
10
|
triageFinding
|
|
11
|
-
} from "./chunk-
|
|
12
|
-
import "./chunk-
|
|
11
|
+
} from "./chunk-WYQBRCKY.js";
|
|
12
|
+
import "./chunk-DRZSUQ7Q.js";
|
|
13
13
|
import "./chunk-2SVAHH5N.js";
|
|
14
|
-
import "./chunk-
|
|
14
|
+
import "./chunk-VPAWRRHL.js";
|
|
15
15
|
import "./chunk-DTWKSZXY.js";
|
|
16
16
|
import "./chunk-FFYBY2NA.js";
|
|
17
17
|
import "./chunk-PGOYDOI4.js";
|
|
@@ -3,11 +3,11 @@ import {
|
|
|
3
3
|
describeFix,
|
|
4
4
|
dirtyPaths,
|
|
5
5
|
runFix
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-KOWMHL23.js";
|
|
7
7
|
import {
|
|
8
8
|
buildAskUserTool,
|
|
9
9
|
respondIn
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-3UYA3KUG.js";
|
|
11
11
|
import {
|
|
12
12
|
buildRememberTool,
|
|
13
13
|
buildSkillTool,
|
|
@@ -22,11 +22,11 @@ import {
|
|
|
22
22
|
specsDir,
|
|
23
23
|
verifyPaths,
|
|
24
24
|
writeFileTool
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-RPVAIS3P.js";
|
|
26
26
|
import {
|
|
27
27
|
askInUserLanguage,
|
|
28
28
|
inUserLanguage
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-EQX7BQYN.js";
|
|
30
30
|
import "./chunk-FGVJFMK5.js";
|
|
31
31
|
import "./chunk-NNTIACT4.js";
|
|
32
32
|
import {
|
|
@@ -35,7 +35,7 @@ import {
|
|
|
35
35
|
import {
|
|
36
36
|
describeEscalation,
|
|
37
37
|
triageFinding
|
|
38
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-WYQBRCKY.js";
|
|
39
39
|
import {
|
|
40
40
|
BATCH_TOOLS_NOTE,
|
|
41
41
|
contextTools,
|
|
@@ -45,13 +45,13 @@ import {
|
|
|
45
45
|
projectToolsNote,
|
|
46
46
|
readFileTool,
|
|
47
47
|
reinforceUsed
|
|
48
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-DRZSUQ7Q.js";
|
|
49
49
|
import "./chunk-2SVAHH5N.js";
|
|
50
50
|
import {
|
|
51
51
|
ToolRegistry,
|
|
52
52
|
handedOver,
|
|
53
53
|
runToCompletion
|
|
54
|
-
} from "./chunk-
|
|
54
|
+
} from "./chunk-VPAWRRHL.js";
|
|
55
55
|
import "./chunk-DTWKSZXY.js";
|
|
56
56
|
import "./chunk-FFYBY2NA.js";
|
|
57
57
|
import {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hizliemre/horse-code",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Terminal coding agent: one sentence to reviewed, committed code — in its own git worktree",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Emre Hızlı <hizliemre@gmail.com>",
|
|
@@ -32,7 +32,8 @@
|
|
|
32
32
|
"node": ">=20"
|
|
33
33
|
},
|
|
34
34
|
"files": [
|
|
35
|
-
"dist"
|
|
35
|
+
"dist",
|
|
36
|
+
"skills"
|
|
36
37
|
],
|
|
37
38
|
"scripts": {
|
|
38
39
|
"build": "tsup",
|