@hizliemre/horse-code 0.1.0 → 0.1.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.
- package/README.md +21 -0
- package/dist/{app-SB2L34JW.js → app-UGFQKMLX.js} +2 -2
- package/dist/chunk-4EWK7HWQ.js +177 -0
- package/dist/cli.js +5 -111
- package/dist/{save-skills-OHYGVTQ4.js → save-skills-NSLBU33X.js} +1 -1
- 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
package/README.md
CHANGED
|
@@ -108,6 +108,27 @@ Facts an agent learns — a command that only works from a subdirectory, a file
|
|
|
108
108
|
|
|
109
109
|
Each role is asked, at the close of its turn, whether anything cost it more than one attempt. That question is the difference between a run that learns and one that rediscovers.
|
|
110
110
|
|
|
111
|
+
## Skills
|
|
112
|
+
|
|
113
|
+
Six skills ship with the package. Each is a verbatim copy of its upstream, under `skills/`, in the same
|
|
114
|
+
format a project uses for its own — the only difference is who supplies them, and a project may replace any
|
|
115
|
+
of them by defining a skill of the same name in `.horsecode/skills/`.
|
|
116
|
+
|
|
117
|
+
| | |
|
|
118
|
+
|---|---|
|
|
119
|
+
| `brainstorming` | intent and requirements before implementation — bound to the brainstormer |
|
|
120
|
+
| `writing-plans` | what makes an individual task executable — bound to the task list |
|
|
121
|
+
| `test-driven-development` | inlined into every role that writes code |
|
|
122
|
+
| `frontend-design` | design direction, inlined into the design roles |
|
|
123
|
+
| `systematic-debugging` | fetched on demand, when something is stuck |
|
|
124
|
+
| `ui-ux-pro-max` | 161 palettes, 57 font pairings, 25 chart types across 10 stacks — fetched on demand |
|
|
125
|
+
|
|
126
|
+
A skill can also be **referenced** rather than copied, for the ones that are large, script-driven, or
|
|
127
|
+
maintained upstream. [`impeccable`](https://github.com/pbakaus/impeccable) ships that way: declared by
|
|
128
|
+
default, installed by `/skills update` into `~/.horsecode/skills/`, and updatable from its source. Startup
|
|
129
|
+
never waits on the network — installing is an explicit act. An explicitly stated `skillSources` list, an
|
|
130
|
+
empty one included, replaces the shipped default entirely.
|
|
131
|
+
|
|
111
132
|
## Tools an agent has
|
|
112
133
|
|
|
113
134
|
`read_file` `write_file` `edit_file` `grep` `glob` `shell` `git` (read-only) `git_write` `web_fetch` `ask_user` `remember_fact` `propose_memory` `skill` `find_tool` `find_unfinished`, plus the graph tools (`graph_overview`, `graph_find`, `graph_context`, `graph_impact`, `graph_trace`) and every tool exposed by a connected MCP server.
|
|
@@ -30,7 +30,7 @@ import "./chunk-HBSC2HT2.js";
|
|
|
30
30
|
import "./chunk-FGVJFMK5.js";
|
|
31
31
|
import {
|
|
32
32
|
saveRoleSkills
|
|
33
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-4EWK7HWQ.js";
|
|
34
34
|
import {
|
|
35
35
|
objectField,
|
|
36
36
|
patchConfig
|
|
@@ -5570,7 +5570,7 @@ Question: ${question}` }
|
|
|
5570
5570
|
};
|
|
5571
5571
|
const addMcp = async (input) => {
|
|
5572
5572
|
const { parseCommand, parseConfigBlock, extractFromPage, verify } = await import("./install-O34KMWJB.js");
|
|
5573
|
-
const { saveMcpServer } = await import("./save-skills-
|
|
5573
|
+
const { saveMcpServer } = await import("./save-skills-NSLBU33X.js");
|
|
5574
5574
|
let cand = parseCommand(input) ?? parseConfigBlock(input);
|
|
5575
5575
|
if (!cand) {
|
|
5576
5576
|
const url = input.trim();
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import {
|
|
2
|
+
arrayField,
|
|
3
|
+
objectField,
|
|
4
|
+
patchConfig
|
|
5
|
+
} from "./chunk-H2FDGPVW.js";
|
|
6
|
+
|
|
7
|
+
// src/config/config.ts
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
var UNSET_MODEL = "default";
|
|
10
|
+
var DEFAULT_CONFIG = {
|
|
11
|
+
baseUrl: "http://localhost:20128",
|
|
12
|
+
model: UNSET_MODEL,
|
|
13
|
+
// acceptEdits: auto-approve file writes/edits (the pipeline builds in an isolated worktree → reviewed as a
|
|
14
|
+
// PR), still prompt for shell/exec. Keeps the automated build flowing without an approval per file.
|
|
15
|
+
mode: "acceptEdits",
|
|
16
|
+
allowlist: [],
|
|
17
|
+
roles: {},
|
|
18
|
+
specKit: { version: "v0.13.2" },
|
|
19
|
+
mcp: {},
|
|
20
|
+
modelSources: [],
|
|
21
|
+
traceDir: "",
|
|
22
|
+
/**
|
|
23
|
+
* Shipped as a REFERENCE, not a copy, for the reasons skills/README.md gives for exactly this shape: it is
|
|
24
|
+
* 3.3 MB across 154 files, it carries its own scripts, and it is maintained upstream. Vendoring it would
|
|
25
|
+
* freeze it at the commit it was taken on and turn every upstream fix into a manual merge — and it would
|
|
26
|
+
* multiply the published package by ten for a skill most runs never open.
|
|
27
|
+
*
|
|
28
|
+
* A default source is not an install. Startup stays offline; `/skills update` is the explicit act that
|
|
29
|
+
* fetches it, and a user who removes this entry from their own config is not overruled — the two configs
|
|
30
|
+
* merge by name.
|
|
31
|
+
*/
|
|
32
|
+
skillSources: [{ name: "impeccable", repo: "pbakaus/impeccable", path: ".agents/skills/impeccable" }],
|
|
33
|
+
maxParallel: 8,
|
|
34
|
+
telemetry: true
|
|
35
|
+
};
|
|
36
|
+
var reviewerSchema = z.object({ name: z.string(), perspective: z.string(), models: z.array(z.string()) });
|
|
37
|
+
var fileSchema = z.object({
|
|
38
|
+
apiKey: z.string().optional(),
|
|
39
|
+
baseUrl: z.string().optional(),
|
|
40
|
+
model: z.string().optional(),
|
|
41
|
+
mode: z.enum(["ask", "acceptEdits", "auto"]).optional(),
|
|
42
|
+
allowlist: z.array(z.string()).optional(),
|
|
43
|
+
roles: z.record(
|
|
44
|
+
z.string(),
|
|
45
|
+
z.object({
|
|
46
|
+
models: z.array(z.string()),
|
|
47
|
+
systemPrompt: z.string().optional(),
|
|
48
|
+
skills: z.array(z.string()).optional(),
|
|
49
|
+
// Anthropic models only — see RoleConfig.effort. Unknown keys are stripped, so without this line a
|
|
50
|
+
// level written into the config would be silently discarded on the way in.
|
|
51
|
+
effort: z.enum(["low", "medium", "high", "xhigh", "max"]).optional()
|
|
52
|
+
})
|
|
53
|
+
).optional(),
|
|
54
|
+
// The review team's finder lenses, one set per stage (any omitted set falls back to the built-in default).
|
|
55
|
+
team: z.object({
|
|
56
|
+
spec: z.array(reviewerSchema).optional(),
|
|
57
|
+
plan: z.array(reviewerSchema).optional(),
|
|
58
|
+
code: z.array(reviewerSchema).optional()
|
|
59
|
+
}).optional(),
|
|
60
|
+
council: z.object({ members: z.array(reviewerSchema) }).optional(),
|
|
61
|
+
specKit: z.object({ version: z.string() }).optional(),
|
|
62
|
+
modelSources: z.array(z.string()).optional(),
|
|
63
|
+
traceDir: z.string().optional(),
|
|
64
|
+
// where /graph trace writes; empty = .horsecode/traces
|
|
65
|
+
mainBranch: z.string().optional(),
|
|
66
|
+
// the branch a resumed session syncs from; asked once, then remembered
|
|
67
|
+
// Bounded: below 1 nothing runs; above 32 the git merge lock, not the models, becomes the limit.
|
|
68
|
+
maxParallel: z.number().int().min(1).max(32).optional(),
|
|
69
|
+
telemetry: z.boolean().optional(),
|
|
70
|
+
skillSources: z.array(z.object({
|
|
71
|
+
name: z.string(),
|
|
72
|
+
repo: z.string(),
|
|
73
|
+
path: z.string().optional(),
|
|
74
|
+
ref: z.string().optional()
|
|
75
|
+
})).optional(),
|
|
76
|
+
mcp: z.record(
|
|
77
|
+
z.string(),
|
|
78
|
+
z.union([
|
|
79
|
+
z.object({ command: z.array(z.string()).min(1), env: z.record(z.string(), z.string()).optional(), readOnly: z.boolean().optional() }),
|
|
80
|
+
z.object({ url: z.string(), headers: z.record(z.string(), z.string()).optional(), readOnly: z.boolean().optional() })
|
|
81
|
+
])
|
|
82
|
+
).optional()
|
|
83
|
+
}).partial();
|
|
84
|
+
function parseFile(raw) {
|
|
85
|
+
if (!raw) return {};
|
|
86
|
+
try {
|
|
87
|
+
const parsed = fileSchema.safeParse(JSON.parse(raw));
|
|
88
|
+
return parsed.success ? parsed.data : {};
|
|
89
|
+
} catch {
|
|
90
|
+
return {};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function loadConfig(opts) {
|
|
94
|
+
const global = parseFile(opts.readFile(`${opts.home}/.horsecode/config.json`));
|
|
95
|
+
const project = parseFile(opts.readFile(`${opts.cwd}/.horsecode/config.json`));
|
|
96
|
+
const { apiKey: _leak, ...projectSafe } = project;
|
|
97
|
+
const merged = {
|
|
98
|
+
...DEFAULT_CONFIG,
|
|
99
|
+
...global,
|
|
100
|
+
...projectSafe
|
|
101
|
+
};
|
|
102
|
+
merged.allowlist = projectSafe.allowlist ?? global.allowlist ?? [];
|
|
103
|
+
merged.roles = { ...global.roles ?? {}, ...projectSafe.roles ?? {} };
|
|
104
|
+
merged.mcp = { ...global.mcp ?? {}, ...projectSafe.mcp ?? {} };
|
|
105
|
+
merged.modelSources = projectSafe.modelSources ?? global.modelSources ?? [];
|
|
106
|
+
merged.maxParallel = projectSafe.maxParallel ?? global.maxParallel ?? DEFAULT_CONFIG.maxParallel;
|
|
107
|
+
merged.telemetry = projectSafe.telemetry ?? global.telemetry ?? DEFAULT_CONFIG.telemetry;
|
|
108
|
+
const spoken = global.skillSources !== void 0 || projectSafe.skillSources !== void 0;
|
|
109
|
+
const byName = new Map((spoken ? [] : DEFAULT_CONFIG.skillSources).map((s) => [s.name, s]));
|
|
110
|
+
for (const s of global.skillSources ?? []) byName.set(s.name, s);
|
|
111
|
+
for (const s of projectSafe.skillSources ?? []) byName.set(s.name, s);
|
|
112
|
+
merged.skillSources = [...byName.values()];
|
|
113
|
+
merged.specKit = projectSafe.specKit ?? global.specKit ?? DEFAULT_CONFIG.specKit;
|
|
114
|
+
const team = {
|
|
115
|
+
spec: projectSafe.team?.spec ?? global.team?.spec,
|
|
116
|
+
plan: projectSafe.team?.plan ?? global.team?.plan,
|
|
117
|
+
code: projectSafe.team?.code ?? global.team?.code
|
|
118
|
+
};
|
|
119
|
+
merged.team = team.spec || team.plan || team.code ? team : void 0;
|
|
120
|
+
const councilMembers = projectSafe.council?.members ?? global.council?.members;
|
|
121
|
+
merged.council = councilMembers ? { members: councilMembers } : void 0;
|
|
122
|
+
if (opts.env.OMNIROUTE_API_KEY) merged.apiKey = opts.env.OMNIROUTE_API_KEY;
|
|
123
|
+
if (opts.env.OMNIROUTE_BASE_URL) merged.baseUrl = opts.env.OMNIROUTE_BASE_URL;
|
|
124
|
+
return merged;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// src/config/save-skills.ts
|
|
128
|
+
async function saveSkillSource(home, src) {
|
|
129
|
+
return patchConfig(home, (current) => {
|
|
130
|
+
if (current["skillSources"] === void 0) current = { ...current, skillSources: [...DEFAULT_CONFIG.skillSources] };
|
|
131
|
+
const sources = arrayField(current, "skillSources").filter(
|
|
132
|
+
(s) => !(typeof s === "object" && s !== null && s.name === src.name)
|
|
133
|
+
);
|
|
134
|
+
return { ...current, skillSources: [...sources, src] };
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
async function removeSkillSource(home, name) {
|
|
138
|
+
let found = false;
|
|
139
|
+
const ok = await patchConfig(home, (current) => {
|
|
140
|
+
const sources = arrayField(current, "skillSources");
|
|
141
|
+
const kept = sources.filter((s) => {
|
|
142
|
+
const match = typeof s === "object" && s !== null && s.name === name;
|
|
143
|
+
if (match) found = true;
|
|
144
|
+
return !match;
|
|
145
|
+
});
|
|
146
|
+
return found ? { ...current, skillSources: kept } : void 0;
|
|
147
|
+
});
|
|
148
|
+
return ok && found;
|
|
149
|
+
}
|
|
150
|
+
async function saveRoleSkills(home, assignments) {
|
|
151
|
+
const names = Object.keys(assignments);
|
|
152
|
+
if (!names.length) return 0;
|
|
153
|
+
const ok = await patchConfig(home, (current) => {
|
|
154
|
+
const roles = objectField(current, "roles");
|
|
155
|
+
for (const role of names) {
|
|
156
|
+
const prev = typeof roles[role] === "object" && roles[role] !== null ? roles[role] : {};
|
|
157
|
+
roles[role] = { ...prev, skills: [...assignments[role]] };
|
|
158
|
+
}
|
|
159
|
+
return { ...current, roles };
|
|
160
|
+
});
|
|
161
|
+
return ok ? names.length : 0;
|
|
162
|
+
}
|
|
163
|
+
async function saveMcpServer(home, name, spec) {
|
|
164
|
+
return patchConfig(home, (current) => ({
|
|
165
|
+
...current,
|
|
166
|
+
mcp: { ...objectField(current, "mcp"), [name]: spec }
|
|
167
|
+
}));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export {
|
|
171
|
+
UNSET_MODEL,
|
|
172
|
+
loadConfig,
|
|
173
|
+
saveSkillSource,
|
|
174
|
+
removeSkillSource,
|
|
175
|
+
saveRoleSkills,
|
|
176
|
+
saveMcpServer
|
|
177
|
+
};
|
package/dist/cli.js
CHANGED
|
@@ -42,8 +42,10 @@ 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,
|
|
@@ -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";
|
|
@@ -1256,7 +1150,7 @@ async function main(argv) {
|
|
|
1256
1150
|
const useTui = shouldUseTui(!!process.stdin.isTTY, !!process.stdout.isTTY, !!args.noTui);
|
|
1257
1151
|
if (!args.prompt) {
|
|
1258
1152
|
if (useTui) {
|
|
1259
|
-
const { runTuiRepl } = await import("./app-
|
|
1153
|
+
const { runTuiRepl } = await import("./app-UGFQKMLX.js");
|
|
1260
1154
|
const { fetchCatalog, makeProbe, discoverSources } = await import("./discover-5URG7C4J.js");
|
|
1261
1155
|
const { loadSourceCache, saveSourceCache } = await import("./source-cache-XEK5WN7I.js");
|
|
1262
1156
|
const manualSources = config.modelSources.length > 0;
|
|
@@ -1439,7 +1333,7 @@ _Committed with the repo, so every clone starts with them. Agents read one with
|
|
|
1439
1333
|
...args.revisionRounds !== void 0 && { revisionRounds: args.revisionRounds }
|
|
1440
1334
|
};
|
|
1441
1335
|
if (useTui) {
|
|
1442
|
-
const { runTui } = await import("./app-
|
|
1336
|
+
const { runTui } = await import("./app-UGFQKMLX.js");
|
|
1443
1337
|
const res = await runTui({ buildDeps, job });
|
|
1444
1338
|
console.log(renderResult(res));
|
|
1445
1339
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hizliemre/horse-code",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
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",
|
package/skills/README.md
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Bundled skills
|
|
2
|
+
|
|
3
|
+
Every `SKILL.md` here is a **verbatim copy** from upstream:
|
|
4
|
+
|
|
5
|
+
| skill | source | licence |
|
|
6
|
+
|---|---|---|
|
|
7
|
+
| `brainstorming` | superpowers 6.1.1 | plugin |
|
|
8
|
+
| `test-driven-development` | superpowers 6.1.1 | plugin |
|
|
9
|
+
| `writing-plans` | superpowers 6.1.1 | plugin |
|
|
10
|
+
| `systematic-debugging` | superpowers 6.1.1 | plugin |
|
|
11
|
+
| `frontend-design` | [anthropics/skills](https://github.com/anthropics/skills) | Apache-2.0 (`LICENSE.txt` shipped alongside) |
|
|
12
|
+
| `ui-ux-pro-max` | ui-ux-pro-max-skill 2.0.1 | MIT © Next Level Builder (`LICENSE.txt` shipped alongside) |
|
|
13
|
+
|
|
14
|
+
Do not edit them. Byte-identical is what makes them re-syncable when the upstream skill changes — a local
|
|
15
|
+
tweak would either be silently overwritten on the next sync, or quietly diverge and stay.
|
|
16
|
+
|
|
17
|
+
| skill | attached to | how |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| `brainstorming` | `brainstormer` | mandatory |
|
|
20
|
+
| `test-driven-development` | `coder`, `senior-coder` | mandatory |
|
|
21
|
+
| `writing-plans` | `project-manager` | mandatory |
|
|
22
|
+
| `systematic-debugging` | — | discoverable |
|
|
23
|
+
| `frontend-design` | `designer`, `senior-designer` | mandatory |
|
|
24
|
+
| `ui-ux-pro-max` | — | discoverable |
|
|
25
|
+
|
|
26
|
+
`ui-ux-pro-max` is discoverable rather than mandatory because of what it is: 43.7 KB of searchable database —
|
|
27
|
+
161 palettes, 57 font pairings, 161 product types, 25 chart types — and not a method. `frontend-design` is the
|
|
28
|
+
method the design roles need on every call, and it is small enough to inline. Paying 43.7 KB on every designer
|
|
29
|
+
prompt for a lookup table most calls never open is the trade the discoverable listing exists to avoid.
|
|
30
|
+
|
|
31
|
+
**Mandatory** skills are inlined into the role's system prompt (`applySkills`). **Discoverable** ones appear
|
|
32
|
+
only as a one-line entry in the listing every role receives, and are fetched on demand with the `skill` tool —
|
|
33
|
+
right for guidance that is only needed when something is stuck, and wasteful to inline into every prompt.
|
|
34
|
+
|
|
35
|
+
## Where the horse-code specifics live
|
|
36
|
+
|
|
37
|
+
Each skill describes a METHOD, and parts of it name conventions from its original habitat that do not exist
|
|
38
|
+
here: `docs/superpowers/…` output paths, "dispatch a subagent", "invoke the writing-plans skill", a
|
|
39
|
+
browser-based visual companion, TodoWrite task lists.
|
|
40
|
+
|
|
41
|
+
Those are mapped onto this pipeline in the **role prompts** (`src/prompts.ts`), never by editing a skill:
|
|
42
|
+
|
|
43
|
+
- the skill is the authority on *how the work is done*,
|
|
44
|
+
- the role prompt is the authority on *where the output goes, what this pipeline already owns, and what
|
|
45
|
+
happens next*.
|
|
46
|
+
|
|
47
|
+
Notably `writing-plans` is bound to the TASKS stage, not the plan stage. spec-kit's own `plan` template
|
|
48
|
+
already governs `plan.md` (Technical Context, Constitution Check, Project Structure); a second competing
|
|
49
|
+
template there would fight it. What spec-kit's *tasks* template does not supply is what makes an individual
|
|
50
|
+
task executable — exact paths, a real test cycle, no placeholders — and that is what the skill contributes.
|
|
51
|
+
|
|
52
|
+
## Overriding
|
|
53
|
+
|
|
54
|
+
A project may replace any of these by defining a skill of the same name in `<project>/.horsecode/skills/`.
|
|
55
|
+
Built-ins load first and the registry is keyed by name, so the project's version wins.
|
|
56
|
+
|
|
57
|
+
## Dispatcher skills
|
|
58
|
+
|
|
59
|
+
A skill may be a **dispatcher**: a small `SKILL.md` that routes to sibling documents ("see
|
|
60
|
+
`reference/critique.md`"). The loader records each skill's directory, and the `skill` tool reads those
|
|
61
|
+
documents on demand — `skill({name, file})`.
|
|
62
|
+
|
|
63
|
+
On demand is the point. A dispatcher's entry point is small enough to sit in a prompt while its reference
|
|
64
|
+
tree can run to megabytes; loading the tree up front would cost far more on every call than the guidance is
|
|
65
|
+
worth on the rare call that needs it. Paths are contained to the skill's own directory and capped in size.
|
|
66
|
+
|
|
67
|
+
## Installed from upstream, not copied
|
|
68
|
+
|
|
69
|
+
A skill can also be **referenced** instead of vendored. Declare it in `config.skillSources`:
|
|
70
|
+
|
|
71
|
+
```json
|
|
72
|
+
"skillSources": [
|
|
73
|
+
{ "name": "impeccable", "repo": "pbakaus/impeccable", "path": ".agents/skills/impeccable" }
|
|
74
|
+
]
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
That entry is not an example: `impeccable` ships this way, declared in `DEFAULT_CONFIG.skillSources`. It is
|
|
78
|
+
3.3 MB across 154 files and drives its own browser scripts, which is precisely the case this section
|
|
79
|
+
describes — and vendoring it would have multiplied the published package by ten for guidance most runs never
|
|
80
|
+
open.
|
|
81
|
+
|
|
82
|
+
A default nobody can turn off would be a worse deal than no default, so an explicitly stated list wins
|
|
83
|
+
outright, an empty one included. "No skill sources" and "never said" mean different things, and only the
|
|
84
|
+
second falls back — the same distinction `saveRoleSkills` draws for role skills. Installing your first source
|
|
85
|
+
writes the shipped ones into your config alongside it, so nothing disappears behind your back; removing one
|
|
86
|
+
afterwards is then a real act with a real result.
|
|
87
|
+
|
|
88
|
+
`/skills update` installs or refreshes them into `~/.horsecode/skills/<name>/` — outside this repo, because
|
|
89
|
+
they are not ours. The commit is recorded beside each one, so an update knows whether anything actually
|
|
90
|
+
changed and a re-run costs nothing when it has not.
|
|
91
|
+
|
|
92
|
+
This is the right shape for a skill that is large, maintained upstream, or carries its own scripts. Copying
|
|
93
|
+
one here would freeze it at the moment it was taken and make every upstream fix a manual merge. The whole
|
|
94
|
+
subtree is installed, not just SKILL.md — a dispatcher's reference documents and any scripts it drives are
|
|
95
|
+
part of the skill, and having them at a real path is what lets a script-driven skill run at all. Each skill's
|
|
96
|
+
base directory is stated to the agent for exactly that reason.
|
|
97
|
+
|
|
98
|
+
Startup never waits on the network: loading is offline, installing is an explicit act.
|
|
99
|
+
|
|
100
|
+
**`impeccable`** is installed this way. It is not a prose skill — its SKILL.md dispatches to ~40 reference
|
|
101
|
+
documents and drives its own `scripts/*.mjs`. It is discoverable rather than attached to a role: its own
|
|
102
|
+
description says when it applies ("Not for backend-only or non-UI tasks"), and a 10 KB script-driven entry
|
|
103
|
+
point does not belong in every designer prompt.
|
|
104
|
+
|
|
105
|
+
## Still not adopted, and why
|
|
106
|
+
|
|
107
|
+
- [`nextlevelbuilder/ui-ux-pro-max-skill`](https://github.com/nextlevelbuilder/ui-ux-pro-max-skill) (MIT) —
|
|
108
|
+
not one skill but seven, and two of them (`ui-styling` at 5.7 MB, `ui-ux-pro-max` at 1.7 MB) are libraries.
|
|
109
|
+
Cherry-picking a single self-contained one (e.g. `design-system`) is the only sane path.
|
|
110
|
+
- [`AccessLint/skills`](https://github.com/AccessLint/skills) — a plugin of three skills that additionally
|
|
111
|
+
depend on an MCP server (`mcp__accesslint__*`) and the `@accesslint/cli`. It also carries **no licence**,
|
|
112
|
+
which has to be settled before any of it is vendored here.
|
|
113
|
+
|
|
114
|
+
## Not adopted
|
|
115
|
+
|
|
116
|
+
`using-git-worktrees`, `subagent-driven-development`, `executing-plans`, `dispatching-parallel-agents` and
|
|
117
|
+
`finishing-a-development-branch` describe work this engine already does itself (worktree lifecycle, the wave
|
|
118
|
+
engine, the review ladder, the PR flow). Shipping them would put two systems in charge of the same thing.
|
|
119
|
+
|
|
120
|
+
`requesting-code-review` is likewise superseded: this pipeline runs its own staged review (per-stage finder
|
|
121
|
+
lenses → council → judge).
|
|
122
|
+
|
|
123
|
+
Only `SKILL.md` is read by the loader; every other file in these directories is documentation.
|