@compilr-dev/sdk 0.27.0 → 0.29.0
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/dist/guide/shared-content.js +42 -9
- package/dist/index.d.ts +6 -0
- package/dist/index.js +3 -0
- package/dist/skills/canvas-macros.js +5 -5
- package/dist/skills/folder.d.ts +34 -0
- package/dist/skills/folder.js +70 -0
- package/dist/skills/index.d.ts +6 -0
- package/dist/skills/index.js +3 -0
- package/dist/skills/loader.js +40 -2
- package/dist/skills/macro-invocation.d.ts +51 -17
- package/dist/skills/macro-invocation.js +249 -26
- package/dist/skills/operations.d.ts +5 -0
- package/dist/skills/operations.js +50 -8
- package/dist/skills/patch.d.ts +25 -0
- package/dist/skills/patch.js +91 -0
- package/dist/skills/prompt-resolver.js +5 -2
- package/dist/skills/reachability.d.ts +36 -0
- package/dist/skills/reachability.js +41 -0
- package/dist/skills/resolver.js +8 -1
- package/dist/skills/software-macros.js +27 -27
- package/dist/skills/types.d.ts +32 -0
- package/package.json +3 -2
|
@@ -115,20 +115,53 @@ The delegated agent runs independently and returns results to the coordinator.`,
|
|
|
115
115
|
},
|
|
116
116
|
// ── Skills ──────────────────────────────────────────────────────────────
|
|
117
117
|
{
|
|
118
|
-
id: '
|
|
119
|
-
title: '
|
|
120
|
-
keywords: ['skills', 'slash', 'command', '/'],
|
|
121
|
-
content: `
|
|
118
|
+
id: 'macros-overview',
|
|
119
|
+
title: 'Macros Overview',
|
|
120
|
+
keywords: ['macros', 'macro', 'skills', 'slash', 'command', '/'],
|
|
121
|
+
content: `Macros are pre-built prompts that guide the agent through specific workflows. You
|
|
122
|
+
invoke one; it expands into a message.
|
|
122
123
|
|
|
123
|
-
Use them by typing / followed by the
|
|
124
|
+
Use them by typing / followed by the macro name (e.g., /design, /build).
|
|
124
125
|
|
|
125
|
-
|
|
126
|
+
Macros vary by project type. Software projects have: /design, /sketch, /prd, /architecture, /scaffold, /build, /refine, /session-notes.
|
|
126
127
|
|
|
127
128
|
Research projects have: /research-scaffold, /outline, /literature-review, /draft-section, /peer-review.
|
|
128
129
|
|
|
129
130
|
Business projects have: /business-vision, /market-analysis, /competitor-analysis, /financial-model, /pitch-outline, /business-review.
|
|
130
131
|
|
|
131
|
-
|
|
132
|
+
Macros can be combined with your own instructions: type /design then add context.
|
|
133
|
+
|
|
134
|
+
**Macros are not Skills.** A macro is text YOU trigger. A Skill is a capability the MODEL
|
|
135
|
+
chooses from a catalogue and loads when it decides it is relevant — see "Skills". They were
|
|
136
|
+
called the same thing until recently, which is why older notes use "skill" for both.`,
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
id: 'skills',
|
|
140
|
+
title: 'Skills — Capabilities the Model Chooses',
|
|
141
|
+
keywords: ['skill', 'skills', 'SKILL.md', 'anthropic', 'agent skills', 'catalogue', 'grant'],
|
|
142
|
+
content: `A Skill is a capability the MODEL picks up when it decides the work calls for it —
|
|
143
|
+
not something you type.
|
|
144
|
+
|
|
145
|
+
Each agent is shown a CATALOGUE: one line per skill, name and description only. When the agent
|
|
146
|
+
judges a skill relevant it loads the body then, and not before, so an unused skill costs a line
|
|
147
|
+
of context rather than a document.
|
|
148
|
+
|
|
149
|
+
**Where they live**
|
|
150
|
+
- User scope: ~/.compilr-dev/skills/<name>/SKILL.md — personal, every project
|
|
151
|
+
- Project scope: <project>/.compilr/skills/<name>/SKILL.md — shared via version control
|
|
152
|
+
|
|
153
|
+
The format is Anthropic's Agent Skills format: YAML frontmatter (name, description) and a
|
|
154
|
+
markdown body. We read the frontmatter and the body; bundled references/, scripts/, templates/
|
|
155
|
+
and assets/ are not read yet.
|
|
156
|
+
|
|
157
|
+
**Granting them per agent**
|
|
158
|
+
In the agent editor's Skills section, tick the skills an agent may choose from. An agent you have
|
|
159
|
+
never restricted may choose any installed skill; once you save a selection it is limited to
|
|
160
|
+
exactly what is ticked, and an empty selection grants none.
|
|
161
|
+
|
|
162
|
+
**Skills are not macros.** A macro (/design, /build) is text YOU trigger. A skill is something
|
|
163
|
+
the model reaches for on its own. A skill you install is also typeable as a slash command, so one
|
|
164
|
+
folder can serve both purposes — see "Custom Skills".`,
|
|
132
165
|
},
|
|
133
166
|
{
|
|
134
167
|
id: 'custom-skills',
|
|
@@ -157,8 +190,8 @@ A folder with a SKILL.md file containing YAML frontmatter (name, description) an
|
|
|
157
190
|
|
|
158
191
|
**Desktop:** The Agents panel has a Skills section showing custom skills. Click the maximize button to open the SkillsView tab with full management (create, fork, bind/unbind, enable/disable, delete). Click a skill name to view/edit in the code editor.
|
|
159
192
|
|
|
160
|
-
**Forking
|
|
161
|
-
You cannot override
|
|
193
|
+
**Forking shipped macros:**
|
|
194
|
+
You cannot override a shipped macro directly — its name is reserved, so creating one is refused. Fork it instead: that copies it, and you edit the copy. Then bind a slash command to your fork (e.g., /design → design-acme).
|
|
162
195
|
|
|
163
196
|
**Bindings:**
|
|
164
197
|
Bindings remap slash commands to custom skills. Example: bind /design to your forked design-acme skill. When you type /design, it uses your custom prompt instead of the SDK version. Use the Bindings tab/section to manage them.
|
package/dist/index.d.ts
CHANGED
|
@@ -73,6 +73,12 @@ export type { SkillScope, SkillPromptResolution, AvailableSkillEntry, SkillSourc
|
|
|
73
73
|
export { resolveSkillPrompt, getAllAvailableSkills } from './skills/index.js';
|
|
74
74
|
export { buildMacroList } from './skills/index.js';
|
|
75
75
|
export { buildMacroInvocation } from './skills/index.js';
|
|
76
|
+
export { skillReachability, isUnreachable } from './skills/index.js';
|
|
77
|
+
export { patchSkillFrontmatter } from './skills/index.js';
|
|
78
|
+
export { readSkillFolder, unreadSkillFiles } from './skills/index.js';
|
|
79
|
+
export type { SkillFolderEntry } from './skills/index.js';
|
|
80
|
+
export type { FrontmatterPatch } from './skills/index.js';
|
|
81
|
+
export type { SkillReachability } from './skills/index.js';
|
|
76
82
|
export type { MacroInvocation, MacroInvocationContext, MacroProjectContext, } from './skills/index.js';
|
|
77
83
|
export type { MacroListEntry, BuildMacroListOptions } from './skills/index.js';
|
|
78
84
|
export { generateSkillCatalog, toCatalogEntry, createLoadSkillTool } from './skills/index.js';
|
package/dist/index.js
CHANGED
|
@@ -156,6 +156,9 @@ export { RESERVED_MACRO_NAMES, isReservedMacroName, parseSkillMarkdown, loadSkil
|
|
|
156
156
|
export { resolveSkillPrompt, getAllAvailableSkills } from './skills/index.js';
|
|
157
157
|
export { buildMacroList } from './skills/index.js';
|
|
158
158
|
export { buildMacroInvocation } from './skills/index.js';
|
|
159
|
+
export { skillReachability, isUnreachable } from './skills/index.js';
|
|
160
|
+
export { patchSkillFrontmatter } from './skills/index.js';
|
|
161
|
+
export { readSkillFolder, unreadSkillFiles } from './skills/index.js';
|
|
159
162
|
export { generateSkillCatalog, toCatalogEntry, createLoadSkillTool } from './skills/index.js';
|
|
160
163
|
export { platformMacros, designMacro, sketchMacro, prdMacro, refineMacro, refineItemMacro, architectureMacro, sessionNotesMacro, buildMacro, scaffoldMacro, outlineMacro, literatureReviewMacro, draftSectionMacro, peerReviewMacro, researchScaffoldMacro, businessVisionMacro, marketAnalysisMacro, competitorAnalysisMacro, financialModelMacro, pitchOutlineMacro, businessReviewMacro, brandSetupMacro, contentStrategyMacro, contentCalendarMacro, createContentMacro, contentReviewMacro, curriculumDesignMacro, lessonPlanMacro, assessmentDesignMacro, courseReviewMacro, bookOutlineMacro, characterDesignMacro, plotThreadsMacro, sceneBreakdownMacro, bookReviewMacro, } from './skills/index.js';
|
|
161
164
|
// =============================================================================
|
|
@@ -31,7 +31,7 @@ export const canvasMacro = defineSkill({
|
|
|
31
31
|
- **carousel** — multiple slide "sheets"; author each slide as a top-level \`<section data-sheet>…</section>\`.
|
|
32
32
|
- **board** — an infinite 2D coordinate space of absolutely-positioned node cards + inline-SVG connectors (mind-map / diagram / UI sketch). Wrap in \`<div data-board style="position:relative;width:Wpx;height:Hpx">\`.
|
|
33
33
|
|
|
34
|
-
**Once you've picked the type, consult its craft guide for the design rules that make it good** — the \`infographic\`
|
|
34
|
+
**Once you've picked the type, consult its craft guide for the design rules that make it good** — the \`infographic\` macro (hierarchy, grid, stat tiles, restrained color, correct charts), the \`carousel\` macro (one idea per slide, narrative arc, consistent master layout, 16:9 discipline), or the \`board\` macro (declared coordinate space, absolutely-positioned node cards, inline-SVG connectors, clustering). This general macro covers the mechanics (tools, structure, theme, Tweaks); the per-type macro covers the craft.
|
|
35
35
|
|
|
36
36
|
## STEPS
|
|
37
37
|
|
|
@@ -89,7 +89,7 @@ stored and what the markup actually uses.
|
|
|
89
89
|
- Do NOT include \`<html>\`, \`<head>\`, \`<meta>\`, \`<!doctype>\`, or your own CSP — the host injects the sandbox and CSP. Just the body content + a \`<style>\` and optional \`<script>\`.
|
|
90
90
|
- The sandbox is \`allow-scripts\` with NO same-origin and CSP \`default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data: blob:\`. So: inline \`<style>\`/\`<script>\` only, no external fonts/CSS/JS/network, no remote images (use inline SVG or data: URIs).
|
|
91
91
|
- **Match the app theme by default.** The host injects its live theme palette as CSS variables you MUST use instead of hardcoded colors: \`var(--canvas-bg)\` (page background), \`var(--canvas-fg)\` (text), \`var(--canvas-accent)\` and \`var(--canvas-secondary)\` (highlights), \`var(--canvas-muted)\` (secondary text), \`var(--canvas-border)\` (lines), \`var(--canvas-card)\` (raised surfaces). Only hardcode specific colors when the user explicitly asks for a particular palette or brand.
|
|
92
|
-
- **Background — infographic & carousel: always set it EXPLICITLY to \`var(--canvas-bg)\`** on your outermost wrapper (and each \`<section data-sheet>\` for a carousel). Do NOT use \`background: transparent\` and do NOT hardcode a fixed color like \`#0a0a0a\`: transparent looks fine in-app but **exports to PDF/HTML on WHITE**, and a fixed color ignores the theme. **EXCEPTION — infinite board: do the OPPOSITE.** Keep the \`data-board\` root **transparent** (do not fill it) — the host paints a dot-grid backdrop that must show through, and filling it turns the infinite surface into a finite rectangle. On a board you style only the node cards; the space between them stays transparent. (See the \`board\`
|
|
92
|
+
- **Background — infographic & carousel: always set it EXPLICITLY to \`var(--canvas-bg)\`** on your outermost wrapper (and each \`<section data-sheet>\` for a carousel). Do NOT use \`background: transparent\` and do NOT hardcode a fixed color like \`#0a0a0a\`: transparent looks fine in-app but **exports to PDF/HTML on WHITE**, and a fixed color ignores the theme. **EXCEPTION — infinite board: do the OPPOSITE.** Keep the \`data-board\` root **transparent** (do not fill it) — the host paints a dot-grid backdrop that must show through, and filling it turns the infinite surface into a finite rectangle. On a board you style only the node cards; the space between them stays transparent. (See the \`board\` macro.)
|
|
93
93
|
- Make it look intentional: a clear grid, strong type scale, generous spacing. Use \`var(--canvas-accent)\` sparingly for emphasis. Prefer inline SVG for shapes/charts/icons (fill/stroke with the theme vars).
|
|
94
94
|
- For a **carousel**, wrap each slide in \`<section data-sheet>…</section>\` — a natural unit to add one per \`canvas_edit\` append.
|
|
95
95
|
|
|
@@ -201,7 +201,7 @@ export const infographicMacro = defineSkill({
|
|
|
201
201
|
- Inline **SVG**, filled/stroked with the theme vars, a consistent stroke width. No emoji as primary iconography.
|
|
202
202
|
|
|
203
203
|
## Before you finish
|
|
204
|
-
- Set the background explicitly to \`var(--canvas-bg)\` (see the canvas
|
|
204
|
+
- Set the background explicitly to \`var(--canvas-bg)\` (see the canvas macro's export note).
|
|
205
205
|
- Add 2–4 **bound** Tweaks (accent color, headline size, a section toggle) — declared AND referenced in the HTML.
|
|
206
206
|
|
|
207
207
|
## Anti-patterns (fix on sight)
|
|
@@ -242,7 +242,7 @@ Structure the sequence like a story, not a list:
|
|
|
242
242
|
- The same card/grid system reused slide to slide.
|
|
243
243
|
|
|
244
244
|
## Before you finish
|
|
245
|
-
- Set \`background: var(--canvas-bg)\` on EACH \`<section data-sheet>\` (see the canvas
|
|
245
|
+
- Set \`background: var(--canvas-bg)\` on EACH \`<section data-sheet>\` (see the canvas macro's export note — transparent slides export white).
|
|
246
246
|
- Add 2–4 **bound** Tweaks (accent color, title size, a toggle for footer/page numbers).
|
|
247
247
|
|
|
248
248
|
## Anti-patterns (fix on sight)
|
|
@@ -335,7 +335,7 @@ export const canvasStylesMacro = defineSkill({
|
|
|
335
335
|
prompt: `The STYLE CATALOGUE for canvases — pick a coherent visual aesthetic instead of defaulting to the flat theme look. Design guidance; author with the canvas tools (see the \`canvas\` skill for mechanics). Applies to **infographic** and **carousel** (a board is a diagram surface — keep it theme-flat unless asked).
|
|
336
336
|
|
|
337
337
|
## How to use it
|
|
338
|
-
- **Offer during the brief, don't over-ask.** In the canvas
|
|
338
|
+
- **Offer during the brief, don't over-ask.** In the canvas macro's Step 1, if the user hasn't named a style, include a SHORT aesthetic menu (2–4 fitting options from below) in the SAME \`propose_alternatives\`/\`ask_user\` round — never a separate interview. If they said "surprise me / your call", just pick the one that best fits the content and build.
|
|
339
339
|
- **A style is a SYSTEM, not a trick.** Surfaces, borders, shadows, type, spacing, and accent all move together. Apply the whole recipe — half a recipe reads as a mistake.
|
|
340
340
|
|
|
341
341
|
## Hard constraints (every style — the sandbox is strict)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What else is in a skill's folder — and what we do not read.
|
|
3
|
+
*
|
|
4
|
+
* ⚠️ A SKILL IS A FOLDER, NOT A FILE, AND WE ONLY READ ONE FILE OF IT. Of 50 Anthropic-authored
|
|
5
|
+
* skills, 16 ship `references/` and 11 ship `scripts/`. That is progressive disclosure: the body
|
|
6
|
+
* stays short precisely because it points at those files, loaded only when the model needs them.
|
|
7
|
+
*
|
|
8
|
+
* We read frontmatter and body. Nothing else. So an installed third-party skill half-works and
|
|
9
|
+
* says nothing about it — the author's instructions reference material that never arrives.
|
|
10
|
+
*
|
|
11
|
+
* Listing them is the interim fix; reading them is the real one. Surfacing the gap is the point of
|
|
12
|
+
* this module: a host can say "points at 3 files we do not load" instead of leaving the user to
|
|
13
|
+
* discover it from a confused agent.
|
|
14
|
+
*/
|
|
15
|
+
export interface SkillFolderEntry {
|
|
16
|
+
/** Path relative to the skill folder, e.g. `references/api.md`. */
|
|
17
|
+
path: string;
|
|
18
|
+
/** Which convention it belongs to. */
|
|
19
|
+
kind: 'reference' | 'script' | 'asset' | 'template' | 'other';
|
|
20
|
+
bytes: number;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Everything in the skill folder besides SKILL.md.
|
|
24
|
+
*
|
|
25
|
+
* Returns [] when the folder is unreadable or holds only SKILL.md — an absent folder is the normal
|
|
26
|
+
* case for a skill someone just created, not an error.
|
|
27
|
+
*/
|
|
28
|
+
export declare function readSkillFolder(skillDir: string): SkillFolderEntry[];
|
|
29
|
+
/**
|
|
30
|
+
* The entries a host should warn about: material the skill's own body points at and we never load.
|
|
31
|
+
*
|
|
32
|
+
* `LICENSE.txt` and the like are not this — they are not instructions the agent was meant to read.
|
|
33
|
+
*/
|
|
34
|
+
export declare function unreadSkillFiles(entries: SkillFolderEntry[]): SkillFolderEntry[];
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What else is in a skill's folder — and what we do not read.
|
|
3
|
+
*
|
|
4
|
+
* ⚠️ A SKILL IS A FOLDER, NOT A FILE, AND WE ONLY READ ONE FILE OF IT. Of 50 Anthropic-authored
|
|
5
|
+
* skills, 16 ship `references/` and 11 ship `scripts/`. That is progressive disclosure: the body
|
|
6
|
+
* stays short precisely because it points at those files, loaded only when the model needs them.
|
|
7
|
+
*
|
|
8
|
+
* We read frontmatter and body. Nothing else. So an installed third-party skill half-works and
|
|
9
|
+
* says nothing about it — the author's instructions reference material that never arrives.
|
|
10
|
+
*
|
|
11
|
+
* Listing them is the interim fix; reading them is the real one. Surfacing the gap is the point of
|
|
12
|
+
* this module: a host can say "points at 3 files we do not load" instead of leaving the user to
|
|
13
|
+
* discover it from a confused agent.
|
|
14
|
+
*/
|
|
15
|
+
import { readdirSync, statSync } from 'node:fs';
|
|
16
|
+
import { join } from 'node:path';
|
|
17
|
+
const KIND_BY_DIR = {
|
|
18
|
+
references: 'reference',
|
|
19
|
+
scripts: 'script',
|
|
20
|
+
assets: 'asset',
|
|
21
|
+
templates: 'template',
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Everything in the skill folder besides SKILL.md.
|
|
25
|
+
*
|
|
26
|
+
* Returns [] when the folder is unreadable or holds only SKILL.md — an absent folder is the normal
|
|
27
|
+
* case for a skill someone just created, not an error.
|
|
28
|
+
*/
|
|
29
|
+
export function readSkillFolder(skillDir) {
|
|
30
|
+
const out = [];
|
|
31
|
+
const walk = (dir, prefix, kind) => {
|
|
32
|
+
let names;
|
|
33
|
+
try {
|
|
34
|
+
names = readdirSync(dir);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
for (const name of names) {
|
|
40
|
+
const full = join(dir, name);
|
|
41
|
+
const rel = prefix ? `${prefix}/${name}` : name;
|
|
42
|
+
try {
|
|
43
|
+
const st = statSync(full);
|
|
44
|
+
if (st.isDirectory()) {
|
|
45
|
+
walk(full, rel, KIND_BY_DIR[name] ?? kind);
|
|
46
|
+
}
|
|
47
|
+
else if (rel !== 'SKILL.md') {
|
|
48
|
+
out.push({
|
|
49
|
+
path: rel,
|
|
50
|
+
kind: prefix ? kind : (KIND_BY_DIR[name] ?? 'other'),
|
|
51
|
+
bytes: st.size,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// a file that vanished between readdir and stat is not worth failing a listing for
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
walk(skillDir, '', 'other');
|
|
61
|
+
return out.sort((a, b) => a.path.localeCompare(b.path));
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The entries a host should warn about: material the skill's own body points at and we never load.
|
|
65
|
+
*
|
|
66
|
+
* `LICENSE.txt` and the like are not this — they are not instructions the agent was meant to read.
|
|
67
|
+
*/
|
|
68
|
+
export function unreadSkillFiles(entries) {
|
|
69
|
+
return entries.filter((e) => e.kind === 'reference' || e.kind === 'script' || e.kind === 'template');
|
|
70
|
+
}
|
package/dist/skills/index.d.ts
CHANGED
|
@@ -21,3 +21,9 @@ export { buildMacroList } from './macro-list.js';
|
|
|
21
21
|
export type { MacroListEntry, BuildMacroListOptions } from './macro-list.js';
|
|
22
22
|
export { buildMacroInvocation } from './macro-invocation.js';
|
|
23
23
|
export type { MacroInvocation, MacroInvocationContext, MacroProjectContext, } from './macro-invocation.js';
|
|
24
|
+
export { skillReachability, isUnreachable } from './reachability.js';
|
|
25
|
+
export type { SkillReachability } from './reachability.js';
|
|
26
|
+
export { patchSkillFrontmatter } from './patch.js';
|
|
27
|
+
export type { FrontmatterPatch } from './patch.js';
|
|
28
|
+
export { readSkillFolder, unreadSkillFiles } from './folder.js';
|
|
29
|
+
export type { SkillFolderEntry } from './folder.js';
|
package/dist/skills/index.js
CHANGED
|
@@ -9,3 +9,6 @@ export { resolveSkillPrompt, getAllAvailableSkills } from './prompt-resolver.js'
|
|
|
9
9
|
export { platformMacros, designMacro, sketchMacro, prdMacro, refineMacro, refineItemMacro, architectureMacro, sessionNotesMacro, buildMacro, scaffoldMacro, outlineMacro, literatureReviewMacro, draftSectionMacro, peerReviewMacro, researchScaffoldMacro, businessVisionMacro, marketAnalysisMacro, competitorAnalysisMacro, financialModelMacro, pitchOutlineMacro, businessReviewMacro, brandSetupMacro, contentStrategyMacro, contentCalendarMacro, createContentMacro, contentReviewMacro, curriculumDesignMacro, lessonPlanMacro, assessmentDesignMacro, courseReviewMacro, bookOutlineMacro, characterDesignMacro, plotThreadsMacro, sceneBreakdownMacro, bookReviewMacro, } from './platform-macros.js';
|
|
10
10
|
export { buildMacroList } from './macro-list.js';
|
|
11
11
|
export { buildMacroInvocation } from './macro-invocation.js';
|
|
12
|
+
export { skillReachability, isUnreachable } from './reachability.js';
|
|
13
|
+
export { patchSkillFrontmatter } from './patch.js';
|
|
14
|
+
export { readSkillFolder, unreadSkillFiles } from './folder.js';
|
package/dist/skills/loader.js
CHANGED
|
@@ -31,9 +31,19 @@ export function parseSkillMarkdown(content, sourcePath, source) {
|
|
|
31
31
|
if (!meta)
|
|
32
32
|
return null;
|
|
33
33
|
const name = typeof meta['name'] === 'string' ? meta['name'] : null;
|
|
34
|
-
|
|
35
|
-
if (!name || !description)
|
|
34
|
+
if (!name)
|
|
36
35
|
return null;
|
|
36
|
+
/*
|
|
37
|
+
⚠️ AN EMPTY DESCRIPTION PARSES. It used to return null, which meant a skill with no description
|
|
38
|
+
was not "incomplete" — it was INVISIBLE, absent from every list with nothing to explain why.
|
|
39
|
+
Since the template now ships `description: ''` deliberately (never pre-fill a field that would
|
|
40
|
+
be valid if saved), refusing to parse it would hide every newly created skill.
|
|
41
|
+
|
|
42
|
+
So it loads, and shows as `not set`. What it must NOT do is reach the model: a catalogue entry
|
|
43
|
+
with nothing to match on is worse than no entry at all — see generateSkillCatalog and
|
|
44
|
+
resolveSkillsForAgent, which both drop it.
|
|
45
|
+
*/
|
|
46
|
+
const description = typeof meta['description'] === 'string' ? meta['description'] : '';
|
|
37
47
|
const skill = {
|
|
38
48
|
name,
|
|
39
49
|
description,
|
|
@@ -49,6 +59,34 @@ export function parseSkillMarkdown(content, sourcePath, source) {
|
|
|
49
59
|
skill.tags = meta['tags'].filter((t) => typeof t === 'string');
|
|
50
60
|
if (typeof meta['enabled'] === 'boolean')
|
|
51
61
|
skill.enabled = meta['enabled'];
|
|
62
|
+
/*
|
|
63
|
+
The Anthropic invocation fields. Hyphenated on disk, camelCase in the type — the file format is
|
|
64
|
+
theirs and we do not get to rename it, so the mapping lives here and nowhere else.
|
|
65
|
+
*/
|
|
66
|
+
if (typeof meta['disable-model-invocation'] === 'boolean') {
|
|
67
|
+
skill.disableModelInvocation = meta['disable-model-invocation'];
|
|
68
|
+
}
|
|
69
|
+
if (typeof meta['user-invocable'] === 'boolean')
|
|
70
|
+
skill.userInvocable = meta['user-invocable'];
|
|
71
|
+
if (typeof meta['argument-hint'] === 'string')
|
|
72
|
+
skill.argumentHint = meta['argument-hint'];
|
|
73
|
+
if (Array.isArray(meta['allowed-tools'])) {
|
|
74
|
+
skill.allowedTools = meta['allowed-tools'].filter((t) => typeof t === 'string');
|
|
75
|
+
}
|
|
76
|
+
else if (typeof meta['allowed-tools'] === 'string') {
|
|
77
|
+
// Seen both ways in the wild: `allowed-tools: [Read, Grep]` and a bare comma string.
|
|
78
|
+
skill.allowedTools = meta['allowed-tools']
|
|
79
|
+
.split(',')
|
|
80
|
+
.map((t) => t.trim())
|
|
81
|
+
.filter(Boolean);
|
|
82
|
+
}
|
|
83
|
+
/*
|
|
84
|
+
⚠️ EVERY KEY, INCLUDING THE ONES WE DO NOT MODEL. A host shows `compatibility` and friends from
|
|
85
|
+
this. It is NOT a write path: regenerating frontmatter from parsed YAML destroys comments —
|
|
86
|
+
measured on our own template, 17 lines became 7 and all 9 comment lines vanished. Writes patch
|
|
87
|
+
the original text instead. Decision D-2.
|
|
88
|
+
*/
|
|
89
|
+
skill.rawFrontmatter = meta;
|
|
52
90
|
if (meta['compilr'] && typeof meta['compilr'] === 'object') {
|
|
53
91
|
skill.compilr = meta['compilr'];
|
|
54
92
|
}
|
|
@@ -1,44 +1,78 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* What a host actually SENDS when a user invokes a macro.
|
|
3
3
|
*
|
|
4
|
-
* ⚠️ THE TWO HOSTS SENT DIFFERENT THINGS FOR THE SAME COMMAND.
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* ⚠️ THE TWO HOSTS SENT DIFFERENT THINGS FOR THE SAME COMMAND. The CLI wrapped each of these
|
|
5
|
+
* macros in project context — do not scan the filesystem, the cwd is not the project, save work
|
|
6
|
+
* items with `workitem_add` and documents with `project_document_add` rather than writing files —
|
|
7
|
+
* while Desktop sent the prompt alone. Same names, same macros, materially different
|
|
8
|
+
* instructions, so the same request produced an agent that interviewed you and filled the backlog
|
|
9
|
+
* in one host and an agent that went looking for source files in the other.
|
|
10
10
|
*
|
|
11
|
-
* The
|
|
11
|
+
* The prompts were always shared data; what surrounded them was not. It is now.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* ⚠️ THE TEXT IS TRANSCRIBED, NOT REWRITTEN. Every block below was generated from the CLI's own
|
|
14
|
+
* template and asserted byte-identical to what it sent before the move, because a single reworded
|
|
15
|
+
* line here is a silent behaviour change in both hosts at once.
|
|
16
16
|
*
|
|
17
|
-
*
|
|
17
|
+
* This composes the MESSAGE only. The tool-gap preamble (`macroToolGap`) stays a separate,
|
|
18
|
+
* host-applied prepend: it depends on which AGENT is addressed, which only the host knows.
|
|
18
19
|
*/
|
|
19
|
-
/** What the host knows about the project a macro is
|
|
20
|
+
/** What the host knows about the project a macro is invoked against. */
|
|
20
21
|
export interface MacroProjectContext {
|
|
21
22
|
id: string | number;
|
|
22
23
|
name: string;
|
|
24
|
+
/** Absolute path on disk. `build` and `scaffold` write files and need it. */
|
|
25
|
+
path?: string;
|
|
26
|
+
}
|
|
27
|
+
/** A backlog item, for `build`. */
|
|
28
|
+
export interface MacroWorkItem {
|
|
29
|
+
itemId: string;
|
|
30
|
+
type: string;
|
|
31
|
+
priority: string;
|
|
32
|
+
title: string;
|
|
33
|
+
description?: string;
|
|
23
34
|
}
|
|
24
35
|
export interface MacroInvocationContext {
|
|
25
|
-
/** Absent when no project is selected; the context block is then omitted. */
|
|
36
|
+
/** Absent when no project is selected; the context block is then omitted entirely. */
|
|
26
37
|
project?: MacroProjectContext;
|
|
38
|
+
/** `prd` — the section the user named, if any. */
|
|
39
|
+
section?: string;
|
|
40
|
+
/** `session-notes` — the title the user gave, if any. */
|
|
41
|
+
noteTitle?: string;
|
|
42
|
+
/** `architecture` — the document type chosen, and how to name it back to the user. */
|
|
43
|
+
architecture?: {
|
|
44
|
+
docType: string;
|
|
45
|
+
displayType?: string;
|
|
46
|
+
};
|
|
47
|
+
/** `refine` — the item id, when refining one item rather than the whole backlog. */
|
|
48
|
+
refineItemId?: string;
|
|
49
|
+
/** `build` — the item to implement. Without it there is nothing to build. */
|
|
50
|
+
workItem?: MacroWorkItem;
|
|
51
|
+
/**
|
|
52
|
+
* `scaffold` — `factoryContext` is a pre-rendered block the host supplies because only it
|
|
53
|
+
* knows the Application Model's state; `isFactory` picks the closing instruction.
|
|
54
|
+
*/
|
|
55
|
+
scaffold?: {
|
|
56
|
+
factoryContext?: string;
|
|
57
|
+
isFactory?: boolean;
|
|
58
|
+
};
|
|
27
59
|
}
|
|
28
60
|
export interface MacroInvocation {
|
|
29
61
|
/** The text to send to the agent. */
|
|
30
62
|
message: string;
|
|
31
|
-
/** The
|
|
63
|
+
/** The short line to show the user in place of the full message. */
|
|
32
64
|
displayMessage: string;
|
|
33
65
|
}
|
|
34
66
|
/**
|
|
35
67
|
* Compose the message for a macro invocation.
|
|
36
68
|
*
|
|
37
|
-
* A macro with no wrapper sends its prompt unchanged —
|
|
38
|
-
*
|
|
69
|
+
* A macro with no wrapper sends its prompt unchanged — the common case, and the default, so
|
|
70
|
+
* adding a macro needs no edit here. A macro WITH a wrapper falls back to the bare prompt when
|
|
71
|
+
* the host cannot supply what the wrapper names: better a plain prompt than one asserting a
|
|
72
|
+
* project, an item or a path that is not there.
|
|
39
73
|
*
|
|
40
74
|
* @param macroName the macro being invoked, e.g. `design`
|
|
41
75
|
* @param prompt the macro's resolved prompt text
|
|
42
|
-
* @param ctx what the host knows
|
|
76
|
+
* @param ctx what the host knows
|
|
43
77
|
*/
|
|
44
78
|
export declare function buildMacroInvocation(macroName: string, prompt: string, ctx?: MacroInvocationContext): MacroInvocation;
|
|
@@ -1,26 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* What a host actually SENDS when a user invokes a macro.
|
|
3
3
|
*
|
|
4
|
-
* ⚠️ THE TWO HOSTS SENT DIFFERENT THINGS FOR THE SAME COMMAND.
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* ⚠️ THE TWO HOSTS SENT DIFFERENT THINGS FOR THE SAME COMMAND. The CLI wrapped each of these
|
|
5
|
+
* macros in project context — do not scan the filesystem, the cwd is not the project, save work
|
|
6
|
+
* items with `workitem_add` and documents with `project_document_add` rather than writing files —
|
|
7
|
+
* while Desktop sent the prompt alone. Same names, same macros, materially different
|
|
8
|
+
* instructions, so the same request produced an agent that interviewed you and filled the backlog
|
|
9
|
+
* in one host and an agent that went looking for source files in the other.
|
|
10
10
|
*
|
|
11
|
-
* The
|
|
11
|
+
* The prompts were always shared data; what surrounded them was not. It is now.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* ⚠️ THE TEXT IS TRANSCRIBED, NOT REWRITTEN. Every block below was generated from the CLI's own
|
|
14
|
+
* template and asserted byte-identical to what it sent before the move, because a single reworded
|
|
15
|
+
* line here is a silent behaviour change in both hosts at once.
|
|
16
16
|
*
|
|
17
|
-
*
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* ⚠️ "A NEW PROJECT" IS ASSERTED, NOT DETECTED, AND THAT IS DELIBERATE. `/design` is the
|
|
21
|
-
* from-scratch macro — `/refine` is the one for a backlog that already exists — so invoking it
|
|
22
|
-
* states the intent. Deriving it instead (counting work items, say) would make the prompt depend
|
|
23
|
-
* on database state at send time, and be wrong the moment someone re-runs /design to redesign.
|
|
17
|
+
* This composes the MESSAGE only. The tool-gap preamble (`macroToolGap`) stays a separate,
|
|
18
|
+
* host-applied prepend: it depends on which AGENT is addressed, which only the host knows.
|
|
24
19
|
*/
|
|
25
20
|
function designContext(project) {
|
|
26
21
|
return `I want to design my project and create the backlog.
|
|
@@ -42,22 +37,250 @@ Save all documents and backlog items using the database - do NOT write files dir
|
|
|
42
37
|
}
|
|
43
38
|
const DESIGN_CLOSER = 'Please start by using todo_write to track the design phases, then begin with Phase 1: Vision. ' +
|
|
44
39
|
'Use the ask_user tool to gather information efficiently.';
|
|
40
|
+
function sketchMessage(p, prompt) {
|
|
41
|
+
return `I want to quickly outline my project.
|
|
42
|
+
|
|
43
|
+
## PROJECT CONTEXT (IMPORTANT)
|
|
44
|
+
- Project Name: ${p.name} (ID: ${String(p.id)})
|
|
45
|
+
- This is a NEW project being designed from scratch
|
|
46
|
+
- Do NOT scan the filesystem - the current directory is NOT the project
|
|
47
|
+
- Do NOT use detect_project, glob, read_file, or ls tools
|
|
48
|
+
- Focus ONLY on gathering requirements via ask_user questions
|
|
49
|
+
|
|
50
|
+
## DOCUMENT STORAGE (CRITICAL)
|
|
51
|
+
Save all documents and backlog items using the database - do NOT write files directly.
|
|
52
|
+
- For backlog items: \`workitem_add({ type: "feature", title: "...", description: "..." })\`
|
|
53
|
+
- For documents: \`project_document_add({ doc_type: "prd", title: "...", content: "..." })\`
|
|
54
|
+
- Do NOT use write_file or edit tools for documentation
|
|
55
|
+
|
|
56
|
+
${prompt}
|
|
57
|
+
|
|
58
|
+
Please start by asking me about the type of application I'm building using the ask_user_simple tool.`;
|
|
59
|
+
}
|
|
60
|
+
function refineMessage(p, prompt, userIntent, agentInstructions) {
|
|
61
|
+
return `${userIntent}
|
|
62
|
+
|
|
63
|
+
## PROJECT CONTEXT (IMPORTANT)
|
|
64
|
+
- Project Name: ${p.name} (ID: ${String(p.id)})
|
|
65
|
+
- Do NOT scan the filesystem - the current directory is NOT the project
|
|
66
|
+
- Do NOT use detect_project, glob, read_file, or ls tools
|
|
67
|
+
- Use ONLY database tools to access project data
|
|
68
|
+
|
|
69
|
+
## DOCUMENT STORAGE (CRITICAL)
|
|
70
|
+
Use database tools for all updates - do NOT write files directly.
|
|
71
|
+
- Use \`workitem_query\` to read items
|
|
72
|
+
- Use \`workitem_update\` to modify items
|
|
73
|
+
- Do NOT use write_file or edit tools
|
|
74
|
+
|
|
75
|
+
${prompt}
|
|
76
|
+
|
|
77
|
+
${agentInstructions}`;
|
|
78
|
+
}
|
|
79
|
+
function buildMessage(p, prompt, item) {
|
|
80
|
+
return `I want to implement backlog item ${item.itemId}: "${item.title}".
|
|
81
|
+
|
|
82
|
+
## PROJECT CONTEXT (CRITICAL)
|
|
83
|
+
- Project Name: ${p.name} (ID: ${String(p.id)})
|
|
84
|
+
- **Project Directory: ${p.path ?? ''}**
|
|
85
|
+
- ALL files MUST be created/modified in this directory
|
|
86
|
+
|
|
87
|
+
## WORK ITEM DETAILS
|
|
88
|
+
- Item ID: ${item.itemId}
|
|
89
|
+
- Type: ${item.type}
|
|
90
|
+
- Priority: ${item.priority}
|
|
91
|
+
- Description: ${item.description ?? 'No description provided'}
|
|
92
|
+
|
|
93
|
+
## TOOL RESTRICTIONS (CRITICAL)
|
|
94
|
+
- Do NOT use detect_project or find_project_root
|
|
95
|
+
- Do NOT scan filesystem to detect project type
|
|
96
|
+
- The project path above is the ONLY source of truth for where to create/modify files
|
|
97
|
+
- Use \`project_document_get\` to read PRD and architecture docs from the database
|
|
98
|
+
- Use \`workitem_query\` to read related backlog items from the database
|
|
99
|
+
- Use \`workitem_update\` to update the item status when done
|
|
100
|
+
|
|
101
|
+
## FILE OPERATIONS (CRITICAL)
|
|
102
|
+
When creating or modifying files:
|
|
103
|
+
- Use absolute paths starting with: ${p.path ?? ''}/
|
|
104
|
+
- Example: write_file({ path: "${p.path ?? ''}/src/index.ts", ... })
|
|
105
|
+
- NEVER use relative paths
|
|
106
|
+
- IGNORE the current working directory (cwd) - it may be different from the project!
|
|
107
|
+
- The user may have started the CLI from a different folder
|
|
108
|
+
|
|
109
|
+
${prompt}
|
|
110
|
+
|
|
111
|
+
Please start by:
|
|
112
|
+
1. Using todo_write to plan the implementation steps
|
|
113
|
+
2. Reading project documents from the database (project_document_list, project_document_get)
|
|
114
|
+
3. Updating the work item status to 'in_progress' using workitem_update
|
|
115
|
+
4. Implementing the feature
|
|
116
|
+
5. Updating the work item status to 'completed' when done`;
|
|
117
|
+
}
|
|
118
|
+
function sessionNotesMessage(p, prompt, titleInstruction) {
|
|
119
|
+
return `I want to create a session note capturing what we've done.
|
|
120
|
+
|
|
121
|
+
## PROJECT CONTEXT
|
|
122
|
+
- Project Name: ${p.name} (ID: ${String(p.id)})
|
|
123
|
+
|
|
124
|
+
## DOCUMENT STORAGE (CRITICAL)
|
|
125
|
+
Save session notes using the database - do NOT write files directly.
|
|
126
|
+
- Tool: \`project_document_add({ doc_type: "session-note", title: "...", content: "..." })\`
|
|
127
|
+
- The database is the source of truth for all project documents
|
|
128
|
+
- Do NOT use write_file or edit tools for documentation
|
|
129
|
+
|
|
130
|
+
${prompt}
|
|
131
|
+
|
|
132
|
+
${titleInstruction}
|
|
133
|
+
|
|
134
|
+
Review the conversation context to understand what was accomplished, then create the session note.`;
|
|
135
|
+
}
|
|
136
|
+
function prdMessage(p, prompt, sectionInstruction) {
|
|
137
|
+
return `I want to update the Product Requirements Document.
|
|
138
|
+
|
|
139
|
+
## DOCUMENT STORAGE (CRITICAL)
|
|
140
|
+
Save PRD updates using the database - do NOT write files directly.
|
|
141
|
+
- Project: ${p.name} (ID: ${String(p.id)})
|
|
142
|
+
- Tool: \`project_document_add({ doc_type: "prd", title: "Product Requirements Document", content: "..." })\`
|
|
143
|
+
- The database is the source of truth for all project documents
|
|
144
|
+
- Do NOT use write_file or edit tools for documentation
|
|
145
|
+
|
|
146
|
+
${prompt}
|
|
147
|
+
|
|
148
|
+
${sectionInstruction}
|
|
149
|
+
|
|
150
|
+
Start by reading the existing PRD from the database using project_document_get({ doc_type: "prd" }). If no document exists yet, create a new one.`;
|
|
151
|
+
}
|
|
152
|
+
function architectureMessage(p, prompt, arch) {
|
|
153
|
+
return `I want to create architecture documentation.
|
|
154
|
+
|
|
155
|
+
## DOCUMENT STORAGE (CRITICAL)
|
|
156
|
+
Save architecture documentation using the database - do NOT write files directly.
|
|
157
|
+
- Project: ${p.name} (ID: ${String(p.id)})
|
|
158
|
+
- Tool: \`project_document_add({ doc_type: "architecture", title: "...", content: "..." })\`
|
|
159
|
+
- The database is the source of truth for all project documents
|
|
160
|
+
- Do NOT use write_file or edit tools for documentation
|
|
161
|
+
|
|
162
|
+
${prompt}
|
|
163
|
+
|
|
164
|
+
Please start by using project_document_list to find any existing PRD, and workitem_query to understand the project backlog. Then use ask_user to gather the information needed for this ${arch.docType} document.`;
|
|
165
|
+
}
|
|
166
|
+
function scaffoldMessage(p, prompt, factoryContext, closingInstruction) {
|
|
167
|
+
return `I want to create the project scaffold/foundation.
|
|
168
|
+
|
|
169
|
+
## PROJECT CONTEXT (CRITICAL)
|
|
170
|
+
- Project Name: ${p.name} (ID: ${String(p.id)})
|
|
171
|
+
- **Project Directory: ${p.path ?? ''}**
|
|
172
|
+
- ALL files MUST be created in this directory
|
|
173
|
+
${factoryContext}
|
|
174
|
+
## TOOL RESTRICTIONS (CRITICAL)
|
|
175
|
+
- Do NOT use detect_project or find_project_root
|
|
176
|
+
- Do NOT scan filesystem to detect project type
|
|
177
|
+
- The project path above is the ONLY source of truth for where to create files
|
|
178
|
+
- Use \`project_document_get\` to read PRD and architecture docs from the database
|
|
179
|
+
- Use \`workitem_query\` to read backlog items from the database
|
|
180
|
+
|
|
181
|
+
## FILE CREATION (CRITICAL)
|
|
182
|
+
When creating files:
|
|
183
|
+
- Use absolute paths starting with: ${p.path ?? ''}/
|
|
184
|
+
- Example: write_file({ path: "${p.path ?? ''}/package.json", ... })
|
|
185
|
+
- NEVER use relative paths
|
|
186
|
+
- IGNORE the current working directory (cwd) - it may be different from the project!
|
|
187
|
+
- The user may have started the CLI from a different folder
|
|
188
|
+
|
|
189
|
+
${prompt}
|
|
190
|
+
|
|
191
|
+
${closingInstruction}`;
|
|
192
|
+
}
|
|
45
193
|
/**
|
|
46
194
|
* Compose the message for a macro invocation.
|
|
47
195
|
*
|
|
48
|
-
* A macro with no wrapper sends its prompt unchanged —
|
|
49
|
-
*
|
|
196
|
+
* A macro with no wrapper sends its prompt unchanged — the common case, and the default, so
|
|
197
|
+
* adding a macro needs no edit here. A macro WITH a wrapper falls back to the bare prompt when
|
|
198
|
+
* the host cannot supply what the wrapper names: better a plain prompt than one asserting a
|
|
199
|
+
* project, an item or a path that is not there.
|
|
50
200
|
*
|
|
51
201
|
* @param macroName the macro being invoked, e.g. `design`
|
|
52
202
|
* @param prompt the macro's resolved prompt text
|
|
53
|
-
* @param ctx what the host knows
|
|
203
|
+
* @param ctx what the host knows
|
|
54
204
|
*/
|
|
55
205
|
export function buildMacroInvocation(macroName, prompt, ctx = {}) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
206
|
+
const p = ctx.project;
|
|
207
|
+
if (!p)
|
|
208
|
+
return { message: prompt, displayMessage: `/${macroName}` };
|
|
209
|
+
switch (macroName) {
|
|
210
|
+
case 'design':
|
|
211
|
+
return {
|
|
212
|
+
message: `${designContext(p)}\n${prompt}\n\n${DESIGN_CLOSER}`,
|
|
213
|
+
displayMessage: 'Start the design process for my project.',
|
|
214
|
+
};
|
|
215
|
+
case 'sketch':
|
|
216
|
+
return { message: sketchMessage(p, prompt), displayMessage: 'Quick project outline.' };
|
|
217
|
+
case 'refine':
|
|
218
|
+
case 'refine-item': {
|
|
219
|
+
// Bound to a local so the narrowing survives into the template literals.
|
|
220
|
+
const itemId = ctx.refineItemId;
|
|
221
|
+
const userIntent = itemId !== undefined
|
|
222
|
+
? `I want to refine backlog item ${itemId}.`
|
|
223
|
+
: 'I want to refine my project requirements.';
|
|
224
|
+
const agentInstructions = itemId !== undefined
|
|
225
|
+
? `Please use workitem_query to find item "${itemId}", then guide me through refining it using workitem_update.`
|
|
226
|
+
: "Please start by using workitem_query with limit:10 to get an overview, then ask me what I'd like to focus on using ask_user_simple.";
|
|
227
|
+
return {
|
|
228
|
+
message: refineMessage(p, prompt, userIntent, agentInstructions),
|
|
229
|
+
displayMessage: userIntent,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
case 'build': {
|
|
233
|
+
// Nothing to build without an item, and the whole wrapper is about one.
|
|
234
|
+
if (!ctx.workItem)
|
|
235
|
+
return { message: prompt, displayMessage: `/${macroName}` };
|
|
236
|
+
return {
|
|
237
|
+
message: buildMessage(p, prompt, ctx.workItem),
|
|
238
|
+
displayMessage: `Build ${ctx.workItem.itemId}: ${ctx.workItem.title}`,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
case 'session-notes': {
|
|
242
|
+
const titleInstruction = ctx.noteTitle
|
|
243
|
+
? `The session title is: "${ctx.noteTitle}"`
|
|
244
|
+
: 'Please ask me for a title using ask_user_simple, or generate one from the session summary.';
|
|
245
|
+
return {
|
|
246
|
+
message: sessionNotesMessage(p, prompt, titleInstruction),
|
|
247
|
+
displayMessage: ctx.noteTitle
|
|
248
|
+
? `Create session note: "${ctx.noteTitle}"`
|
|
249
|
+
: 'Create a session note for this session.',
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
case 'prd': {
|
|
253
|
+
const valid = ['vision', 'scope', 'technical', 'success'];
|
|
254
|
+
const section = ctx.section ?? '';
|
|
255
|
+
const sectionInstruction = valid.includes(section)
|
|
256
|
+
? `The user wants to update the "${section}" section specifically.`
|
|
257
|
+
: 'Ask the user which section they want to update using ask_user_simple.';
|
|
258
|
+
return {
|
|
259
|
+
message: prdMessage(p, prompt, sectionInstruction),
|
|
260
|
+
displayMessage: section
|
|
261
|
+
? `Update PRD: ${section} section`
|
|
262
|
+
: 'Update the Product Requirements Document',
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
case 'architecture': {
|
|
266
|
+
if (!ctx.architecture)
|
|
267
|
+
return { message: prompt, displayMessage: `/${macroName}` };
|
|
268
|
+
return {
|
|
269
|
+
message: architectureMessage(p, prompt, ctx.architecture),
|
|
270
|
+
displayMessage: `Create ${ctx.architecture.displayType ?? ctx.architecture.docType} documentation.`,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
case 'scaffold':
|
|
274
|
+
case 'factory-scaffold': {
|
|
275
|
+
const closingInstruction = macroName === 'factory-scaffold' || ctx.scaffold?.isFactory
|
|
276
|
+
? 'Start by checking the Application Model with app_model_get. If no model exists, read the PRD with project_document_get and build the model incrementally using app_model_update. Then generate with factory_scaffold.'
|
|
277
|
+
: 'Start by reading the project documents from the database using project_document_list and project_document_get. Then create a plan with todo_write before generating files.';
|
|
278
|
+
return {
|
|
279
|
+
message: scaffoldMessage(p, prompt, ctx.scaffold?.factoryContext ?? '', closingInstruction),
|
|
280
|
+
displayMessage: 'Create project scaffold.',
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
default:
|
|
284
|
+
return { message: prompt, displayMessage: `/${macroName}` };
|
|
61
285
|
}
|
|
62
|
-
return { message: prompt, displayMessage: `/${macroName}` };
|
|
63
286
|
}
|
|
@@ -65,6 +65,11 @@ export interface SkillValidationIssue {
|
|
|
65
65
|
/**
|
|
66
66
|
* Validate a parsed custom skill beyond basic frontmatter parsing.
|
|
67
67
|
*/
|
|
68
|
+
/**
|
|
69
|
+
* The description our own template ships. Matched on its opening, so an author who edited the tail
|
|
70
|
+
* but left the head is still caught.
|
|
71
|
+
*/
|
|
72
|
+
export declare function isPlaceholderDescription(description: string): boolean;
|
|
68
73
|
export declare function validateSkill(skill: CustomSkill, folderName: string): SkillValidationIssue[];
|
|
69
74
|
export interface ScopeConfig {
|
|
70
75
|
slashCommands?: Record<string, string>;
|
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
* - Validation (quality checks beyond parseSkillMarkdown)
|
|
8
8
|
* - Binding resolution (config.json slash command remapping)
|
|
9
9
|
*/
|
|
10
|
-
import { RESERVED_MACRO_NAMES } from './types.js';
|
|
11
10
|
import { platformMacros } from './platform-macros.js';
|
|
11
|
+
import { builtinSkills } from '@compilr-dev/agents';
|
|
12
|
+
import { RESERVED_MACRO_NAMES } from './types.js';
|
|
12
13
|
/**
|
|
13
14
|
* Detect user/project skills that shadow SDK reserved names.
|
|
14
15
|
* Called at startup to warn users about collisions introduced by SDK updates.
|
|
@@ -152,13 +153,18 @@ export function buildForkContent(sdkSkill, newName, sdkVersion) {
|
|
|
152
153
|
* Build the SKILL.md template for a new custom skill.
|
|
153
154
|
*/
|
|
154
155
|
export function buildNewSkillContent(name, scope) {
|
|
156
|
+
/*
|
|
157
|
+
⚠️ NO DESCRIPTION. It used to ship a paragraph of advice IN the description field, and people
|
|
158
|
+
shipped it unchanged — 291 characters, which passed every length check we had, so the catalogue
|
|
159
|
+
filled with entries no agent would ever choose. The advice moved to a comment, where it cannot
|
|
160
|
+
be mistaken for content. Never pre-fill a field with text that would be valid if saved.
|
|
161
|
+
*/
|
|
155
162
|
return `---
|
|
156
163
|
name: ${name}
|
|
157
|
-
description:
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
intent-rich because the model reads them to decide when to load the skill.)
|
|
164
|
+
description: ''
|
|
165
|
+
# ↑ REQUIRED, and written for a different reader: the model decides from this whether to load the
|
|
166
|
+
# skill. Name the trigger conditions, the phrases a user would say, and what it produces. Real
|
|
167
|
+
# skills run 100+ characters — it is a net for matching, not a summary.
|
|
162
168
|
version: 0.1.0
|
|
163
169
|
|
|
164
170
|
# Optional — compilr extensions (ignored by Anthropic-compatible runtimes).
|
|
@@ -186,6 +192,13 @@ Describe the first step.
|
|
|
186
192
|
/**
|
|
187
193
|
* Validate a parsed custom skill beyond basic frontmatter parsing.
|
|
188
194
|
*/
|
|
195
|
+
/**
|
|
196
|
+
* The description our own template ships. Matched on its opening, so an author who edited the tail
|
|
197
|
+
* but left the head is still caught.
|
|
198
|
+
*/
|
|
199
|
+
export function isPlaceholderDescription(description) {
|
|
200
|
+
return description.trim().startsWith('Use this skill when …');
|
|
201
|
+
}
|
|
189
202
|
export function validateSkill(skill, folderName) {
|
|
190
203
|
const issues = [];
|
|
191
204
|
if (skill.name !== folderName) {
|
|
@@ -194,12 +207,41 @@ export function validateSkill(skill, folderName) {
|
|
|
194
207
|
message: `Frontmatter name '${skill.name}' doesn't match folder '${folderName}'.`,
|
|
195
208
|
});
|
|
196
209
|
}
|
|
197
|
-
|
|
210
|
+
/*
|
|
211
|
+
⚠️ THE FLOOR IS 100, AND MEASURED. Across 50 Anthropic-authored SKILL.md files the shortest
|
|
212
|
+
description is 105 characters; median 329, p90 906. The old floor of 60 sat BELOW the shortest
|
|
213
|
+
real skill, so it could never fire on anything plausible.
|
|
214
|
+
|
|
215
|
+
And it cannot catch the thing that actually breaks: our own template placeholder was 291
|
|
216
|
+
characters — comfortably normal — so every length check passed it. Only persistent UI state
|
|
217
|
+
solves that; the explicit placeholder check below is the cheap half.
|
|
218
|
+
*/
|
|
219
|
+
if (skill.description.trim().length < 100) {
|
|
220
|
+
issues.push({
|
|
221
|
+
level: 'warning',
|
|
222
|
+
message: `Description is ${String(skill.description.trim().length)} chars. Real skills run 100+ — it is a net for matching, not a summary.`,
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
if (isPlaceholderDescription(skill.description)) {
|
|
198
226
|
issues.push({
|
|
199
227
|
level: 'warning',
|
|
200
|
-
message:
|
|
228
|
+
message: 'Description is still the template placeholder — no agent will choose this skill. Length checks cannot catch it; it is 291 characters.',
|
|
201
229
|
});
|
|
202
230
|
}
|
|
231
|
+
/*
|
|
232
|
+
Forking copies the source's description verbatim, so two catalogue entries read the same and no
|
|
233
|
+
agent can tell them apart. This becomes the COMMON failure once forking is one click, and
|
|
234
|
+
neither the old UI nor the original design brief caught it.
|
|
235
|
+
*/
|
|
236
|
+
if (skill.forkedFrom?.skill) {
|
|
237
|
+
const source = [...platformMacros, ...builtinSkills].find((m) => m.name === skill.forkedFrom?.skill);
|
|
238
|
+
if (source && source.description.trim() === skill.description.trim()) {
|
|
239
|
+
issues.push({
|
|
240
|
+
level: 'warning',
|
|
241
|
+
message: `Description is identical to /${skill.forkedFrom.skill}, which it was forked from — an agent cannot choose between them.`,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
}
|
|
203
245
|
if (!skill.prompt || skill.prompt.trim().length === 0) {
|
|
204
246
|
issues.push({
|
|
205
247
|
level: 'warning',
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edit a SKILL.md's frontmatter WITHOUT rewriting it.
|
|
3
|
+
*
|
|
4
|
+
* ⚠️ PARSE FOR READING, PATCH FOR WRITING. Regenerating frontmatter from parsed YAML destroys
|
|
5
|
+
* comments — they are not in any YAML data model. Measured on our own template: `parse → stringify`
|
|
6
|
+
* took it from 17 lines to 7 and all 9 comment lines to 0, deleting the commented-out `compilr:`
|
|
7
|
+
* block we ship deliberately as documentation.
|
|
8
|
+
*
|
|
9
|
+
* So retaining unknown KEYS is not sufficient. This edits the lines it is given and returns every
|
|
10
|
+
* other byte unchanged: comments, key order, spacing, and fields we have never modelled.
|
|
11
|
+
*
|
|
12
|
+
* The guard this buys, and it covers the whole class: open a skill, change nothing, save — the
|
|
13
|
+
* file must be byte-identical.
|
|
14
|
+
*
|
|
15
|
+
* Decision D-2: project-docs/.../compilr-dev-skills/implementation-plan.md
|
|
16
|
+
*/
|
|
17
|
+
/** `undefined` leaves a key alone; `null` removes it; anything else sets it. */
|
|
18
|
+
export type FrontmatterPatch = Record<string, string | number | boolean | null | undefined>;
|
|
19
|
+
/**
|
|
20
|
+
* Apply `patch` to `content`'s frontmatter, leaving everything else byte-identical.
|
|
21
|
+
*
|
|
22
|
+
* Returns the content unchanged when it has no frontmatter — a caller should not be silently
|
|
23
|
+
* handed a file it cannot edit.
|
|
24
|
+
*/
|
|
25
|
+
export declare function patchSkillFrontmatter(content: string, patch: FrontmatterPatch): string;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edit a SKILL.md's frontmatter WITHOUT rewriting it.
|
|
3
|
+
*
|
|
4
|
+
* ⚠️ PARSE FOR READING, PATCH FOR WRITING. Regenerating frontmatter from parsed YAML destroys
|
|
5
|
+
* comments — they are not in any YAML data model. Measured on our own template: `parse → stringify`
|
|
6
|
+
* took it from 17 lines to 7 and all 9 comment lines to 0, deleting the commented-out `compilr:`
|
|
7
|
+
* block we ship deliberately as documentation.
|
|
8
|
+
*
|
|
9
|
+
* So retaining unknown KEYS is not sufficient. This edits the lines it is given and returns every
|
|
10
|
+
* other byte unchanged: comments, key order, spacing, and fields we have never modelled.
|
|
11
|
+
*
|
|
12
|
+
* The guard this buys, and it covers the whole class: open a skill, change nothing, save — the
|
|
13
|
+
* file must be byte-identical.
|
|
14
|
+
*
|
|
15
|
+
* Decision D-2: project-docs/.../compilr-dev-skills/implementation-plan.md
|
|
16
|
+
*/
|
|
17
|
+
function splitFrontmatter(content) {
|
|
18
|
+
if (!content.startsWith('---'))
|
|
19
|
+
return null;
|
|
20
|
+
const lines = content.split('\n');
|
|
21
|
+
if (lines[0] !== '---')
|
|
22
|
+
return null;
|
|
23
|
+
for (let i = 1; i < lines.length; i++) {
|
|
24
|
+
if (lines[i] === '---') {
|
|
25
|
+
return {
|
|
26
|
+
before: '---\n',
|
|
27
|
+
frontmatter: lines.slice(1, i).join('\n'),
|
|
28
|
+
after: '\n---' + content.slice(content.indexOf('\n---', content.indexOf('\n')) + 4),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
/** Does this line begin the given top-level key? Indented lines are values, not keys. */
|
|
35
|
+
function startsKey(line, key) {
|
|
36
|
+
return new RegExp(`^${key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*:`).test(line);
|
|
37
|
+
}
|
|
38
|
+
/** The lines a top-level key owns: its own, plus any indented or blank continuation. */
|
|
39
|
+
function keyBlockEnd(lines, start) {
|
|
40
|
+
let end = start + 1;
|
|
41
|
+
while (end < lines.length) {
|
|
42
|
+
const l = lines[end];
|
|
43
|
+
if (l.trim() === '' || /^\s/.test(l)) {
|
|
44
|
+
end++;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
// Trailing blank lines belong to whatever follows, not to this key.
|
|
50
|
+
while (end > start + 1 && lines[end - 1].trim() === '')
|
|
51
|
+
end--;
|
|
52
|
+
return end;
|
|
53
|
+
}
|
|
54
|
+
function render(key, value) {
|
|
55
|
+
if (typeof value === 'string' && (value.includes('\n') || value.length > 80)) {
|
|
56
|
+
const body = value
|
|
57
|
+
.split('\n')
|
|
58
|
+
.map((l) => ` ${l}`)
|
|
59
|
+
.join('\n');
|
|
60
|
+
return `${key}: |\n${body}`;
|
|
61
|
+
}
|
|
62
|
+
return `${key}: ${String(value)}`;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Apply `patch` to `content`'s frontmatter, leaving everything else byte-identical.
|
|
66
|
+
*
|
|
67
|
+
* Returns the content unchanged when it has no frontmatter — a caller should not be silently
|
|
68
|
+
* handed a file it cannot edit.
|
|
69
|
+
*/
|
|
70
|
+
export function patchSkillFrontmatter(content, patch) {
|
|
71
|
+
const split = splitFrontmatter(content);
|
|
72
|
+
if (!split)
|
|
73
|
+
return content;
|
|
74
|
+
const lines = split.frontmatter.split('\n');
|
|
75
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
76
|
+
if (value === undefined)
|
|
77
|
+
continue;
|
|
78
|
+
const idx = lines.findIndex((l) => startsKey(l, key));
|
|
79
|
+
if (value === null) {
|
|
80
|
+
if (idx !== -1)
|
|
81
|
+
lines.splice(idx, keyBlockEnd(lines, idx) - idx);
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (idx === -1) {
|
|
85
|
+
lines.push(render(key, value));
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
lines.splice(idx, keyBlockEnd(lines, idx) - idx, ...render(key, value).split('\n'));
|
|
89
|
+
}
|
|
90
|
+
return split.before + lines.join('\n') + split.after;
|
|
91
|
+
}
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
import { readFileSync, existsSync, readdirSync } from 'node:fs';
|
|
17
17
|
import { join } from 'node:path';
|
|
18
18
|
import { parseSkillMarkdown } from './loader.js';
|
|
19
|
+
import { skillReachability } from './reachability.js';
|
|
19
20
|
import { resolveBinding, getSkillsDir, readScopeConfigSync } from './paths.js';
|
|
20
21
|
import { platformMacros } from './platform-macros.js';
|
|
21
22
|
// Re-export builtinSkills from agents (available via SDK re-export)
|
|
@@ -35,7 +36,9 @@ function readScopedSkillPrompt(scope, name, projectDir) {
|
|
|
35
36
|
return null;
|
|
36
37
|
const content = readFileSync(file, 'utf-8');
|
|
37
38
|
const parsed = parseSkillMarkdown(content);
|
|
38
|
-
|
|
39
|
+
// A skill the USER may not type does not resolve as a slash command. `enabled: false` maps
|
|
40
|
+
// to both switches off, so a shelved skill still refuses here (D-1).
|
|
41
|
+
if (!parsed || !skillReachability(parsed).byUser)
|
|
39
42
|
return null;
|
|
40
43
|
return parsed.prompt;
|
|
41
44
|
}
|
|
@@ -168,7 +171,7 @@ export function getAllAvailableSkills(projectDir, sources) {
|
|
|
168
171
|
description: parsed.description,
|
|
169
172
|
source: scope,
|
|
170
173
|
scope,
|
|
171
|
-
enabled: parsed.
|
|
174
|
+
enabled: skillReachability(parsed).byUser,
|
|
172
175
|
hasWarnings: parsed.description.length < 60 || !parsed.prompt.trim(),
|
|
173
176
|
isForked: !!parsed.forkedFrom,
|
|
174
177
|
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Who can reach a skill — and the migration off `enabled`.
|
|
3
|
+
*
|
|
4
|
+
* ⚠️ TWO AXES, NOT ONE. Reachability asks "can ANYTHING reach this skill?" and lives in the file,
|
|
5
|
+
* in Anthropic's own fields. The GRANT asks "which agents may choose among the reachable ones?"
|
|
6
|
+
* and lives on the agent as `grantedSkills`, the same contract as `mcpServers`. They compose: an
|
|
7
|
+
* unreachable skill is unreachable whatever the grants say.
|
|
8
|
+
*
|
|
9
|
+
* ⚠️ `enabled` IS NOT PART OF THE FORMAT. Zero of 50 Anthropic-authored SKILL.md files carry it;
|
|
10
|
+
* we invented it, and three precedents point the other way — Claude Code disables plugins in
|
|
11
|
+
* settings.json rather than by editing the artifact, and our own MCP model has no enabled flag
|
|
12
|
+
* either. It is mapped here, on READ, so a skill someone disabled long ago and never re-saved
|
|
13
|
+
* keeps reading as unreachable. It is never written back.
|
|
14
|
+
*
|
|
15
|
+
* Decision D-1: project-docs/.../compilr-dev-skills/implementation-plan.md
|
|
16
|
+
*/
|
|
17
|
+
import type { CustomSkill } from './types.js';
|
|
18
|
+
export interface SkillReachability {
|
|
19
|
+
/** An agent may choose this skill from its catalogue. */
|
|
20
|
+
byModel: boolean;
|
|
21
|
+
/** A user may type this skill as a slash command. */
|
|
22
|
+
byUser: boolean;
|
|
23
|
+
}
|
|
24
|
+
/** Nothing can reach it — the state that replaces `enabled: false`. */
|
|
25
|
+
export declare function isUnreachable(skill: Pick<CustomSkill, 'enabled' | 'disableModelInvocation' | 'userInvocable'>): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Resolve the two switches, applying the legacy mapping.
|
|
28
|
+
*
|
|
29
|
+
* Defaults, and why they differ from Anthropic's:
|
|
30
|
+
* - `byModel` defaults TRUE, matching the format: absent `disable-model-invocation` means the
|
|
31
|
+
* model may choose it.
|
|
32
|
+
* - `byUser` defaults TRUE, which Anthropic's format does NOT — there, `user-invocable` opts in.
|
|
33
|
+
* Every SKILL.md in compilr has always been typeable and decision D-1 in commands-and-skills.md
|
|
34
|
+
* keeps it so; flipping the default would silently un-type every skill anyone has already built.
|
|
35
|
+
*/
|
|
36
|
+
export declare function skillReachability(skill: Pick<CustomSkill, 'enabled' | 'disableModelInvocation' | 'userInvocable'>): SkillReachability;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Who can reach a skill — and the migration off `enabled`.
|
|
3
|
+
*
|
|
4
|
+
* ⚠️ TWO AXES, NOT ONE. Reachability asks "can ANYTHING reach this skill?" and lives in the file,
|
|
5
|
+
* in Anthropic's own fields. The GRANT asks "which agents may choose among the reachable ones?"
|
|
6
|
+
* and lives on the agent as `grantedSkills`, the same contract as `mcpServers`. They compose: an
|
|
7
|
+
* unreachable skill is unreachable whatever the grants say.
|
|
8
|
+
*
|
|
9
|
+
* ⚠️ `enabled` IS NOT PART OF THE FORMAT. Zero of 50 Anthropic-authored SKILL.md files carry it;
|
|
10
|
+
* we invented it, and three precedents point the other way — Claude Code disables plugins in
|
|
11
|
+
* settings.json rather than by editing the artifact, and our own MCP model has no enabled flag
|
|
12
|
+
* either. It is mapped here, on READ, so a skill someone disabled long ago and never re-saved
|
|
13
|
+
* keeps reading as unreachable. It is never written back.
|
|
14
|
+
*
|
|
15
|
+
* Decision D-1: project-docs/.../compilr-dev-skills/implementation-plan.md
|
|
16
|
+
*/
|
|
17
|
+
/** Nothing can reach it — the state that replaces `enabled: false`. */
|
|
18
|
+
export function isUnreachable(skill) {
|
|
19
|
+
const r = skillReachability(skill);
|
|
20
|
+
return !r.byModel && !r.byUser;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Resolve the two switches, applying the legacy mapping.
|
|
24
|
+
*
|
|
25
|
+
* Defaults, and why they differ from Anthropic's:
|
|
26
|
+
* - `byModel` defaults TRUE, matching the format: absent `disable-model-invocation` means the
|
|
27
|
+
* model may choose it.
|
|
28
|
+
* - `byUser` defaults TRUE, which Anthropic's format does NOT — there, `user-invocable` opts in.
|
|
29
|
+
* Every SKILL.md in compilr has always been typeable and decision D-1 in commands-and-skills.md
|
|
30
|
+
* keeps it so; flipping the default would silently un-type every skill anyone has already built.
|
|
31
|
+
*/
|
|
32
|
+
export function skillReachability(skill) {
|
|
33
|
+
// The legacy field wins when explicitly false: it is the user's stated intent to shelve it,
|
|
34
|
+
// recorded before the two switches existed.
|
|
35
|
+
if (skill.enabled === false)
|
|
36
|
+
return { byModel: false, byUser: false };
|
|
37
|
+
return {
|
|
38
|
+
byModel: skill.disableModelInvocation !== true,
|
|
39
|
+
byUser: skill.userInvocable !== false,
|
|
40
|
+
};
|
|
41
|
+
}
|
package/dist/skills/resolver.js
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* - Stage 2 ACTIVATION: model picks from descriptions, runs per turn.
|
|
16
16
|
* (Stage 2 is the existing skill invocation — this file only does Stage 1.)
|
|
17
17
|
*/
|
|
18
|
+
import { skillReachability } from './reachability.js';
|
|
18
19
|
/**
|
|
19
20
|
* Resolve a list of skills from layered sources by priority. First match
|
|
20
21
|
* by name wins. Returns the deduplicated list, with each skill carrying
|
|
@@ -55,7 +56,13 @@ export function resolveSkillsForAgent(skills, context) {
|
|
|
55
56
|
*/
|
|
56
57
|
const allowlist = context.grantedSkills ? new Set(context.grantedSkills) : null;
|
|
57
58
|
return skills.filter((skill) => {
|
|
58
|
-
|
|
59
|
+
// Stage 0 — a skill with no description cannot be CHOSEN: the description is the only thing
|
|
60
|
+
// the model sees in the catalogue. Loading it costs tokens and offers nothing to match on.
|
|
61
|
+
if (skill.description.trim() === '')
|
|
62
|
+
return false;
|
|
63
|
+
// Stage 1.0 — reachability. A skill the MODEL may not choose never enters a catalogue,
|
|
64
|
+
// whatever the grants say. `enabled: false` maps here; see skillReachability (D-1).
|
|
65
|
+
if (!skillReachability(skill).byModel)
|
|
59
66
|
return false;
|
|
60
67
|
// Stage 1.1 — Targeting. Default = targets every agent.
|
|
61
68
|
const targets = skill.compilr?.targets;
|
|
@@ -18,9 +18,9 @@ export const designMacro = defineSkill({
|
|
|
18
18
|
- Need to create initial backlog
|
|
19
19
|
|
|
20
20
|
## When NOT to Use
|
|
21
|
-
- Project already has requirements →
|
|
22
|
-
- Quick project outline needed →
|
|
23
|
-
- Just need to update PRD →
|
|
21
|
+
- Project already has requirements → tell the user to run /refine
|
|
22
|
+
- Quick project outline needed → tell the user to run /sketch
|
|
23
|
+
- Just need to update PRD → tell the user to run /prd
|
|
24
24
|
|
|
25
25
|
## PHASES
|
|
26
26
|
|
|
@@ -104,9 +104,9 @@ export const refineMacro = defineSkill({
|
|
|
104
104
|
- Adding missing items to backlog
|
|
105
105
|
|
|
106
106
|
## When NOT to Use
|
|
107
|
-
- No backlog exists yet →
|
|
108
|
-
- Quick outline needed →
|
|
109
|
-
- Refining single item →
|
|
107
|
+
- No backlog exists yet → tell the user to run /design
|
|
108
|
+
- Quick outline needed → tell the user to run /sketch
|
|
109
|
+
- Refining single item → tell the user to run /refine-item
|
|
110
110
|
|
|
111
111
|
## STARTUP
|
|
112
112
|
|
|
@@ -182,9 +182,9 @@ export const sketchMacro = defineSkill({
|
|
|
182
182
|
- Initial brainstorming session
|
|
183
183
|
|
|
184
184
|
## When NOT to Use
|
|
185
|
-
- Detailed requirements needed →
|
|
186
|
-
- Project already has backlog →
|
|
187
|
-
- Single item focus →
|
|
185
|
+
- Detailed requirements needed → tell the user to run /design
|
|
186
|
+
- Project already has backlog → tell the user to run /refine
|
|
187
|
+
- Single item focus → tell the user to run /refine-item
|
|
188
188
|
|
|
189
189
|
STEPS:
|
|
190
190
|
1. Use todo_write with 6 tasks (one per question)
|
|
@@ -221,9 +221,9 @@ export const refineItemMacro = defineSkill({
|
|
|
221
221
|
- Changing priority of specific item
|
|
222
222
|
|
|
223
223
|
## When NOT to Use
|
|
224
|
-
- Multiple items need refinement →
|
|
225
|
-
- No items exist yet →
|
|
226
|
-
- Ready to implement →
|
|
224
|
+
- Multiple items need refinement → tell the user to run /refine
|
|
225
|
+
- No items exist yet → tell the user to run /design or /sketch
|
|
226
|
+
- Ready to implement → tell the user to run /build
|
|
227
227
|
|
|
228
228
|
## STARTUP
|
|
229
229
|
|
|
@@ -290,9 +290,9 @@ export const architectureMacro = defineSkill({
|
|
|
290
290
|
- Recording technical decisions
|
|
291
291
|
|
|
292
292
|
## When NOT to Use
|
|
293
|
-
- Need product requirements →
|
|
294
|
-
- Need to implement features →
|
|
295
|
-
- Just want to understand existing code →
|
|
293
|
+
- Need product requirements → tell the user to run /design or /prd
|
|
294
|
+
- Need to implement features → tell the user to run /build
|
|
295
|
+
- Just want to understand existing code → tell the user to run /explain
|
|
296
296
|
|
|
297
297
|
## DOCUMENT TYPE: {{doc_type}}
|
|
298
298
|
{{#if custom_topic}}Custom Topic: {{custom_topic}}{{/if}}
|
|
@@ -434,9 +434,9 @@ export const prdMacro = defineSkill({
|
|
|
434
434
|
- Refining vision or scope
|
|
435
435
|
|
|
436
436
|
## When NOT to Use
|
|
437
|
-
- No PRD exists yet →
|
|
438
|
-
- Need architecture docs →
|
|
439
|
-
- Need session summary →
|
|
437
|
+
- No PRD exists yet → tell the user to run /design first
|
|
438
|
+
- Need architecture docs → tell the user to run /architecture
|
|
439
|
+
- Need session summary → tell the user to run /session-notes
|
|
440
440
|
|
|
441
441
|
## STARTUP
|
|
442
442
|
|
|
@@ -519,8 +519,8 @@ export const sessionNotesMacro = defineSkill({
|
|
|
519
519
|
- Need to capture decisions for future reference
|
|
520
520
|
|
|
521
521
|
## When NOT to Use
|
|
522
|
-
- Need product requirements →
|
|
523
|
-
- Need architecture docs →
|
|
522
|
+
- Need product requirements → tell the user to run /prd
|
|
523
|
+
- Need architecture docs → tell the user to run /architecture
|
|
524
524
|
- In the middle of active work
|
|
525
525
|
|
|
526
526
|
## STARTUP
|
|
@@ -614,10 +614,10 @@ export const buildMacro = defineSkill({
|
|
|
614
614
|
- Ready to write code for a feature
|
|
615
615
|
|
|
616
616
|
## When NOT to Use
|
|
617
|
-
- Item is too vague →
|
|
618
|
-
- No backlog exists →
|
|
619
|
-
- Just exploring code →
|
|
620
|
-
- Need to fix a bug →
|
|
617
|
+
- Item is too vague → tell the user to run /refine-item first
|
|
618
|
+
- No backlog exists → tell the user to run /design or /sketch
|
|
619
|
+
- Just exploring code → tell the user to run /code-navigation
|
|
620
|
+
- Need to fix a bug → tell the user to run /debug
|
|
621
621
|
|
|
622
622
|
## CRITICAL: PROJECT DIRECTORY
|
|
623
623
|
|
|
@@ -711,9 +711,9 @@ export const scaffoldMacro = defineSkill({
|
|
|
711
711
|
- Need basic project structure
|
|
712
712
|
|
|
713
713
|
## When NOT to Use
|
|
714
|
-
- Project already has structure →
|
|
715
|
-
- Need requirements first →
|
|
716
|
-
- Modifying existing project →
|
|
714
|
+
- Project already has structure → tell the user to run /build
|
|
715
|
+
- Need requirements first → tell the user to run /design
|
|
716
|
+
- Modifying existing project → tell the user to run /appropriate
|
|
717
717
|
|
|
718
718
|
## FACTORY CHECK (TRY THIS FIRST)
|
|
719
719
|
|
package/dist/skills/types.d.ts
CHANGED
|
@@ -60,7 +60,39 @@ export interface CustomSkill {
|
|
|
60
60
|
license?: string;
|
|
61
61
|
version?: string;
|
|
62
62
|
tags?: string[];
|
|
63
|
+
/**
|
|
64
|
+
* ⚠️ LEGACY, AND NOT PART OF THE ANTHROPIC FORMAT. Zero of 50 Anthropic-authored SKILL.md files
|
|
65
|
+
* carry it; we invented it. It is read ONLY so the loader can migrate it — see
|
|
66
|
+
* `skillReachability`, which maps `enabled: false` onto both invocation switches being off.
|
|
67
|
+
* Never write it. Decision D-1 in the skills implementation plan.
|
|
68
|
+
*/
|
|
63
69
|
enabled?: boolean;
|
|
70
|
+
/**
|
|
71
|
+
* The model may NOT choose this skill (Anthropic: `disable-model-invocation`).
|
|
72
|
+
* Absent means the model may choose it, which is the format's default.
|
|
73
|
+
*/
|
|
74
|
+
disableModelInvocation?: boolean;
|
|
75
|
+
/**
|
|
76
|
+
* The user may type this skill as a slash command (Anthropic: `user-invocable`).
|
|
77
|
+
*
|
|
78
|
+
* ⚠️ WE DEFAULT THIS ON; ANTHROPIC'S FORMAT DEFAULTS IT OFF. Deliberate: every SKILL.md in
|
|
79
|
+
* compilr has always been typeable, and decision D-1 in commands-and-skills.md keeps it that
|
|
80
|
+
* way so `/my-thing` does not stop working for anyone who built one. Flipping the default would
|
|
81
|
+
* silently un-type every existing user skill.
|
|
82
|
+
*/
|
|
83
|
+
userInvocable?: boolean;
|
|
84
|
+
/** Argument hint shown for typed invocation (Anthropic: `argument-hint`). */
|
|
85
|
+
argumentHint?: string;
|
|
86
|
+
/** Tools this skill declares it needs (Anthropic: `allowed-tools`). */
|
|
87
|
+
allowedTools?: string[];
|
|
88
|
+
/**
|
|
89
|
+
* Every frontmatter key exactly as parsed, including ones we do not model.
|
|
90
|
+
*
|
|
91
|
+
* Populated on read so a host can SHOW `compatibility`, `license` and anything else without us
|
|
92
|
+
* having modelled it. Writing is done by patching the original text — see
|
|
93
|
+
* `patchSkillFrontmatter` — so this is for display, never for regeneration.
|
|
94
|
+
*/
|
|
95
|
+
rawFrontmatter?: Record<string, unknown>;
|
|
64
96
|
compilr?: CompilrSkillExtension;
|
|
65
97
|
/** Set when this skill was created via /skill fork. */
|
|
66
98
|
forkedFrom?: ForkedFromMarker;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@compilr-dev/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.29.0",
|
|
4
4
|
"description": "Universal agent runtime for building AI-powered applications",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -29,7 +29,8 @@
|
|
|
29
29
|
"./models": {
|
|
30
30
|
"types": "./dist/models/index.d.ts",
|
|
31
31
|
"import": "./dist/models/index.js"
|
|
32
|
-
}
|
|
32
|
+
},
|
|
33
|
+
"./package.json": "./package.json"
|
|
33
34
|
},
|
|
34
35
|
"files": [
|
|
35
36
|
"dist",
|