@vegastack/skills 0.12.0 → 0.13.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/package.json +1 -1
- package/skill/dev-architect/SKILL.md +5 -3
- package/skill/dev-chronicle/SKILL.md +1 -1
- package/skill/dev-debug/SKILL.md +1 -1
- package/skill/dev-implement/SKILL.md +1 -1
- package/skill/dev-implement/references/ledger-and-resume.md +1 -1
- package/skill/dev-intake/SKILL.md +1 -1
- package/skill/dev-plan/SKILL.md +2 -2
- package/skill/dev-review/SKILL.md +1 -1
- package/skill/dev-setup/SKILL.md +1 -1
- package/skill/dev-setup/references/stack-playbooks.md +2 -2
- package/skill/dev-ship/SKILL.md +2 -2
- package/skill/dev-status/SKILL.md +1 -1
- package/skill/skill-maintainer/SKILL.md +15 -4
- package/skill/skill-maintainer/references/release-ops.md +4 -4
- package/skill/skillify/SKILL.md +5 -4
- package/skill/skillify/assets/templates/skill.test.ts.template +1 -1
- package/skill/skillify/scripts/scaffold-skill.mjs +141 -25
- package/skill-integrity.json +17 -17
package/package.json
CHANGED
|
@@ -14,7 +14,9 @@ honestly. VegaStack is a 3-4 person team; every extra service is maintenance som
|
|
|
14
14
|
Nearest neighbors: `dev-setup` writes `.vegastack/dev.md` including its `## Architecture`
|
|
15
15
|
section — setup owns the file, this skill owns the judgment reading it. `dev-intake` routes
|
|
16
16
|
stack-bearing approach choices here while writing a brief; `vegastack-design-system` owns
|
|
17
|
-
component and token choices inside the UI.
|
|
17
|
+
component and token choices inside the UI. Artifact formats — the register line included — follow
|
|
18
|
+
`references/conventions.md`, the spec `dev-setup` authors and every dev-family skill ships a
|
|
19
|
+
copy of.
|
|
18
20
|
|
|
19
21
|
## Every task
|
|
20
22
|
|
|
@@ -32,8 +34,8 @@ component and token choices inside the UI.
|
|
|
32
34
|
3. Load only the references the task touches (table below). Do not bulk-read the set.
|
|
33
35
|
4. Separate what is fact, what is assumption, and what is MK's recorded decision. A
|
|
34
36
|
directive tagged "(inferred)" is a researched extrapolation MK has not ratified —
|
|
35
|
-
confirm on first use, and a confirmation is proposed as a register line
|
|
36
|
-
|
|
37
|
+
confirm on first use, and a confirmation is proposed as a register line in conventions'
|
|
38
|
+
Operator identity format, its decision text `ratified: <the directive>`; recording it drops
|
|
37
39
|
the tag from the reference file in the same change, so inferred never lingers as
|
|
38
40
|
ratified-in-practice. Everything untagged is his recorded decision or a verified fact.
|
|
39
41
|
Never re-litigate a recorded decision to route around a blocker — surface the blocker.
|
|
@@ -7,7 +7,7 @@ description: The project's narrative record — what got built, why, and how it
|
|
|
7
7
|
|
|
8
8
|
`.vegastack/chronicle.md` is the project's story, newest first — the answer to "what did I build here and what happened?" months later, when the operator remembers nothing. Entries are **story language for a human**, never commit-log prose: the changelog tells consumers what changed; the chronicle tells the operator what happened.
|
|
9
9
|
|
|
10
|
-
Nearest neighbors: `dev-implement` writes the entries at hand-back (the write rule lives there; the format lives here); `dev-status` answers "what needs me now" — this skill answers "how did we get here". `dev-ship`'s ship-gate checks entry presence when dev.md says `chronicle: on`.
|
|
10
|
+
Nearest neighbors: `dev-implement` writes the entries at hand-back (the write rule lives there; the format lives here); `dev-status` answers "what needs me now" — this skill answers "how did we get here". `dev-ship`'s ship-gate checks entry presence when dev.md says `chronicle: on`. Artifact formats follow `references/conventions.md`, the spec `dev-setup` authors and every dev-family skill ships a copy of.
|
|
11
11
|
|
|
12
12
|
## The entry — one per behavior-changing branch
|
|
13
13
|
|
package/skill/dev-debug/SKILL.md
CHANGED
|
@@ -7,7 +7,7 @@ description: Reproduce-first bug work. Use when given a bug to fix — "debug th
|
|
|
7
7
|
|
|
8
8
|
The failure this skill prevents: reading code, forming one theory, and "fixing" something that was never the cause. The discipline is a hard order — **reproduce, shrink, suspect, test, prove, clean** — and each phase has a completion criterion you can check, not vibe. It runs inside dev-implement's dark mode: no operator questions; missing-artifact stops are one `handback` comment; every phase result is a ledger checkpoint.
|
|
9
9
|
|
|
10
|
-
Nearest neighbors: `dev-intake`'s bug variant writes the brief this skill executes; `dev-implement` owns the surrounding build ceremony; `dev-review` judges the finished fix.
|
|
10
|
+
Nearest neighbors: `dev-intake`'s bug variant writes the brief this skill executes; `dev-implement` owns the surrounding build ceremony; `dev-review` judges the finished fix. Artifact formats follow `references/conventions.md`, the spec `dev-setup` authors and every dev-family skill ships a copy of.
|
|
11
11
|
|
|
12
12
|
## Phase 1 — the red command. No red command, no theorizing.
|
|
13
13
|
|
|
@@ -5,7 +5,7 @@ description: Implement an approved GitHub issue end to end without further user
|
|
|
5
5
|
|
|
6
6
|
# dev-implement
|
|
7
7
|
|
|
8
|
-
One issue, one session, end to end: preflight → claim → build dark → verify → review → evidence → stop. The operator reads the result in the issue on their own time; nothing here creates a PR or merges — those are `dev-ship`, on the operator's word. Artifact formats follow the `dev-setup` skill
|
|
8
|
+
One issue, one session, end to end: preflight → claim → build dark → verify → review → evidence → stop. The operator reads the result in the issue on their own time; nothing here creates a PR or merges — those are `dev-ship`, on the operator's word. Artifact formats follow `references/conventions.md`, the spec `dev-setup` authors and every dev-family skill ships a copy of; the ledger discipline lives in [ledger-and-resume](references/ledger-and-resume.md).
|
|
9
9
|
|
|
10
10
|
Nearest neighbors: `dev-plan` writes the plan this skill executes task by task; `dev-review` judges the result; issues that turn out to need decisions go back through `needs-operator`, never guessed. `.vegastack/dev.md` missing → run `dev-setup` first. Read dev.md before anything; its knobs govern this skill, and the `## Architecture` section governs stack-touching choices.
|
|
11
11
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Using the ledger
|
|
2
2
|
|
|
3
|
-
The ledger comment's format, line vocabulary, and the resume read-order (brief → plan → ledger → `git log`, nothing else) live in the `dev-setup` skill
|
|
3
|
+
The ledger comment's format, line vocabulary, and the resume read-order (brief → plan → ledger → `git log`, nothing else) live in `references/conventions.md`, the spec `dev-setup` authors and every dev-family skill ships a copy of — this file owns only how dev-implement **uses** them. The ledger is the build's recovery map and the operator's live progress view.
|
|
4
4
|
|
|
5
5
|
## When to checkpoint
|
|
6
6
|
|
|
@@ -5,7 +5,7 @@ description: Turn ideas, brainstorms, feature requests, bug reports, or SOW docu
|
|
|
5
5
|
|
|
6
6
|
# dev-intake
|
|
7
7
|
|
|
8
|
-
Requirements come in as the operator's brainstorm, feature thought, bug report, or SOW; issues go out complete enough that a fresh agent needs nothing but the URL. Every question gets asked **here** — once implementation starts, dark mode means no questions, so an under-specified issue becomes either an interruption or a guess. Artifact formats (markers, operator identity, labels, revisions) follow the `dev-setup` skill
|
|
8
|
+
Requirements come in as the operator's brainstorm, feature thought, bug report, or SOW; issues go out complete enough that a fresh agent needs nothing but the URL. Every question gets asked **here** — once implementation starts, dark mode means no questions, so an under-specified issue becomes either an interruption or a guess. Artifact formats (markers, operator identity, labels, revisions) follow `references/conventions.md`, the spec `dev-setup` authors and every dev-family skill ships a copy of.
|
|
9
9
|
|
|
10
10
|
Nearest neighbors: `dev-plan` owns the how once a brief is approved — intake owns the what/why and the approval mechanics; `dev-implement` builds. If `.vegastack/dev.md` is missing → run `dev-setup` first, then continue.
|
|
11
11
|
|
package/skill/dev-plan/SKILL.md
CHANGED
|
@@ -5,7 +5,7 @@ description: Write the implementation plan for an approved issue before any code
|
|
|
5
5
|
|
|
6
6
|
# dev-plan
|
|
7
7
|
|
|
8
|
-
The planning stage: an approved brief goes in, an operator-approved plan comes out, and only then does code exist. Plans are written against the repo **as it is now** — a separate session from intake for `full-plan` issues, because code drifts between brief approval and build. Formats follow
|
|
8
|
+
The planning stage: an approved brief goes in, an operator-approved plan comes out, and only then does code exist. Plans are written against the repo **as it is now** — a separate session from intake for `full-plan` issues, because code drifts between brief approval and build. Formats follow `references/conventions.md`, the spec `dev-setup` authors and every dev-family skill ships a copy of.
|
|
9
9
|
|
|
10
10
|
Nearest neighbors: `dev-intake` owns the brief and its approval mechanics — this skill owns the how; `dev-implement` executes what this produces, task by task. If `.vegastack/dev.md` is missing → run `dev-setup` first.
|
|
11
11
|
|
|
@@ -37,7 +37,7 @@ Checkboxes belong to the implement session — never pre-tick them.
|
|
|
37
37
|
|
|
38
38
|
## Labels and approval
|
|
39
39
|
|
|
40
|
-
Post the plan → flip to `needs-operator`. On the operator's "plan approved": record
|
|
40
|
+
Post the plan → flip to `needs-operator`. On the operator's "plan approved": record the approval marker comment per conventions with `scope=plan`, quoting their words in its operator-identity format, flip to `ready`, stop — building is dev-implement's.
|
|
41
41
|
|
|
42
42
|
## The ratchet — one home, this file
|
|
43
43
|
|
|
@@ -5,7 +5,7 @@ description: Independent review of finished implementation work — a diff again
|
|
|
5
5
|
|
|
6
6
|
# dev-review
|
|
7
7
|
|
|
8
|
-
Review is a specified system, not a vibe: fresh eyes per axis, severities with teeth, a bounded fix loop, and every dismissal on the record. The reviewer's job is findings or verified absence of findings — never praise. Formats follow the `dev-setup` skill
|
|
8
|
+
Review is a specified system, not a vibe: fresh eyes per axis, severities with teeth, a bounded fix loop, and every dismissal on the record. The reviewer's job is findings or verified absence of findings — never praise. Formats follow `references/conventions.md`, the spec `dev-setup` authors and every dev-family skill ships a copy of; the reviewer briefs live in [dispatch-prompts](references/dispatch-prompts.md).
|
|
9
9
|
|
|
10
10
|
Nearest neighbors: `dev-implement` invokes this per dev.md's `review:` knob and applies the findings; `dev-ship` consumes the verdict marker; `dev-plan`'s approval gate reviews plans before build — this skill reviews built work after.
|
|
11
11
|
|
package/skill/dev-setup/SKILL.md
CHANGED
|
@@ -65,7 +65,7 @@ Everything else — merge style, branch naming, the stop-and-ask list — takes
|
|
|
65
65
|
| `AGENTS.md` | create it, or insert/replace only the block between `<!-- vsk-dev:start -->` and `<!-- vsk-dev:end -->` using the [agents-section template](assets/agents-section.md.template); content outside the markers is the user's and stays untouched |
|
|
66
66
|
| `CLAUDE.md` | ensure its first line is `@AGENTS.md` — Claude Code does not read AGENTS.md natively and needs this import ([harness-facts](references/harness-facts.md)); create the file when absent |
|
|
67
67
|
| labels | `gh label create <name> --color <hex> --description "<text>"` for the names the `labels:` knob records, skipping ones that exist; default names and creation colors ([conventions](references/conventions.md) holds meanings): state `needs-operator` FBCA04 · `needs-plan` E36209 · `ready` 0E8A16 · `working` 1D76DB · `for-operator` 5319E7; modifiers `risky` B60205 · scope `research` C5DEF5 · `quick-build` 76C7C0 · `full-plan` 2A9D8F · `epic` 24292E (only when the org has no native Epic issue type) |
|
|
68
|
-
| decision register | create the file the `decisions:` knob names (default `.vegastack/decisions.md`) when missing, with a two-line header stating the format
|
|
68
|
+
| decision register | create the file the `decisions:` knob names (default `.vegastack/decisions.md`) when missing, with a two-line header stating the register-line format conventions' Operator identity section defines (username via `gh api user -q .login`, fallback `git config user.name`); a project with an existing register keeps it and the knob points there |
|
|
69
69
|
| guard workflows / hook files | only the ones the user said yes to in Round C |
|
|
70
70
|
|
|
71
71
|
## Step 4 — Report
|
|
@@ -8,12 +8,12 @@ Each playbook fills the same six slots: **detect** (file signals) · **ship draf
|
|
|
8
8
|
|
|
9
9
|
- **Detect:** `.changeset/config.json`, or a publish workflow / `"private": false` package.json with a registry config.
|
|
10
10
|
- **Ship draft:**
|
|
11
|
-
- `auto: apply pending changesets (changeset version)
|
|
11
|
+
- `auto: apply pending changesets (changeset version), install so any dependency changes reach the lockfile, commit as chore: release <version>`
|
|
12
12
|
- `ask: tag v<version> and push — the tag triggers the publish pipeline`
|
|
13
13
|
- `guard: tag matches the package version` · `guard: changelog has an entry for the tagged version`
|
|
14
14
|
- `auto: confirm the registry shows the new version; report old → new`
|
|
15
15
|
- **Changelog:** `changesets`. Per behavior-changing branch, dev-implement writes `.changeset/<slug>.md` **directly** — frontmatter `"<package-name>": <patch|minor|major>` (from the brief's version-impact line) plus the entry itself, shaped per dev-implement's changelog rule. `changeset add` (the bare `changeset` prompt) is interactive; never invoke it in a dark run — `changeset version` at release time is the only CLI use. The release changelog is changesets-written — never hand-edited.
|
|
16
|
-
- **Version identity:** `package.json` `version`, changesets-managed. Monorepos: identity is per-package; changesets handles multiple packages natively — the guard reads the released package's manifest. A private workspace root carries no *real* version — pin it at `0.0.0` (some tooling, e.g. `npm sbom` purl generation, requires every package to have one) and never bump it; deleting the field outright breaks such tooling.
|
|
16
|
+
- **Version identity:** `package.json` `version`, changesets-managed. Monorepos: identity is per-package; changesets handles multiple packages natively — the guard reads the released package's manifest. A private workspace root carries no *real* version — pin it at `0.0.0` (some tooling, e.g. `npm sbom` purl generation, requires every package to have one) and never bump it; deleting the field outright breaks such tooling. A lockfile is never the version identity, and package managers differ on whether a workspace's recorded version follows a version-only bump — npm re-records it on the next install, bun does not — so an older version inside a lockfile is a manager behavior to confirm before calling it a defect, and is never repaired by hand-editing the lockfile.
|
|
17
17
|
- **Guards:** changeset-presence (PR-time) · tag↔version · changelog-entry.
|
|
18
18
|
- **Rollback:** `Rollback is roll-forward: revert on main, release previous-good as a new patch, deprecate the bad version on the registry.`
|
|
19
19
|
|
package/skill/dev-ship/SKILL.md
CHANGED
|
@@ -7,7 +7,7 @@ description: Land finished work, each step only on the operator's explicit word.
|
|
|
7
7
|
|
|
8
8
|
Gates spent only by the user's words: their words asking for a PR authorize the PR and nothing more; their words asking to merge authorize the merge. The dev.md `gates` knob sets how many actions one word covers — `3` keeps PR and merge as separate words, `2` lets one "ship it" cover both, `1` is direct-to-main (the ship word merges locally and pushes; no PR object, everything else identical). Passing checks, PR permissions, and the calendar authorize nothing by themselves.
|
|
9
9
|
|
|
10
|
-
Nearest neighbor: `dev-implement` produces the `for-operator` issue with its evidence comment; ship packages and lands it. Corrections found here go back through implement's corrections loop.
|
|
10
|
+
Nearest neighbor: `dev-implement` produces the `for-operator` issue with its evidence comment; ship packages and lands it. Corrections found here go back through implement's corrections loop. Artifact formats — the register line included — follow `references/conventions.md`, the spec `dev-setup` authors and every dev-family skill ships a copy of.
|
|
11
11
|
|
|
12
12
|
## Gate 1 — the PR
|
|
13
13
|
|
|
@@ -33,7 +33,7 @@ With `gates: 1` there is no PR: the same verifications run, then the ship word t
|
|
|
33
33
|
On the user's merge instruction:
|
|
34
34
|
|
|
35
35
|
- Re-check that the PR head is still the revision the evidence comment names and checks are green — a branch that moved since review gets re-verified before it lands.
|
|
36
|
-
- Pending `Decision:` lines exist (issue comments, or the evidence comment's `**Decision:**` line) → name them in the merge confirmation — "merging will record: …" — so the operator's word demonstrably covers them; never append on inferred consent. On the word, append each to the register dev.md names (`decisions:` knob) in
|
|
36
|
+
- Pending `Decision:` lines exist (issue comments, or the evidence comment's `**Decision:**` line) → name them in the merge confirmation — "merging will record: …" — so the operator's word demonstrably covers them; never append on inferred consent. On the word, append each to the register dev.md names (`decisions:` knob) in conventions' Operator identity format; the register is append-only and this is its moment.
|
|
37
37
|
- A merge conflict with the default branch is corrections work: update the branch, re-verify what the update touched, and the standing merge instruction holds once checks are green again — unless the update changed behavior, or more than 7 days have passed since the word; either way, re-confirm with one sentence rather than acting on a stale instruction.
|
|
38
38
|
- Merge per the dev.md `merge` knob (default `gh pr merge --squash`; `gates: 1` merges locally per the same knob and pushes). `Closes #<n>` closes the issue; confirm both happened.
|
|
39
39
|
- A bot PR (Renovate, Dependabot) has no issue or evidence comment and merging it is still shipping: green checks qualify it, only the operator's explicit word — per PR or per named batch — merges it; majors and security advisories get named before their word is acted on.
|
|
@@ -7,7 +7,7 @@ description: The operator's board — whose move is it, across every issue in th
|
|
|
7
7
|
|
|
8
8
|
One question, answered from deterministic data: **whose move is it?** The bundled script gathers; this skill orders and narrates — it never invents state, and an unverifiable board is reported as exactly that.
|
|
9
9
|
|
|
10
|
-
Nearest neighbors: `dev-chronicle` answers "how did we get here"; this skill answers "what needs whom right now". Formats read via the
|
|
10
|
+
Nearest neighbors: `dev-chronicle` answers "how did we get here"; this skill answers "what needs whom right now". Formats read via the markers in `references/conventions.md`, the spec `dev-setup` authors and every dev-family skill ships a copy of.
|
|
11
11
|
|
|
12
12
|
## Gather
|
|
13
13
|
|
|
@@ -9,14 +9,14 @@ Maintenance skill for this repository. Every change under `skills/` must satisfy
|
|
|
9
9
|
|
|
10
10
|
## Operating rules
|
|
11
11
|
|
|
12
|
-
1. Skill content lives only in `skills/<name
|
|
12
|
+
1. Skill content lives only in the skill's own directory, at one of exactly two depths: `skills/<name>/` ungrouped, or `skills/<group>/<name>/` inside a group. Both are first-class; deeper is a build error. A group is a directory holding skills plus a `GROUP.md` (an H1 display title, then one blurb line), and group and skill names share one namespace and one grammar. Wiring lives outside the skill: the per-skill packaging allowlist in `packages/cli/packaging.json` — always keyed by **bare** skill name, because the packaged bundle is flat and an install command never carries a group — the root `README.md` skills table, and `CHANGELOG.md` (via changesets). The skillify scaffolder writes all three when creating a skill.
|
|
13
13
|
2. Frontmatter is exactly two keys: `name` and `description`. The open spec also allows `license`, `compatibility`, and `metadata`, but this repo defaults to the minimal two; adding any other key is a policy exception needing maintainer sign-off.
|
|
14
14
|
3. `name` must equal the directory name. Grammar: starts with a lowercase letter, then only lowercase letters, digits, and hyphens; no leading/trailing hyphen, no consecutive hyphens; at most 64 chars.
|
|
15
15
|
4. `description`: at most 1024 chars, third person, states WHAT the skill does and WHEN to load it, trigger words front-loaded, no angle brackets. Never summarize the workflow in it — agents follow the summary and skip the body. Err slightly pushy: agents under-trigger.
|
|
16
16
|
5. `SKILL.md` under 500 lines (target under 150) and roughly under 5k tokens. Detail goes to `references/`, executables to `scripts/`, templates to `assets/`. Relative links stay inside the skill, one level deep.
|
|
17
17
|
6. No Claude-only body syntax anywhere in a skill: no dynamic command injection, no argument placeholders, no Claude environment-variable paths — the exact token list is in [standards](references/standards.md). Reference scripts as plain relative paths runnable from the skill directory.
|
|
18
18
|
7. Never hand-edit checksums, versions, or timestamps in any `refresh/sources.json` — they must come from a refresh-runner run.
|
|
19
|
-
8. Before finishing any change: `node packages/cli/scripts/validate-skill.mjs
|
|
19
|
+
8. Before finishing any change: `node packages/cli/scripts/validate-skill.mjs <skill-dir>` and `bun test <skill-dir>` for the skill you touched, plus `node packages/cli/scripts/structure.mjs check` for the repo shape (all from repo root) must pass. `bun run check` runs the structure check as one of its stages.
|
|
20
20
|
|
|
21
21
|
## Route progressively
|
|
22
22
|
|
|
@@ -24,13 +24,24 @@ Maintenance skill for this repository. Every change under `skills/` must satisfy
|
|
|
24
24
|
|---|---|
|
|
25
25
|
| tri-harness standards: discovery paths, frontmatter rules, context budgets, install surfaces, portability rules, unverified items | [standards](references/standards.md) |
|
|
26
26
|
| new skill: should-it-exist gate, scaffolding, the 8-item contract audit, behavioral evals | the `skillify` skill |
|
|
27
|
+
| repo shape: groups, `GROUP.md`, the README sections, the structure check | the group workflow below |
|
|
27
28
|
| release, rename, deprecate, rollback mechanics | [release ops](references/release-ops.md) |
|
|
28
29
|
| this skill's own freshness contract | [REFRESH](refresh/REFRESH.md), [sources](refresh/sources.json) |
|
|
29
30
|
| authoritative repo policy | `CONTRIBUTING.md` and `.vegastack/dev.md` at the repo root (release runbook, content semver, rollback) |
|
|
30
31
|
|
|
32
|
+
## Workflow: create or maintain a group
|
|
33
|
+
|
|
34
|
+
Groups are this skill's responsibility: the repo shape, its `GROUP.md` files, and the root README sections that mirror them. The deterministic method is `packages/cli/scripts/structure.mjs`, run from the repo root.
|
|
35
|
+
|
|
36
|
+
1. **Create a group** — `node packages/cli/scripts/structure.mjs create-group <name> --title "<Display Title>" --blurb "<one line>"` prints the plan; add `--write` to apply. It writes `skills/<name>/GROUP.md` and inserts the matching `### <Display Title>` README section after the existing tables. Dry-run by default, and idempotent when re-run with the same title. Every refusal happens before it writes anything: a name that breaks the grammar, names an existing skill at either depth, or resolves to a symlink; a title another group already uses, or a different title on an existing group (that is a rename — edit `GROUP.md` and the README heading together); a title or blurb that would produce a `GROUP.md` its own reader rejects; an existing but malformed `GROUP.md`; and a README with no `## Skills` table to hold the section.
|
|
37
|
+
2. **Put skills in it** — skillify's scaffolder places a new skill with `--group <name>`; it refuses — before writing anything — an unknown group, a malformed `GROUP.md`, a group with no README section, and a skill name already used at either depth, so a mistyped group never creates a stray family and the flat bundle never gets two skills of one name. Moving an existing skill into a group is a `git mv` plus its README row and its test's validator-import depth; `structure.mjs check` names anything left inconsistent.
|
|
38
|
+
3. **Check the shape** — `node packages/cli/scripts/structure.mjs check` blocks on illegal depth, name collisions, a missing or malformed `GROUP.md`, two groups sharing one `GROUP.md` title, a group with no README section, stray files in a group, a skill missing a contract meta file, packaging entries that disagree with the authored tree, a group-qualified packaging key, and README rows that are absent, duplicated, mispathed, or in the wrong section. It warns — without blocking, so warnings never fail `bun run check` — on an empty group, a group of one, and scaffolded placeholder text; `--strict` makes warnings exit 1. Dot-prefixed files and directories under `skills/` are ignored everywhere — discovery, this check, and the packaging build alike: they are tool and OS leftovers, not skills.
|
|
39
|
+
|
|
40
|
+
A group never reaches an installer: the bundle is flat, so `GROUP.md` ships nowhere and `add <skill>` never names a group. Ungrouped skills at `skills/<name>/` stay fully supported; grouping is a choice, not a migration.
|
|
41
|
+
|
|
31
42
|
## Workflow: scaffold a new skill
|
|
32
43
|
|
|
33
|
-
1. Run skillify's `scripts/scaffold-skill.mjs` — it creates the contract tree (`SKILL.md`, `README.md`, `refresh/`, `agents/openai.yaml`, tests and the trigger fixture) and performs the repo wiring itself: the `packages/cli/packaging.json` entry, the root README row, and the changeset (a new skill is MINOR). Fill in the README row description and changeset text it leaves as
|
|
44
|
+
1. Run skillify's `scripts/scaffold-skill.mjs` — it creates the contract tree (`SKILL.md`, `README.md`, `refresh/`, `agents/openai.yaml`, tests and the trigger fixture) and performs the repo wiring itself: the `packages/cli/packaging.json` entry, the root README row in the right section, and the changeset (a new skill is MINOR). Pass `--group <name>` to place it in an existing group. Fill in the README row description and changeset text it leaves as placeholders; files added after scaffolding go into the skill's `packaging.json` entry by hand (the build fails loudly on unlisted files).
|
|
34
45
|
2. Write frontmatter and body per operating rules 2–6; the body routes to references, it does not inline them. `skillify` owns the full authoring and eval discipline.
|
|
35
46
|
3. Seed refresh baselines with the deterministic runner, never by hand — invocation in [REFRESH](refresh/REFRESH.md).
|
|
36
47
|
4. Run the checks in operating rule 8.
|
|
@@ -61,7 +72,7 @@ Run this checklist before merging any skill change; per-harness detail in [stand
|
|
|
61
72
|
- [ ] No Claude-only body syntax (token list in standards).
|
|
62
73
|
- [ ] `agents/openai.yaml` present for Codex; extra files are safely ignored by the other harnesses.
|
|
63
74
|
- [ ] Install surfaces respected: Claude Code `.claude/skills` (project) / `~/.claude/skills` (global); Codex `.agents/skills` (project and global); Hermes `~/.hermes/skills` **global only** — never assume Hermes project-level discovery.
|
|
64
|
-
- [ ] `node packages/cli/scripts/validate-skill.mjs
|
|
75
|
+
- [ ] `node packages/cli/scripts/validate-skill.mjs <skill-dir>` and `bun test <skill-dir>` pass, and `node packages/cli/scripts/structure.mjs check` is clean.
|
|
65
76
|
|
|
66
77
|
## Hard limits
|
|
67
78
|
|
|
@@ -24,7 +24,7 @@ profile carries a schema version to validate against.
|
|
|
24
24
|
## Release flow (tag-driven)
|
|
25
25
|
|
|
26
26
|
1. Every PR that changes released behavior lands with a changeset — a `.changeset/<slug>.md` written directly, since the `changeset` add prompt is interactive (`bunx changeset version` at release time is the only CLI use) — whose bump follows the table above and whose shape follows dev-implement's changelog rule.
|
|
27
|
-
2. Maintainer, at release time: `bunx changeset version` (applies changesets to `packages/cli/package.json` and the changelog), then `bun install` so `bun.lock`
|
|
27
|
+
2. Maintainer, at release time: `bunx changeset version` (applies changesets to `packages/cli/package.json` and the changelog), then `bun install` so any dependency changes riding along reach the lockfile, commit. That install does **not** update the workspace's own version inside `bun.lock`: measured on the bun the root `packageManager` line pins, the recorded version is left unchanged by a plain install, by `--force`, and even by `--lockfile-only`, so it sits at an older number indefinitely. `bun install --frozen-lockfile`, which CI and the release workflow both run, passes with it, and nothing in this repo reads it: `packages/cli/package.json` is the single release identity. Never hand-edit the lockfile to "correct" it. The release record is `packages/cli/CHANGELOG.md`, changesets-written — never by hand; the root `CHANGELOG.md` is the frozen pre-0.3.0 record pointing there.
|
|
28
28
|
3. Tag the release commit `v<version>`, push the tag.
|
|
29
29
|
4. The release workflow runs on the tag: `bun run check`, tag↔version guard, `npm publish` via trusted publishing (OIDC, token-free, provenance by default; idempotently skipped if already on the registry), SBOM, GitHub release. Never pass `--provenance` explicitly — it conflicts with trusted-publishing config.
|
|
30
30
|
|
|
@@ -46,14 +46,14 @@ Skill names are consumer-facing identifiers — treat a rename as a stable-ID br
|
|
|
46
46
|
2. Update every wiring point in the same PR: the skill's entry in `packages/cli/packaging.json`, the root README skills table row, and any cross-skill or docs links.
|
|
47
47
|
3. Changeset: MINOR by default — major only when the operator declares it. Either way, note the migration in `CHANGELOG.md`: copies installed under the old name are orphaned, and installer operations addressed to the old name stop resolving once the shipped manifest no longer knows it.
|
|
48
48
|
4. Registry source IDs inside `refresh/sources.json` are skill-internal and unaffected, but every `affected` ref must still resolve to a real reference in the renamed tree.
|
|
49
|
-
5. Re-run `node packages/cli/scripts/validate-skill.mjs
|
|
49
|
+
5. Re-run `node packages/cli/scripts/validate-skill.mjs <new-skill-dir>`, the skill's tests, and `node packages/cli/scripts/structure.mjs check` — name/directory equality is validated, and the structure check catches a README row left pointing at the old path.
|
|
50
50
|
|
|
51
51
|
## Deprecate / remove a skill
|
|
52
52
|
|
|
53
53
|
1. Announce deprecation in the root README table and `CHANGELOG.md` at least one release before removal when practical.
|
|
54
|
-
2. Removal: delete
|
|
54
|
+
2. Removal: delete the skill's directory, remove its allowlist entries and README row, MAJOR changeset with migration notes. Removing the last skill from a group also removes the group's `GROUP.md` and its README section.
|
|
55
55
|
3. Removing a skill in a new MAJOR does **not** deprecate previously published package versions — `npm deprecate` only versions that are themselves broken.
|
|
56
56
|
|
|
57
57
|
## Refresh branches
|
|
58
58
|
|
|
59
|
-
Branches named `refresh/**` are reserved for the automated freshness loop and are CI-restricted to `skills
|
|
59
|
+
Branches named `refresh/**` are reserved for the automated freshness loop and are CI-restricted to refresh metadata at either legal depth (`skills/<name>/refresh/` and `skills/<group>/<name>/refresh/`). Human content changes go on normal branches. Never hand-edit checksums/versions/timestamps anywhere — CI re-fetches claimed baselines, so hand-edited values cannot merge.
|
package/skill/skillify/SKILL.md
CHANGED
|
@@ -5,7 +5,7 @@ description: Repo-local skill factory and auditor for the vegastack-skills monor
|
|
|
5
5
|
|
|
6
6
|
# Skillify
|
|
7
7
|
|
|
8
|
-
Turn a raw workflow into a properly-skilled unit of this monorepo, or audit an existing `skills/<name>/`
|
|
8
|
+
Turn a raw workflow into a properly-skilled unit of this monorepo, or audit an existing skill tree — `skills/<name>/` or `skills/<group>/<name>/` — against the repo contract (CONTRIBUTING.md, "Adding a new skill"). Skillify is repo-only: it creates and scores skills in this repository, not anywhere else. Deep wiring/release mechanics belong to `skill-maintainer`; cross-reference it rather than restating it.
|
|
9
9
|
|
|
10
10
|
## The contract checklist
|
|
11
11
|
|
|
@@ -41,7 +41,7 @@ Scope upper bound: **one skill = one capability = one coherent trigger family.**
|
|
|
41
41
|
For an existing skill, score the checklist against the actual tree and stop with the verdict:
|
|
42
42
|
|
|
43
43
|
```
|
|
44
|
-
Skill: <name> Path: skills/<name>/
|
|
44
|
+
Skill: <name> Path: skills/<name>/ | skills/<group>/<name>/
|
|
45
45
|
Score: <passed>/8 Verdict: <verdict>
|
|
46
46
|
Missing: <item>: <one-line evidence> ...
|
|
47
47
|
```
|
|
@@ -94,8 +94,9 @@ Now that quality is proven:
|
|
|
94
94
|
## Phase 6 — Verify
|
|
95
95
|
|
|
96
96
|
```sh
|
|
97
|
-
node packages/cli/scripts/validate-skill.mjs
|
|
98
|
-
bun test
|
|
97
|
+
node packages/cli/scripts/validate-skill.mjs <skill-dir>
|
|
98
|
+
bun test <skill-dir>
|
|
99
|
+
node packages/cli/scripts/structure.mjs check
|
|
99
100
|
bun run check
|
|
100
101
|
```
|
|
101
102
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, expect, test } from 'bun:test'
|
|
2
2
|
import { readFileSync } from 'node:fs'
|
|
3
3
|
import { join, resolve } from 'node:path'
|
|
4
|
-
import { validateSkill } from '
|
|
4
|
+
import { validateSkill } from '{{validatorPath}}'
|
|
5
5
|
|
|
6
6
|
const skillRoot = resolve(import.meta.dir, '..')
|
|
7
7
|
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// Scaffold a new skill tree at skills/<name>/ from skillify's
|
|
2
|
+
// Scaffold a new skill tree at skills/<name>/ (or skills/<group>/<name>/) from skillify's
|
|
3
|
+
// templates.
|
|
3
4
|
//
|
|
4
|
-
// node scripts/scaffold-skill.mjs <skill-name> --dir <repo-root> [--write] [--json]
|
|
5
|
+
// node scripts/scaffold-skill.mjs <skill-name> --dir <repo-root> [--group <group>] [--write] [--json]
|
|
6
|
+
//
|
|
7
|
+
// --group places the skill in an existing group. Creating a group is skill-maintainer's job
|
|
8
|
+
// (packages/cli/scripts/structure.mjs create-group), so an unknown group is refused rather than
|
|
9
|
+
// invented: a mistyped group must never bring a stray family into existence.
|
|
5
10
|
//
|
|
6
11
|
// Dry-run by default: prints the plan (files that would be created plus the
|
|
7
12
|
// wiring actions that would be performed) and creates nothing. --write stages
|
|
@@ -9,14 +14,14 @@
|
|
|
9
14
|
// refusing existing directories and symlinks, then performs the repo wiring
|
|
10
15
|
// itself: packaging.json entry, root README row, changeset. Exit codes: 0 ok,
|
|
11
16
|
// 1 refusal or failure, 2 usage error.
|
|
12
|
-
import { lstat, mkdir, mkdtemp, readFile, rename, rm, writeFile } from 'node:fs/promises'
|
|
17
|
+
import { lstat, mkdir, mkdtemp, readFile, readdir, rename, rm, writeFile } from 'node:fs/promises'
|
|
13
18
|
import { dirname, join, resolve } from 'node:path'
|
|
14
19
|
import { fileURLToPath } from 'node:url'
|
|
15
20
|
|
|
16
21
|
const here = dirname(fileURLToPath(import.meta.url))
|
|
17
22
|
const templatesRoot = resolve(here, '..', 'assets', 'templates')
|
|
18
23
|
|
|
19
|
-
// Template file -> output path inside
|
|
24
|
+
// Template file -> output path inside the skill (null = tests/<name>.test.ts).
|
|
20
25
|
export const templateFiles = [
|
|
21
26
|
['SKILL.md.template', 'SKILL.md'],
|
|
22
27
|
['README.md.template', 'README.md'],
|
|
@@ -44,6 +49,31 @@ export function validateName(name) {
|
|
|
44
49
|
return null
|
|
45
50
|
}
|
|
46
51
|
|
|
52
|
+
// This script ships inside the skillify skill, so it must stay dependency-free and cannot import
|
|
53
|
+
// the repo's lib/skills.mjs. Deliberate small duplication of its GROUP.md title read; the two are
|
|
54
|
+
// kept honest by structure.mjs check, which fails when the README and GROUP.md disagree.
|
|
55
|
+
export function groupTitle(markdown) {
|
|
56
|
+
const lines = markdown.split('\n')
|
|
57
|
+
const headingIndex = lines.findIndex(line => /^#\s+\S/.test(line))
|
|
58
|
+
if (headingIndex === -1) return null
|
|
59
|
+
const title = lines[headingIndex].replace(/^#\s+/, '').trim()
|
|
60
|
+
const blurb = lines.slice(headingIndex + 1).find(line => line.trim() !== '')?.trim()
|
|
61
|
+
if (!title || !blurb || blurb.startsWith('#')) return null
|
|
62
|
+
return title
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Two-level scan mirroring lib/skills.mjs's discovery. Duplicated for the same reason as
|
|
66
|
+
// groupTitle: this script ships inside the skillify skill and cannot import repo tooling.
|
|
67
|
+
async function findSkillAnywhere(skillsRoot, name) {
|
|
68
|
+
if ((await entryAt(join(skillsRoot, name, 'SKILL.md')))?.isFile()) return join(skillsRoot, name)
|
|
69
|
+
for (const entry of await readdir(skillsRoot, { withFileTypes: true })) {
|
|
70
|
+
if (!entry.isDirectory()) continue
|
|
71
|
+
const path = join(skillsRoot, entry.name, name)
|
|
72
|
+
if ((await entryAt(join(path, 'SKILL.md')))?.isFile()) return path
|
|
73
|
+
}
|
|
74
|
+
return null
|
|
75
|
+
}
|
|
76
|
+
|
|
47
77
|
async function entryAt(path) {
|
|
48
78
|
try {
|
|
49
79
|
return await lstat(path)
|
|
@@ -73,21 +103,50 @@ async function wirePackaging(repoRoot, name, write) {
|
|
|
73
103
|
return { step: 'packaging.json entry', status: 'done' }
|
|
74
104
|
}
|
|
75
105
|
|
|
76
|
-
//
|
|
77
|
-
|
|
106
|
+
// Resolves where a row belongs: the end of the ungrouped table, or of the table under the
|
|
107
|
+
// group's "### <title>" section. Both windows are bounded by the "## Skills" region, so a row can
|
|
108
|
+
// never land in a neighbouring family's table or in an unrelated table elsewhere in the README.
|
|
109
|
+
// Returns null when the README has no usable table, so callers can refuse before writing rather
|
|
110
|
+
// than report a "skipped:" success afterwards.
|
|
111
|
+
export function findRowInsertion(lines, group, groupHeading) {
|
|
112
|
+
const regionStart = lines.findIndex(line => /^##\s+Skills\s*$/.test(line))
|
|
113
|
+
if (regionStart < 0) return null
|
|
114
|
+
const afterRegion = lines.findIndex((line, index) => index > regionStart && /^##\s+/.test(line) && !/^###/.test(line))
|
|
115
|
+
const regionEnd = afterRegion < 0 ? lines.length : afterRegion
|
|
116
|
+
|
|
117
|
+
let from = regionStart
|
|
118
|
+
let to = regionEnd
|
|
119
|
+
if (group) {
|
|
120
|
+
from = lines.findIndex((line, index) => index > regionStart && index < regionEnd && line.trim() === `### ${groupHeading}`)
|
|
121
|
+
if (from < 0) return { missingSection: true }
|
|
122
|
+
const next = lines.findIndex((line, index) => index > from && index < regionEnd && /^###\s+/.test(line))
|
|
123
|
+
to = next < 0 ? regionEnd : next
|
|
124
|
+
} else {
|
|
125
|
+
const firstSection = lines.findIndex((line, index) => index > regionStart && index < regionEnd && /^###\s+/.test(line))
|
|
126
|
+
if (firstSection >= 0) to = firstSection
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const header = lines.findIndex((line, index) => index >= from && index < to && /^\| *Skill *\|/.test(line))
|
|
130
|
+
if (header < 0 || !/^\|[ -]*---/.test(lines[header + 1] ?? '')) return null
|
|
131
|
+
let last = header + 1
|
|
132
|
+
while (last + 1 < to && lines[last + 1]?.startsWith('|')) last += 1
|
|
133
|
+
return { index: last }
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async function wireReadme(repoRoot, name, group, groupHeading, write) {
|
|
78
137
|
const path = join(repoRoot, 'README.md')
|
|
79
138
|
if (!(await entryAt(path))?.isFile()) return { step: 'root README row', status: 'skipped: README.md not found' }
|
|
80
139
|
const body = await readFile(path, 'utf8')
|
|
81
|
-
|
|
140
|
+
const relativePath = group ? `${group}/${name}` : name
|
|
141
|
+
if (body.includes(`](skills/${relativePath}/)`)) return { step: 'root README row', status: 'skipped: row already exists' }
|
|
82
142
|
const lines = body.split('\n')
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
while (lines[last + 1]?.startsWith('|')) last += 1
|
|
143
|
+
|
|
144
|
+
const target = findRowInsertion(lines, group, groupHeading)
|
|
145
|
+
if (target?.missingSection) throw new Error(`README.md has no "### ${groupHeading}" section for group "${group}" - create it with structure.mjs create-group`)
|
|
146
|
+
if (!target) return { step: 'root README row', status: 'skipped: Skills table not found' }
|
|
147
|
+
const last = target.index
|
|
89
148
|
if (!write) return { step: 'root README row', status: 'planned' }
|
|
90
|
-
const row = `| [${name}](skills/${
|
|
149
|
+
const row = `| [${name}](skills/${relativePath}/) | TODO: one-line description | [Walkthrough](skills/${relativePath}/README.md) · [SKILL.md](skills/${relativePath}/SKILL.md) |`
|
|
91
150
|
lines.splice(last + 1, 0, row)
|
|
92
151
|
await writeAtomic(path, lines.join('\n'))
|
|
93
152
|
return { step: 'root README row', status: 'done' }
|
|
@@ -104,15 +163,24 @@ async function wireChangeset(repoRoot, name, write) {
|
|
|
104
163
|
return { step: 'changeset', status: 'done' }
|
|
105
164
|
}
|
|
106
165
|
|
|
107
|
-
export async function wireSkill({ name, repoRoot, write = false }) {
|
|
166
|
+
export async function wireSkill({ name, repoRoot, group = null, groupHeading = null, write = false }) {
|
|
167
|
+
// groupHeading is an optimisation for scaffoldSkill, which has already read GROUP.md. A caller
|
|
168
|
+
// using the documented { name, repoRoot, group, write } shape gets it derived here rather than
|
|
169
|
+
// a row addressed to "### null".
|
|
170
|
+
if (group && !groupHeading) {
|
|
171
|
+
const doc = await entryAt(join(repoRoot, 'skills', group, 'GROUP.md'))
|
|
172
|
+
if (!doc?.isFile()) throw new Error(`Group "${group}" has no GROUP.md - every group carries one`)
|
|
173
|
+
groupHeading = groupTitle(await readFile(join(repoRoot, 'skills', group, 'GROUP.md'), 'utf8'))
|
|
174
|
+
if (!groupHeading) throw new Error(`skills/${group}/GROUP.md is malformed - it needs an H1 title followed by one non-empty blurb line`)
|
|
175
|
+
}
|
|
108
176
|
return [
|
|
109
177
|
await wirePackaging(repoRoot, name, write),
|
|
110
|
-
await wireReadme(repoRoot, name, write),
|
|
178
|
+
await wireReadme(repoRoot, name, group, groupHeading, write),
|
|
111
179
|
await wireChangeset(repoRoot, name, write),
|
|
112
180
|
]
|
|
113
181
|
}
|
|
114
182
|
|
|
115
|
-
export async function scaffoldSkill({ name, dir, write = false, now = new Date() }) {
|
|
183
|
+
export async function scaffoldSkill({ name, dir, group = null, write = false, now = new Date() }) {
|
|
116
184
|
const nameError = validateName(name)
|
|
117
185
|
if (nameError) throw new Error(`Invalid skill name ${JSON.stringify(name ?? null)}: ${nameError}`)
|
|
118
186
|
if (!dir) throw new Error('--dir <repo-root> is required')
|
|
@@ -123,19 +191,63 @@ export async function scaffoldSkill({ name, dir, write = false, now = new Date()
|
|
|
123
191
|
if (!skillsEntry || !skillsEntry.isDirectory()) {
|
|
124
192
|
throw new Error(`${skillsRoot} is not a real directory - point --dir at the vegastack-skills repo root`)
|
|
125
193
|
}
|
|
126
|
-
|
|
194
|
+
|
|
195
|
+
// A group must already exist and carry a well-formed GROUP.md; creating one is
|
|
196
|
+
// skill-maintainer's structure.mjs create-group, never a side effect of scaffolding a skill.
|
|
197
|
+
let groupHeading = null
|
|
198
|
+
if (group) {
|
|
199
|
+
const groupError = validateName(group)
|
|
200
|
+
if (groupError) throw new Error(`Invalid group name ${JSON.stringify(group)}: ${groupError}`)
|
|
201
|
+
const groupRoot = join(skillsRoot, group)
|
|
202
|
+
const groupEntry = await entryAt(groupRoot)
|
|
203
|
+
if (!groupEntry || !groupEntry.isDirectory()) {
|
|
204
|
+
throw new Error(`Group "${group}" does not exist at ${groupRoot} - create it first with: node packages/cli/scripts/structure.mjs create-group ${group} --title <title> --blurb <blurb> --write`)
|
|
205
|
+
}
|
|
206
|
+
const groupDoc = await entryAt(join(groupRoot, 'GROUP.md'))
|
|
207
|
+
if (!groupDoc?.isFile()) throw new Error(`Group "${group}" has no GROUP.md - every group carries one; see skill-maintainer's group workflow`)
|
|
208
|
+
groupHeading = groupTitle(await readFile(join(groupRoot, 'GROUP.md'), 'utf8'))
|
|
209
|
+
if (!groupHeading) throw new Error(`skills/${group}/GROUP.md is malformed - it needs an H1 title followed by one non-empty blurb line`)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const parent = group ? join(skillsRoot, group) : skillsRoot
|
|
213
|
+
const target = join(parent, name)
|
|
127
214
|
if (await entryAt(target)) throw new Error(`Refusing to scaffold: ${target} already exists`)
|
|
128
215
|
|
|
216
|
+
// Skill names are unique across the whole tree, not just within one directory: the packaged
|
|
217
|
+
// bundle is flat, so a duplicate at the other depth would break the next build. Checked here,
|
|
218
|
+
// before anything is written, rather than left to that build.
|
|
219
|
+
const clash = await findSkillAnywhere(skillsRoot, name)
|
|
220
|
+
if (clash) throw new Error(`Refusing to scaffold: a skill named "${name}" already exists at ${clash} - the packaged bundle is flat, so skill names are unique across the whole tree`)
|
|
221
|
+
|
|
222
|
+
// Every refusal belongs in this pre-flight. wireReadme runs after the tree is renamed into
|
|
223
|
+
// place and the packaging entry written, so anything discovered there would leave a half-wired
|
|
224
|
+
// skill on disk while reporting a refusal - or, worse, report success with no row at all.
|
|
225
|
+
const readmePath = join(repoRoot, 'README.md')
|
|
226
|
+
if ((await entryAt(readmePath))?.isFile()) {
|
|
227
|
+
const lines = (await readFile(readmePath, 'utf8')).split('\n')
|
|
228
|
+
const target = findRowInsertion(lines, group, groupHeading)
|
|
229
|
+
if (target?.missingSection) {
|
|
230
|
+
throw new Error(`README.md has no "### ${groupHeading}" section for group "${group}" - create it with structure.mjs create-group`)
|
|
231
|
+
}
|
|
232
|
+
if (!target) {
|
|
233
|
+
throw new Error(`README.md has no ${group ? `table under "### ${groupHeading}"` : 'ungrouped Skills table'} to add a row to - every skill needs its row, so refusing rather than scaffolding a skill the structure check would block`)
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// The generated test imports the repo validator by relative path, so its depth follows the
|
|
238
|
+
// skill's: skills/<name>/tests/ is three levels up, skills/<group>/<name>/tests/ is four.
|
|
239
|
+
const validatorPath = `${group ? '../../../..' : '../../..'}/packages/cli/scripts/validate-skill.mjs`
|
|
240
|
+
|
|
129
241
|
const outputs = templateFiles.map(([source, output]) => [source, output ?? `tests/${name}.test.ts`])
|
|
130
|
-
const plan = { name, target, files: outputs.map(([, output]) => output), wrote: false }
|
|
131
|
-
if (!write) return { ...plan, wiring: await wireSkill({ name, repoRoot }) }
|
|
242
|
+
const plan = { name, group, target, files: outputs.map(([, output]) => output), wrote: false }
|
|
243
|
+
if (!write) return { ...plan, wiring: await wireSkill({ name, repoRoot, group, groupHeading }) }
|
|
132
244
|
|
|
133
245
|
const date = now.toISOString().slice(0, 10)
|
|
134
|
-
const staging = await mkdtemp(join(
|
|
246
|
+
const staging = await mkdtemp(join(parent, `.${name}.scaffold-`))
|
|
135
247
|
try {
|
|
136
248
|
for (const [source, output] of outputs) {
|
|
137
249
|
const body = await readFile(join(templatesRoot, source), 'utf8')
|
|
138
|
-
const rendered = body.replaceAll('{{name}}', name).replaceAll('{{date}}', date)
|
|
250
|
+
const rendered = body.replaceAll('{{name}}', name).replaceAll('{{date}}', date).replaceAll('{{validatorPath}}', validatorPath)
|
|
139
251
|
const destination = join(staging, output)
|
|
140
252
|
await mkdir(dirname(destination), { recursive: true })
|
|
141
253
|
await writeFile(destination, rendered)
|
|
@@ -146,11 +258,11 @@ export async function scaffoldSkill({ name, dir, write = false, now = new Date()
|
|
|
146
258
|
await rm(staging, { recursive: true, force: true })
|
|
147
259
|
throw error
|
|
148
260
|
}
|
|
149
|
-
return { ...plan, wrote: true, wiring: await wireSkill({ name, repoRoot, write: true }) }
|
|
261
|
+
return { ...plan, wrote: true, wiring: await wireSkill({ name, repoRoot, group, groupHeading, write: true }) }
|
|
150
262
|
}
|
|
151
263
|
|
|
152
264
|
function parseArguments(argv) {
|
|
153
|
-
const options = { name: undefined, dir: undefined, write: false, json: false }
|
|
265
|
+
const options = { name: undefined, dir: undefined, group: null, write: false, json: false }
|
|
154
266
|
const rest = [...argv]
|
|
155
267
|
while (rest.length) {
|
|
156
268
|
const flag = rest.shift()
|
|
@@ -158,6 +270,10 @@ function parseArguments(argv) {
|
|
|
158
270
|
const value = rest.shift()
|
|
159
271
|
if (value === undefined || value.startsWith('-')) throw new Error('--dir requires a value')
|
|
160
272
|
options.dir = value
|
|
273
|
+
} else if (flag === '--group') {
|
|
274
|
+
const value = rest.shift()
|
|
275
|
+
if (value === undefined || value.startsWith('-')) throw new Error('--group requires a value')
|
|
276
|
+
options.group = value
|
|
161
277
|
} else if (flag === '--write') options.write = true
|
|
162
278
|
else if (flag === '--json') options.json = true
|
|
163
279
|
else if (flag.startsWith('-')) throw new Error(`Unknown option: ${flag}`)
|
|
@@ -165,7 +281,7 @@ function parseArguments(argv) {
|
|
|
165
281
|
else throw new Error(`Unexpected argument: ${flag}`)
|
|
166
282
|
}
|
|
167
283
|
if (!options.name || !options.dir) {
|
|
168
|
-
throw new Error('Usage: node scripts/scaffold-skill.mjs <skill-name> --dir <repo-root> [--write] [--json]')
|
|
284
|
+
throw new Error('Usage: node scripts/scaffold-skill.mjs <skill-name> --dir <repo-root> [--group <group>] [--write] [--json]')
|
|
169
285
|
}
|
|
170
286
|
return options
|
|
171
287
|
}
|
package/skill-integrity.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"skills": {
|
|
4
4
|
"dev-architect": {
|
|
5
5
|
"files": {
|
|
6
|
-
"SKILL.md": "
|
|
6
|
+
"SKILL.md": "74150dde2d3c83598769a4c9f8cd295a7496d9eb9a5e920a29cba8464eb58128",
|
|
7
7
|
"agents/openai.yaml": "99023b056d5c568ff3acc059e5bb7a4e6cfa5261cd0d35a525407f9e159de562",
|
|
8
8
|
"references/ai-agents.md": "565e5d480f7e342adf43f9b864ab4cb66b7e513f0ea676de66909fe0ccd95988",
|
|
9
9
|
"references/conventions.md": "815f5189daff1b71b3251b7a57de183a4a387fcdedbd5875e673d5bd3de398fc",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
},
|
|
22
22
|
"dev-chronicle": {
|
|
23
23
|
"files": {
|
|
24
|
-
"SKILL.md": "
|
|
24
|
+
"SKILL.md": "23c6266758726d6435de38cb0cc142a94a0b55f2329f7ab6404e42fa5d0dee3a",
|
|
25
25
|
"agents/openai.yaml": "765b12e17b7fce88c53ca0fae735bf0efceb843cc3703b91bc1801ab5cac06b4",
|
|
26
26
|
"references/conventions.md": "815f5189daff1b71b3251b7a57de183a4a387fcdedbd5875e673d5bd3de398fc",
|
|
27
27
|
"refresh/REFRESH.md": "760e66642eec426cd5ab2319613e817adf5bbd7b64c8eaa22dd13497351bf9c1",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
},
|
|
31
31
|
"dev-debug": {
|
|
32
32
|
"files": {
|
|
33
|
-
"SKILL.md": "
|
|
33
|
+
"SKILL.md": "38ba9e67c00925f4e4738be333dc398218008df929fcf17b61398966ce4deee1",
|
|
34
34
|
"agents/openai.yaml": "4aa52b5c620b0bf4de7f1609867956ce762d83dc4297ab5670a68c206ae63075",
|
|
35
35
|
"references/conventions.md": "815f5189daff1b71b3251b7a57de183a4a387fcdedbd5875e673d5bd3de398fc",
|
|
36
36
|
"references/loop-ladder.md": "ddcb45ec2241bc5dc25936348074cdcc8f9af848e90b363351a7faf97007c256",
|
|
@@ -40,10 +40,10 @@
|
|
|
40
40
|
},
|
|
41
41
|
"dev-implement": {
|
|
42
42
|
"files": {
|
|
43
|
-
"SKILL.md": "
|
|
43
|
+
"SKILL.md": "724f6bc83467c28dc61a45161d3ffba7fecef2712eff3333bdf40d762731db44",
|
|
44
44
|
"agents/openai.yaml": "1abab383deca54d32749fd9156f9a6211fe96e02e0166dc8a16ff8459ac31b74",
|
|
45
45
|
"references/conventions.md": "815f5189daff1b71b3251b7a57de183a4a387fcdedbd5875e673d5bd3de398fc",
|
|
46
|
-
"references/ledger-and-resume.md": "
|
|
46
|
+
"references/ledger-and-resume.md": "5e3deae2af67735940564e04adcbc23c0c0dc86da2f6c80d68b8c1207c8f5900",
|
|
47
47
|
"refresh/REFRESH.md": "7b854109d897b78b3055bf1ab89517afdca65c12eb65a4d009141e3ee11cf58f",
|
|
48
48
|
"refresh/sources.json": "7bd883e5b3512e236a909ad6ff20c97f3555785867c31fd728256c7c2c514c74",
|
|
49
49
|
"scripts/evidence-check.mjs": "9a360e6c5460d46b271b28457ee6be92f165c54c771c2dc6860c4a52e6890a9a",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
},
|
|
54
54
|
"dev-intake": {
|
|
55
55
|
"files": {
|
|
56
|
-
"SKILL.md": "
|
|
56
|
+
"SKILL.md": "4754383297822d3f6e0ad8a391ac76499d83c74f424b90b928199d6c2649dc71",
|
|
57
57
|
"agents/openai.yaml": "5473d2d3efffc518c07dfa8e6788c6535a5054ceca696119c5856ad265ef5581",
|
|
58
58
|
"references/brief-template.md": "906c6b805f9737755f4d4b34fd16d4296924b95039379ea86bc59973949d9823",
|
|
59
59
|
"references/conventions.md": "815f5189daff1b71b3251b7a57de183a4a387fcdedbd5875e673d5bd3de398fc",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
},
|
|
65
65
|
"dev-plan": {
|
|
66
66
|
"files": {
|
|
67
|
-
"SKILL.md": "
|
|
67
|
+
"SKILL.md": "eb2ce1542b1769fa6e7d9dc7be9fc9c84df8b353ded16e39e1e578ee372c1aca",
|
|
68
68
|
"agents/openai.yaml": "4b9ccc9870b4eafb73c1bc4492a0b5a530097bbcbae11dbf3235160f4f05587b",
|
|
69
69
|
"references/conventions.md": "815f5189daff1b71b3251b7a57de183a4a387fcdedbd5875e673d5bd3de398fc",
|
|
70
70
|
"references/plan-format.md": "a915acce2fc447e685077756aeb94fdc3f84139800703034987b8409971493db",
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
},
|
|
76
76
|
"dev-review": {
|
|
77
77
|
"files": {
|
|
78
|
-
"SKILL.md": "
|
|
78
|
+
"SKILL.md": "be32aa737cfd3309f7413407470ebffeedf6284f0f98cef6ae185c478c896c26",
|
|
79
79
|
"agents/openai.yaml": "3f35d3dc60f179d4ac2526997c4d10173ba3018d06f0dbe77e209ce2978fa702",
|
|
80
80
|
"assets/review-known-patterns.md.template": "2fe7dbcc4d0eeac542e7624111130265b2bb4f08806f20dbd2087842cbb22b5c",
|
|
81
81
|
"references/conventions.md": "815f5189daff1b71b3251b7a57de183a4a387fcdedbd5875e673d5bd3de398fc",
|
|
@@ -88,20 +88,20 @@
|
|
|
88
88
|
},
|
|
89
89
|
"dev-setup": {
|
|
90
90
|
"files": {
|
|
91
|
-
"SKILL.md": "
|
|
91
|
+
"SKILL.md": "d8eb7741983998f56ff8aff52f66ec3887b7fd055c26cc232e1ff41da6fab7c1",
|
|
92
92
|
"agents/openai.yaml": "be790c04fdf910e71642cfadf1a7c07c217bf7fad18581610feb4c31a79651f2",
|
|
93
93
|
"assets/agents-section.md.template": "6c9591d50f73e1e05e78c8969c7836767432cf32487af7205b2cb9f4a410440e",
|
|
94
94
|
"assets/dev-profile.md.template": "45b2a90fc7aee39e7b975151f1281787d6dd4d3b7d5f9df5b8f282d020a1d7a9",
|
|
95
95
|
"references/conventions.md": "815f5189daff1b71b3251b7a57de183a4a387fcdedbd5875e673d5bd3de398fc",
|
|
96
96
|
"references/harness-facts.md": "2be1d81f6a616d40dc865aaeefaf53c61bc13ade4ba1de8fb406c520bf15be40",
|
|
97
|
-
"references/stack-playbooks.md": "
|
|
97
|
+
"references/stack-playbooks.md": "8f457c3d0f96e3c99d745b04332590fdf8af53a9694541181d9c878ae0718ea2",
|
|
98
98
|
"refresh/REFRESH.md": "debda6ab96e899dd7e38309f714e9d65a3fbc53fd8ca68b8156f1348226c87b9",
|
|
99
99
|
"refresh/sources.json": "1252b43433d8799e9e577542f0898f8f2c83f17e661ca2d3e2d4fa57c8d3689a"
|
|
100
100
|
}
|
|
101
101
|
},
|
|
102
102
|
"dev-ship": {
|
|
103
103
|
"files": {
|
|
104
|
-
"SKILL.md": "
|
|
104
|
+
"SKILL.md": "aa3bd8b0a1ac076309190f2008896f24ebe1216352872ff428c1b88b55c06e65",
|
|
105
105
|
"agents/openai.yaml": "8f18cfd505131557eb4c9f7b7f54e9069f787be4c162403fc30253eed49a06ea",
|
|
106
106
|
"references/conventions.md": "815f5189daff1b71b3251b7a57de183a4a387fcdedbd5875e673d5bd3de398fc",
|
|
107
107
|
"references/runbook.md": "5cbf3f89b2fcad1a2873f729b6c1a027566344d5dffcb799ed1d039e76b3e679",
|
|
@@ -112,7 +112,7 @@
|
|
|
112
112
|
},
|
|
113
113
|
"dev-status": {
|
|
114
114
|
"files": {
|
|
115
|
-
"SKILL.md": "
|
|
115
|
+
"SKILL.md": "56415c2f69bb1a2fcb488b9846a20e632eb902b05c5b2b58ae9206d3eaffd77b",
|
|
116
116
|
"agents/openai.yaml": "72435947f5afef0c8a2c170e987b14fe6057b174318c16b6b538ae8d129791fb",
|
|
117
117
|
"references/conventions.md": "815f5189daff1b71b3251b7a57de183a4a387fcdedbd5875e673d5bd3de398fc",
|
|
118
118
|
"refresh/REFRESH.md": "52aee9bae64d8342e127704d1db282e43ed57c101148727bf10172d6cb975bfa",
|
|
@@ -122,9 +122,9 @@
|
|
|
122
122
|
},
|
|
123
123
|
"skill-maintainer": {
|
|
124
124
|
"files": {
|
|
125
|
-
"SKILL.md": "
|
|
125
|
+
"SKILL.md": "4645ed387cc6cf1c6c1e8aa0f08f7dd73274727030018af3d42fbaa360a001c0",
|
|
126
126
|
"agents/openai.yaml": "0b89d4e6416cf20b448f5322f3d7433cfde333d2b4a05b42ed53db3759701fb4",
|
|
127
|
-
"references/release-ops.md": "
|
|
127
|
+
"references/release-ops.md": "b5df1922b34daf7751c332c019a0ea770a48283aaa2e25f83c9851bef47999b1",
|
|
128
128
|
"references/standards.md": "3646d0dd0560a87b333f70febf670b093c89b35111132cdb641b763bf184b02f",
|
|
129
129
|
"refresh/REFRESH.md": "3955bff5f0a14c83336a73cdbd07ba18d9398996934af87fac4454206cd14023",
|
|
130
130
|
"refresh/sources.json": "4977152381d53555196ac676f95e61b4eaa301309a17560da5cda722010eb80f"
|
|
@@ -132,20 +132,20 @@
|
|
|
132
132
|
},
|
|
133
133
|
"skillify": {
|
|
134
134
|
"files": {
|
|
135
|
-
"SKILL.md": "
|
|
135
|
+
"SKILL.md": "310ac32ff4d557cc166a470fd1866060dfc5f03898e8ff3da9c406b84580742b",
|
|
136
136
|
"agents/openai.yaml": "df320cf32a8db06c213f7892844dab810b7e96cfbf32502d25185d4a4167ef87",
|
|
137
137
|
"assets/templates/README.md.template": "68230ada702ad39f5de1f35e838ba590d89ca19b23868d4499353db6034fcc2c",
|
|
138
138
|
"assets/templates/REFRESH.md.template": "ff86afade565d668333e7f6d0432213760fd68a62b8ba22897a8601b74c94ac0",
|
|
139
139
|
"assets/templates/SKILL.md.template": "b038775f97b13b54227fe41a767a48cdaa922a93d1732f22c2d1d79bff52ca00",
|
|
140
140
|
"assets/templates/openai.yaml.template": "2fbf488369825109d6cad4f17e7b729a40d9e3c43c7f01bc5dfa16361d59c086",
|
|
141
|
-
"assets/templates/skill.test.ts.template": "
|
|
141
|
+
"assets/templates/skill.test.ts.template": "9ca1e63ba6566350284c742e0e7958816d571fa57bd979b2c6a09104a0d9477c",
|
|
142
142
|
"assets/templates/sources.json.template": "39449adcef33e6ffbc369a342bbfd1f299e9327157fe873991e1ed8d243288d1",
|
|
143
143
|
"assets/templates/trigger-queries.json.template": "37517e5f3dc66819f61f5a7bb8ace1921282415f10551d2defa5c3eb0985b570",
|
|
144
144
|
"references/authoring.md": "de61bc54140e07bb1910d5adac4e988e10fea9bd11b3e27234c0c699aa7bbf0d",
|
|
145
145
|
"references/eval-playbook.md": "1f463dfea8d69b7022712776794369892643aa020186fe3a3707c493a6c2d4e9",
|
|
146
146
|
"refresh/REFRESH.md": "fb74342bec10fbb8d28590dfde9147564346f2eba8976777073901a56e2742a8",
|
|
147
147
|
"refresh/sources.json": "e554e80c72c16dc70587361804db9e0cf2716c128820b2612d5983a009b2b242",
|
|
148
|
-
"scripts/scaffold-skill.mjs": "
|
|
148
|
+
"scripts/scaffold-skill.mjs": "7304795fa30834ce2e44c834e99d1cf2d07caafcdd5c30ebf0f49e13096a5d57"
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
151
|
}
|