@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 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-DKVIN43T.js";
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-OHYGVTQ4.js");
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-DKVIN43T.js";
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-SB2L34JW.js");
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-SB2L34JW.js");
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;
@@ -3,7 +3,7 @@ import {
3
3
  saveMcpServer,
4
4
  saveRoleSkills,
5
5
  saveSkillSource
6
- } from "./chunk-DKVIN43T.js";
6
+ } from "./chunk-4EWK7HWQ.js";
7
7
  import "./chunk-H2FDGPVW.js";
8
8
  export {
9
9
  removeSkillSource,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hizliemre/horse-code",
3
- "version": "0.1.0",
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",
@@ -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.