@sassywares/skills 0.0.0-stage → 0.2.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/CONCEPTS.md +59 -0
- package/LICENSE +21 -0
- package/README.md +333 -2
- package/bin/sassy-skills.mjs +193 -0
- package/package.json +41 -4
- package/skills/sassy-babysit/SKILL.md +74 -0
- package/skills/sassy-commit/SKILL.md +53 -0
- package/skills/sassy-commit-push-pr/SKILL.md +55 -0
- package/skills/sassy-compare/SKILL.md +95 -0
- package/skills/sassy-compare/scripts/compare.py +493 -0
- package/skills/sassy-compound/SKILL.md +56 -0
- package/skills/sassy-compound/references/concepts.md +11 -0
- package/skills/sassy-compound/references/templates.md +48 -0
- package/skills/sassy-cycle/SKILL.md +66 -0
- package/skills/sassy-debug/SKILL.md +109 -0
- package/skills/sassy-handbook/SKILL.md +51 -0
- package/skills/sassy-handbook/references/handbook-format.md +69 -0
- package/skills/sassy-handoff/SKILL.md +55 -0
- package/skills/sassy-lfg/SKILL.md +89 -0
- package/skills/sassy-plan/SKILL.md +66 -0
- package/skills/sassy-plan/marker +1 -0
- package/skills/sassy-plan/references/plan-format.md +78 -0
- package/skills/sassy-review/SKILL.md +54 -0
- package/skills/sassy-scout/SKILL.md +53 -0
- package/skills/sassy-setup/SKILL.md +82 -0
- package/skills/sassy-setup/assets/sassywares-readme.md +30 -0
- package/skills/sassy-setup/assets/snapshot +262 -0
- package/skills/sassy-setup/references/config.md +268 -0
- package/skills/sassy-setup/scripts/detect.sh +401 -0
- package/skills/sassy-ship/SKILL.md +62 -0
- package/skills/sassy-skill/SKILL.md +70 -0
- package/skills/sassy-validate/SKILL.md +43 -0
- package/skills/sassy-work/SKILL.md +110 -0
package/CONCEPTS.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Sassy Skills — Concepts
|
|
2
|
+
|
|
3
|
+
## Source of truth
|
|
4
|
+
|
|
5
|
+
The repo's `skills/` directory is the only copy of the skills. It ships as the npm package `@sassywares/skills`, so consumers pin a version rather than linking a directory; the sync service handles git commit and push.
|
|
6
|
+
|
|
7
|
+
## Skill
|
|
8
|
+
|
|
9
|
+
A directory containing `SKILL.md`, discovered by a harness from a project skills directory, a user skills directory, or an installed package. Skills are explicit workflows, not always-on instructions. Most have `disable-model-invocation: true` and are invoked with `/skill:<name>`.
|
|
10
|
+
|
|
11
|
+
## Agent
|
|
12
|
+
|
|
13
|
+
A specialized subagent defined under `~/.pi/agent/agents/`. Skills delegate bounded work to agents such as `scout`, `worker`, `reviewer`, and `validator`.
|
|
14
|
+
|
|
15
|
+
## Orchestrator
|
|
16
|
+
|
|
17
|
+
A skill that mostly coordinates. It reads compact results, writes briefs, and avoids reading source or diffs in the main context. `sassy-work`, `sassy-lfg`, `sassy-ship`, and `sassy-debug` are orchestrators.
|
|
18
|
+
|
|
19
|
+
## Cycle
|
|
20
|
+
|
|
21
|
+
`sassy-cycle` runs one unit of work through implement → review → validate → fix → re-check. It never commits. Work, LFG, and debug all use it.
|
|
22
|
+
|
|
23
|
+
## Gates
|
|
24
|
+
|
|
25
|
+
A gate is a deliberate stop or pass/fail condition.
|
|
26
|
+
|
|
27
|
+
- **Fix gate** — `sassy-debug` presents the causal chain, fix, and risks before changing code.
|
|
28
|
+
- **Code approval gate** — the user approves the code before committing or shipping. Always on for `sassy-work` and `sassy-lfg`.
|
|
29
|
+
- **Ship review gate** — `sassy-ship` summarizes the close-out and waits in `mode:manual` (default); skipped only when the caller passed `mode:auto`.
|
|
30
|
+
- **QA gate** — manual QA checklists, part of phase completion rather than a separate track: a phase turns `Done` only when the code is approved and every QA row passed. The user runs the rows; no agent ticks its own. It exists only when the plan document has QA tables; an in-context brief has none.
|
|
31
|
+
|
|
32
|
+
## Modes
|
|
33
|
+
|
|
34
|
+
Two mode tokens: `mode:manual` (default) and `mode:auto`. `mode:manual` asks at the skill's gate; `mode:auto` means pre-authorized, no review.
|
|
35
|
+
|
|
36
|
+
- `sassy-ship` and `sassy-compound` accept both. Ship reviews the whole close-out in `mode:manual` and forwards `mode:auto` to compound once its own gate is answered.
|
|
37
|
+
- `sassy-debug` accepts no mode. It always leaves ship's review gate in place.
|
|
38
|
+
- `sassy-work` and `sassy-lfg` accept no mode.
|
|
39
|
+
|
|
40
|
+
Everything else is decided by the artifact, not a mode:
|
|
41
|
+
|
|
42
|
+
- A plan document under `docs/plans/` drives phased execution and carries manual QA tables.
|
|
43
|
+
- An in-context brief drives one cycle and has no plan file and no QA tables.
|
|
44
|
+
|
|
45
|
+
## Plan
|
|
46
|
+
|
|
47
|
+
A durable document under `docs/plans/` with intent, decisions, constraints, phase tracking, and per-phase manual QA tables.
|
|
48
|
+
|
|
49
|
+
## Brief
|
|
50
|
+
|
|
51
|
+
A settled in-context brief produced by `sassy-plan` when the user picks "start working now" or "take it all the way". It carries the goal, decisions, constraints, work, and done-when, but no plan file and no QA tables.
|
|
52
|
+
|
|
53
|
+
## Handoff
|
|
54
|
+
|
|
55
|
+
`sassy-handoff` uses the `/handoff` extension to summarize the current session and continue in a fresh session. It is command-only because a skill cannot replace a session by itself.
|
|
56
|
+
|
|
57
|
+
## Sync
|
|
58
|
+
|
|
59
|
+
There is no background watcher. Any agent that changes a skill must run `scripts/sync.sh` immediately, without asking for confirmation. The sync script commits the change, rebases on `origin/main`, and pushes. It installs nothing: a changed skill reaches consumers only when a new version is released and a consumer bumps its pin.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sassywares
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,334 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Sassy Skills
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Lean and mean skills for shipping fast — with full control and 100% reliability.**
|
|
4
|
+
|
|
5
|
+
Sassy Skills is a suite of explicit-invocation Agent Skills. It follows the Agent Skills format, so it works with any harness that can load `SKILL.md` workflows.
|
|
6
|
+
|
|
7
|
+
It covers the whole path from a rough idea to a shipped PR, and from a bug report to a verified fix — without giving up human control at the moments that matter.
|
|
8
|
+
|
|
9
|
+
## Table of contents
|
|
10
|
+
|
|
11
|
+
- [Why this suite](#why-this-suite)
|
|
12
|
+
- [Quick start](#quick-start)
|
|
13
|
+
- [Skills](#skills)
|
|
14
|
+
- [Project config](#project-config)
|
|
15
|
+
- [Workflows](#workflows)
|
|
16
|
+
- [From idea to ship](#from-idea-to-ship)
|
|
17
|
+
- [From bug to ship](#from-bug-to-ship)
|
|
18
|
+
- [Gates and controls](#gates-and-controls)
|
|
19
|
+
- [Repository layout](#repository-layout)
|
|
20
|
+
- [Installation](#installation)
|
|
21
|
+
- [Repository maintenance](#repository-maintenance)
|
|
22
|
+
|
|
23
|
+
## Why this suite
|
|
24
|
+
|
|
25
|
+
- **Lean.** Skills are indexes and workflows, not documentation dumps. Detail moves into `references/` only when it is conditionally needed.
|
|
26
|
+
- **Explicit.** Every skill is invoked deliberately. No silent background behavior.
|
|
27
|
+
- **Controlled.** Code approval, manual QA, and ship review are visible gates that all run in sequence. There are two mode tokens, `mode:manual` (default) and `mode:auto`, accepted by `sassy-ship` and `sassy-compound` only; every other skill takes no mode.
|
|
28
|
+
- **Reliable.** Work runs through `sassy-cycle` with isolated subagents for implementation, review, validation, and fixes.
|
|
29
|
+
- **Context-efficient.** Orchestrators coordinate; subagents do the heavy reading and return compact results.
|
|
30
|
+
- **Never surprising.** Nothing is committed just because a step finished. Shipping is its own phase.
|
|
31
|
+
|
|
32
|
+
## Quick start
|
|
33
|
+
|
|
34
|
+
Examples use simple slash commands.
|
|
35
|
+
|
|
36
|
+
An optional first step: `/sassy-setup` detects the project and writes `.sassywares/config.yaml`, so plans, learnings, and PR bases land where your repo actually keeps them. Skipping it is fine — every key falls back to a documented default.
|
|
37
|
+
|
|
38
|
+
```text
|
|
39
|
+
/sassy-plan Add workspace invitations
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
That discussion settles the work, then offers a pick:
|
|
43
|
+
|
|
44
|
+
```text
|
|
45
|
+
Settled. Pick one:
|
|
46
|
+
1. Write it to docs/plans/<slug>.md
|
|
47
|
+
2. Start working now — stop before ship
|
|
48
|
+
3. Take it all the way — run it, ship it, babysit the PR
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Pick 1 to get a plan file, then run one phase at a time:
|
|
52
|
+
|
|
53
|
+
```text
|
|
54
|
+
/sassy-work docs/plans/add-workspace-invitations.md
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Or take the whole plan through one session:
|
|
58
|
+
|
|
59
|
+
```text
|
|
60
|
+
/sassy-lfg docs/plans/add-workspace-invitations.md
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
For small work that does not need a plan document, choose 2 or 3 at the picker, or invoke the executor directly with the brief:
|
|
64
|
+
|
|
65
|
+
```text
|
|
66
|
+
/sassy-work "Add a copy-link button to the invite modal"
|
|
67
|
+
/sassy-lfg "Add a copy-link button to the invite modal"
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Debugging:
|
|
71
|
+
|
|
72
|
+
```text
|
|
73
|
+
/sassy-debug https://github.com/org/repo/issues/123
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Skills
|
|
77
|
+
|
|
78
|
+
### Planning and execution
|
|
79
|
+
|
|
80
|
+
| Skill | What it does |
|
|
81
|
+
|---|---|
|
|
82
|
+
| **`sassy-plan`** | Brainstorms and settles the work in one discussion, then offers three choices: write the plan, start working now, or take it all the way to a PR. |
|
|
83
|
+
| **`sassy-scout`** | Grounds an area on demand and writes the shared grounding brief other agents read instead of re-exploring. |
|
|
84
|
+
| **`sassy-work`** | Executes exactly one plan phase, or one in-context brief, commits after code approval, then hands off the next phase — a brief stops before ship. |
|
|
85
|
+
| **`sassy-lfg`** | Executes every phase of a plan, or one in-context brief, then closes out via `sassy-ship` after code approval. |
|
|
86
|
+
| **`sassy-cycle`** | Runs one unit of work through implement → review → validate → fix → re-check. |
|
|
87
|
+
| **`sassy-debug`** | Diagnoses a bug, gates the fix, fixes via `sassy-cycle`, then ships after code approval. Debug takes no mode and always leaves ship's review gate in place unless you invoke ship yourself with `mode:auto`. |
|
|
88
|
+
| **`sassy-handoff`** | Summarizes the session and opens a fresh one via `/handoff`, with the continuation prompt seeded in the editor for you to submit. |
|
|
89
|
+
|
|
90
|
+
### Code quality
|
|
91
|
+
|
|
92
|
+
| Skill | What it does |
|
|
93
|
+
|---|---|
|
|
94
|
+
| **`sassy-review`** | Reviews a diff, branch, PR, or files for bugs, project-rule conformance, and design-system conformance, then offers to fix findings. |
|
|
95
|
+
| **`sassy-validate`** | Checks whether work matches a plan or stated intent, then offers to close gaps. |
|
|
96
|
+
|
|
97
|
+
### Shipping
|
|
98
|
+
|
|
99
|
+
| Skill | What it does |
|
|
100
|
+
|---|---|
|
|
101
|
+
| **`sassy-ship`** | Compound learnings, optional handbook, commit, PR, and babysit. Review gate in `mode:manual` (default), skipped by `mode:auto`. |
|
|
102
|
+
| **`sassy-commit`** | Creates a local commit with a clear message. No push, no PR. |
|
|
103
|
+
| **`sassy-commit-push-pr`** | Commits, pushes, opens or updates a PR, and babysits by default. |
|
|
104
|
+
| **`sassy-babysit`** | Watches a PR until it looks merge-ready or blocked. |
|
|
105
|
+
|
|
106
|
+
### Knowledge and docs
|
|
107
|
+
|
|
108
|
+
| Skill | What it does |
|
|
109
|
+
|---|---|
|
|
110
|
+
| **`sassy-compound`** | Records a solved problem as a durable repo learning. Takes `mode:manual` or `mode:auto`; ship passes `mode:auto` after its own gate. |
|
|
111
|
+
| **`sassy-handbook`** | Writes a plain-English team handbook with mermaid diagrams. |
|
|
112
|
+
|
|
113
|
+
### Meta
|
|
114
|
+
|
|
115
|
+
| Skill | What it does |
|
|
116
|
+
|---|---|
|
|
117
|
+
| **`sassy-skill`** | Creates, retunes, or restructures Agent Skills. |
|
|
118
|
+
| **`sassy-setup`** | Detects the project topology and writes `.sassywares/config.yaml`; installs the snapshot tooling for a multi-repo workspace. |
|
|
119
|
+
| **`sassy-compare`** | Runs one prompt against two models in isolated worktrees, with optional per-side thinking levels, and compares output, code changes, timing, tokens, and errors. |
|
|
120
|
+
|
|
121
|
+
## Project config
|
|
122
|
+
|
|
123
|
+
Every skill resolves one file, `.sassywares/config.yaml`, found by walking up from where you are. It is optional — no file means the documented defaults.
|
|
124
|
+
|
|
125
|
+
```yaml
|
|
126
|
+
artifacts: # where plans, learnings, handbooks, and the glossary live
|
|
127
|
+
plans: docs/plans
|
|
128
|
+
solutions: docs/solutions
|
|
129
|
+
handbooks: docs/handbooks
|
|
130
|
+
concepts: CONCEPTS.md
|
|
131
|
+
knowledge:
|
|
132
|
+
read: [docs/solutions, CONCEPTS.md] # searched for prior learnings
|
|
133
|
+
write: true # false = this project takes no learnings
|
|
134
|
+
git:
|
|
135
|
+
repo: origin # a remote name | workspace (repos below, none here) | none (no git)
|
|
136
|
+
# remote: org/backup # workspace only: the snapshot mirror; required when repo: workspace
|
|
137
|
+
default_branch: main # what branches are cut from and PRs target
|
|
138
|
+
repos: # workspace only: each nested repo -> the base its PRs target
|
|
139
|
+
fe: dev
|
|
140
|
+
commit:
|
|
141
|
+
branch: current
|
|
142
|
+
pr:
|
|
143
|
+
babysit: true
|
|
144
|
+
merge: false # true authorizes merging every babysat PR here
|
|
145
|
+
delete_branch: false
|
|
146
|
+
hooks: # project steps injected into the PR pipeline
|
|
147
|
+
pr:
|
|
148
|
+
before_create: [] # after the commit, before the PR opens
|
|
149
|
+
after_create: [] # the moment the PR exists
|
|
150
|
+
before_merge: [] # this run is about to merge
|
|
151
|
+
after_merge: [] # this run observed the merge
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Extending the pipeline
|
|
155
|
+
|
|
156
|
+
A project adds its own steps by naming a skill that lives in its own `.agents/skills/`. Nothing declared means nothing runs.
|
|
157
|
+
|
|
158
|
+
| Hook | Runs | Typical use |
|
|
159
|
+
|---|---|---|
|
|
160
|
+
| `before_create` | after the commit, before the PR opens | release artifacts: changeset, release notes, i18n sync |
|
|
161
|
+
| `after_create` | the PR was just created | labels, deploy-target comment |
|
|
162
|
+
| `before_merge` | this run is about to merge | a final approval gate |
|
|
163
|
+
| `after_merge` | this run observed the merge | sync a back-merge branch, tag, notify |
|
|
164
|
+
|
|
165
|
+
Hooks run in declared order, one subagent each, and fail closed: a declared skill that is missing or returns `blocked` stops the run. Only `before_create` may commit its own output, because it runs after the pipeline's commit. `before_merge` and `after_merge` never fire unless this run merges.
|
|
166
|
+
|
|
167
|
+
This is deliberately not a fork mechanism. A project skill that *adds* a step is a hook; one that *restates* our steps is a copy, and copies rot. Rules are not hooks either — those belong in `AGENTS.md`.
|
|
168
|
+
|
|
169
|
+
Precedence is always explicit token > session instruction > config > skill default. `sassy-setup` writes the file from the tree it detects and keeps `pr.merge` off unless you say otherwise. The full schema, resolution chains, and the three topologies are in [`skills/sassy-setup/references/config.md`](skills/sassy-setup/references/config.md).
|
|
170
|
+
|
|
171
|
+
In a `workspace` there is exactly one ignore file, and it is not at the root:
|
|
172
|
+
|
|
173
|
+
```text
|
|
174
|
+
.sassywares/
|
|
175
|
+
├── config.yaml
|
|
176
|
+
└── workspace/
|
|
177
|
+
├── snapshot installed by sassy-setup
|
|
178
|
+
├── .gitignore what the snapshot leaves out
|
|
179
|
+
└── mirror/ the side clone, excluded by the script itself
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Workflows
|
|
183
|
+
|
|
184
|
+
### From idea to ship
|
|
185
|
+
|
|
186
|
+
```mermaid
|
|
187
|
+
flowchart LR
|
|
188
|
+
A[Idea] --> B[sassy-plan]
|
|
189
|
+
B --> C[Discussion]
|
|
190
|
+
C --> D{3-option picker}
|
|
191
|
+
D -->|1. plan document| E[docs/plans/slug.md]
|
|
192
|
+
D -->|2. start working| F[sassy-work]
|
|
193
|
+
D -->|3. take it all the way| G[sassy-lfg]
|
|
194
|
+
E --> F
|
|
195
|
+
E --> G
|
|
196
|
+
F --> H[sassy-cycle]
|
|
197
|
+
G --> H
|
|
198
|
+
H --> I{Code approval}
|
|
199
|
+
I -->|changes| H
|
|
200
|
+
I --> Q{Manual QA}
|
|
201
|
+
Q -->|fails| H
|
|
202
|
+
Q -->|passes, via sassy-work| J[Commit on current branch and stop]
|
|
203
|
+
Q -->|passes, via sassy-lfg| K[sassy-ship]
|
|
204
|
+
K --> L{Ship review}
|
|
205
|
+
L -->|approve or mode:auto| M[Commit, PR, babysit]
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Option 1 produces a durable plan with phased slices and manual QA tables. Options 2 and 3 keep the whole job as one settled in-context brief and write no plan file.
|
|
209
|
+
|
|
210
|
+
`sassy-work` handles one phase and hands off the next. `sassy-lfg` drains the whole plan in one session. Both delegate to `sassy-cycle` and stop at one gate carrying code approval plus the manual QA checklist; nothing is `Done`, committed, or shipped until both pass. With an in-context brief there are no QA rows, so the gate is code approval alone.
|
|
211
|
+
|
|
212
|
+
Work always commits to the current branch; `sassy-ship` creates the shipping branch at close-out when the checked-out branch is the repo default.
|
|
213
|
+
|
|
214
|
+
`sassy-ship` is the close-out. It summarizes what is about to ship and waits, unless the caller passed `mode:auto`.
|
|
215
|
+
|
|
216
|
+
### From bug to ship
|
|
217
|
+
|
|
218
|
+
```mermaid
|
|
219
|
+
flowchart LR
|
|
220
|
+
A[Bug or issue] --> B[sassy-debug]
|
|
221
|
+
B --> C[Triage]
|
|
222
|
+
C --> D[scout helper]
|
|
223
|
+
D --> E{Fix gate}
|
|
224
|
+
E -->|1. fix it now| F[sassy-work]
|
|
225
|
+
E -->|2. write a plan| G[sassy-plan]
|
|
226
|
+
E -->|3. hand off| H[sassy-handoff]
|
|
227
|
+
E -->|4. stop| I[Diagnosis only]
|
|
228
|
+
F --> J{Code approval}
|
|
229
|
+
J -->|changes| F
|
|
230
|
+
J -->|approved| K[sassy-ship]
|
|
231
|
+
K --> L{Ship review}
|
|
232
|
+
L -->|approve| M[Commit, PR, babysit]
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
`sassy-debug` diagnoses inline, using the read-only `scout` as a helper to ground the failure surface; the causal reasoning stays in the main context. It then stops at the fix gate with the causal chain, proposed fix, and risks.
|
|
236
|
+
|
|
237
|
+
The fix gate offers four options:
|
|
238
|
+
|
|
239
|
+
- **Fix it now** — hand the fix brief to `sassy-work`: one cycle, its own code approval gate, a commit, then a stop before ship
|
|
240
|
+
- **Write a plan** — carry the diagnosis into `sassy-plan`, which ends in its own 3-option picker
|
|
241
|
+
- **Hand off** — continue in a fresh session via `sassy-handoff`
|
|
242
|
+
- **Stop here** — keep the diagnosis and change nothing
|
|
243
|
+
|
|
244
|
+
`sassy-debug` takes no mode. Shipping keeps its own `mode:manual` gate unless you invoke `sassy-ship` yourself with `mode:auto`.
|
|
245
|
+
|
|
246
|
+
## Gates and controls
|
|
247
|
+
|
|
248
|
+
| Control | When it applies |
|
|
249
|
+
|---|---|
|
|
250
|
+
| Code approval | Always on for `sassy-work` and `sassy-lfg`. |
|
|
251
|
+
| Manual QA | Part of the same gate, and only when the plan document has QA tables. The user runs the rows; a phase is `Done` only when both pass. |
|
|
252
|
+
| Ship review | On in `mode:manual` (default), skipped by `mode:auto`. |
|
|
253
|
+
|
|
254
|
+
The artifact decides QA: a plan document carries manual QA tables that gate phase completion, an in-context brief does not. Gates are deliberate stops; the only way past one is the user's approval. `mode:auto` is accepted only by `sassy-ship` and `sassy-compound`.
|
|
255
|
+
|
|
256
|
+
## Repository layout
|
|
257
|
+
|
|
258
|
+
```text
|
|
259
|
+
sassy-skills/
|
|
260
|
+
├── skills/
|
|
261
|
+
│ ├── sassy-plan/
|
|
262
|
+
│ ├── sassy-work/
|
|
263
|
+
│ ├── sassy-lfg/
|
|
264
|
+
│ └── ...
|
|
265
|
+
├── scripts/
|
|
266
|
+
│ └── sync.sh
|
|
267
|
+
├── AGENTS.md
|
|
268
|
+
├── CONCEPTS.md
|
|
269
|
+
└── README.md
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## Installation
|
|
273
|
+
|
|
274
|
+
The suite ships as the npm package **`@sassywares/skills`**. Pin it, don't symlink it into a global directory — the version in your lockfile is the pin, and upgrading is one command.
|
|
275
|
+
|
|
276
|
+
### As a project dependency (recommended for teams)
|
|
277
|
+
|
|
278
|
+
```bash
|
|
279
|
+
bun add -d @sassywares/skills
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
Nothing scans `node_modules` on its own, so give your harness the directory it does read:
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
mkdir -p .agents/skills
|
|
286
|
+
for d in node_modules/@sassywares/skills/skills/*/; do
|
|
287
|
+
ln -sfn "../../$d" ".agents/skills/$(basename "$d")"
|
|
288
|
+
done
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
`.agents/skills/` is read by Cursor and pi. Claude Code reads `.claude/skills/` only, so add one directory symlink:
|
|
292
|
+
|
|
293
|
+
```bash
|
|
294
|
+
mkdir -p .claude && ln -sfn ../.agents/skills .claude/skills
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Ignore both: they're derived from the lockfile, like any build output. A `postinstall` that runs the loop gives teammates zero setup beyond `bun install`; a preflight in the skills tells anyone who skipped it what to run.
|
|
298
|
+
|
|
299
|
+
### As a pi package
|
|
300
|
+
|
|
301
|
+
pi resolves packages directly, so no links are needed:
|
|
302
|
+
|
|
303
|
+
```bash
|
|
304
|
+
pi install npm:@sassywares/skills@0.1.0 # user-level, all projects
|
|
305
|
+
pi install npm:@sassywares/skills@0.1.0 --local # project-level → commit .pi/settings.json
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### While editing the suite
|
|
309
|
+
|
|
310
|
+
Point a consumer at your checkout instead of a published version:
|
|
311
|
+
|
|
312
|
+
```bash
|
|
313
|
+
bun link @sassywares/skills
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
Inside this repo, `.pi/settings.json` already declares `./skills`, so pi loads the working copy directly.
|
|
317
|
+
|
|
318
|
+
Get the skills however they arrive — package, link, or vendored copy — and invoke them with your harness's skill syntax.
|
|
319
|
+
|
|
320
|
+
## Repository maintenance
|
|
321
|
+
|
|
322
|
+
`scripts/sync.sh` commits skill changes, rebases on `origin/main`, and pushes. Any agent that changes a skill must run it immediately, without asking for confirmation. It installs nothing anywhere — a change reaches consumers when a version is released and they bump their pin. `AGENTS.md` documents the workflow for agents working on this repo.
|
|
323
|
+
|
|
324
|
+
### Releases
|
|
325
|
+
|
|
326
|
+
Publishing is tag-driven. The `Publish` workflow (`.github/workflows/publish.yml`) validates every `skills/*/SKILL.md`, checks that the tag matches `package.json`, and publishes `@sassywares/skills` to npm on a `v*` tag push.
|
|
327
|
+
|
|
328
|
+
```bash
|
|
329
|
+
npm version patch --no-git-tag-version
|
|
330
|
+
git add -A && git commit -m "chore: release v$(node -p "require('./package.json').version")"
|
|
331
|
+
git push origin main
|
|
332
|
+
git tag "v$(node -p "require('./package.json').version")"
|
|
333
|
+
git push origin --tags
|
|
334
|
+
```
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// sassy-skills — wire this package's skills into a project.
|
|
3
|
+
//
|
|
4
|
+
// Nothing scans node_modules on its own, so the harnesses that read a project
|
|
5
|
+
// skills directory need links. This tool creates them, checks them, and updates
|
|
6
|
+
// the pinned version.
|
|
7
|
+
//
|
|
8
|
+
// sassy-skills sync link skills into .agents/skills (and .claude/skills)
|
|
9
|
+
// sassy-skills check exit 1 if the wiring is missing or stale
|
|
10
|
+
// sassy-skills update bump to the latest published version, then sync
|
|
11
|
+
//
|
|
12
|
+
// Options: --root <dir> --quiet --help
|
|
13
|
+
import { existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, readlinkSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
14
|
+
import { tmpdir } from "node:os";
|
|
15
|
+
import { execFileSync } from "node:child_process";
|
|
16
|
+
import { dirname, join, resolve } from "node:path";
|
|
17
|
+
import { fileURLToPath } from "node:url";
|
|
18
|
+
|
|
19
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
20
|
+
const SKILLS_DIR = resolve(HERE, "..", "skills");
|
|
21
|
+
const PKG = JSON.parse(readFileSync(resolve(HERE, "..", "package.json"), "utf8"));
|
|
22
|
+
const NAME = PKG.name;
|
|
23
|
+
const VERSION = PKG.version;
|
|
24
|
+
const REGISTRY = process.env.SASSY_SKILLS_REGISTRY ?? "https://registry.npmjs.org";
|
|
25
|
+
|
|
26
|
+
const args = process.argv.slice(2);
|
|
27
|
+
const quiet = args.includes("--quiet");
|
|
28
|
+
const rootFlag = args.indexOf("--root");
|
|
29
|
+
const rootValueIndex = rootFlag >= 0 ? rootFlag + 1 : -1;
|
|
30
|
+
const ROOT = rootFlag >= 0 ? resolve(args[rootFlag + 1] ?? ".") : process.cwd();
|
|
31
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
32
|
+
console.log(`sassy-skills@${VERSION}\n\n sync link skills into .agents/skills and .claude/skills\n check exit 1 when the wiring is missing or stale\n update bump ${NAME} to the latest version, then sync\n outdated print a one-line nudge when a newer version is published (throttled, offline-safe)\n\nOptions: --root <dir> --quiet --force`);
|
|
33
|
+
process.exit(0);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const command = args.find((a, i) => !a.startsWith("-") && i !== rootValueIndex) ?? "sync";
|
|
37
|
+
|
|
38
|
+
const log = (...a) => { if (!quiet) console.log(...a); };
|
|
39
|
+
const ok = (m) => log(` ✓ ${m}`);
|
|
40
|
+
const warn = (m) => console.error(` ! ${m}`);
|
|
41
|
+
const die = (m) => { console.error(` ✗ ${m}`); process.exit(1); };
|
|
42
|
+
|
|
43
|
+
const IS_WIN = process.platform === "win32";
|
|
44
|
+
|
|
45
|
+
function linkedSkills() {
|
|
46
|
+
if (!existsSync(SKILLS_DIR)) die(`package has no skills/ directory at ${SKILLS_DIR}`);
|
|
47
|
+
return readdirSync(SKILLS_DIR, { withFileTypes: true })
|
|
48
|
+
.filter((e) => e.isDirectory() && existsSync(join(SKILLS_DIR, e.name, "SKILL.md")))
|
|
49
|
+
.map((e) => e.name)
|
|
50
|
+
.sort();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function makeLink(target, linkPath) {
|
|
54
|
+
try {
|
|
55
|
+
if (lstatSync(linkPath).isSymbolicLink()) rmSync(linkPath);
|
|
56
|
+
else { warn(`${linkPath} exists and is not a symlink — leaving it alone`); return false; }
|
|
57
|
+
} catch { /* does not exist */ }
|
|
58
|
+
symlinkSync(target, linkPath, IS_WIN ? "junction" : "dir");
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function sync() {
|
|
63
|
+
const skills = linkedSkills();
|
|
64
|
+
const agents = join(ROOT, ".agents", "skills");
|
|
65
|
+
mkdirSync(agents, { recursive: true });
|
|
66
|
+
|
|
67
|
+
let made = 0;
|
|
68
|
+
for (const name of skills) {
|
|
69
|
+
const target = resolve(SKILLS_DIR, name);
|
|
70
|
+
if (makeLink(target, join(agents, name))) made++;
|
|
71
|
+
}
|
|
72
|
+
ok(`${skills.length} skills linked into .agents/skills (${NAME}@${VERSION})`);
|
|
73
|
+
|
|
74
|
+
// Claude Code reads .claude/skills only. A directory symlink is enough, and it
|
|
75
|
+
// is the least invasive form when the directory does not already exist.
|
|
76
|
+
const claude = join(ROOT, ".claude", "skills");
|
|
77
|
+
try {
|
|
78
|
+
const st = lstatSync(claude);
|
|
79
|
+
if (!st.isSymbolicLink()) {
|
|
80
|
+
warn(".claude/skills exists and is not a symlink — Claude Code will not see the project skills unless you link them yourself");
|
|
81
|
+
}
|
|
82
|
+
} catch {
|
|
83
|
+
mkdirSync(dirname(claude), { recursive: true });
|
|
84
|
+
symlinkSync(IS_WIN ? join(ROOT, ".agents", "skills") : "../.agents/skills", claude, IS_WIN ? "junction" : "dir");
|
|
85
|
+
ok("linked .claude/skills → .agents/skills");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (made) log(`\nRestart or /reload your harness to pick up the new skills.`);
|
|
89
|
+
return 0;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function check() {
|
|
93
|
+
const skills = linkedSkills();
|
|
94
|
+
const agents = join(ROOT, ".agents", "skills");
|
|
95
|
+
let problems = 0;
|
|
96
|
+
|
|
97
|
+
for (const name of skills) {
|
|
98
|
+
const link = join(agents, name);
|
|
99
|
+
const target = resolve(SKILLS_DIR, name);
|
|
100
|
+
try {
|
|
101
|
+
const st = lstatSync(link);
|
|
102
|
+
if (!st.isSymbolicLink()) { warn(`${name}: real directory, not a link`); problems++; continue; }
|
|
103
|
+
const points = resolve(agents, readlinkSync(link));
|
|
104
|
+
if (points !== target) { warn(`${name}: points at ${points}, expected ${target}`); problems++; }
|
|
105
|
+
} catch {
|
|
106
|
+
warn(`${name}: not linked`);
|
|
107
|
+
problems++;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (!existsSync(join(ROOT, ".claude", "skills"))) warn(".claude/skills missing (Claude Code only)");
|
|
111
|
+
if (problems) { console.error(` ✗ ${problems} skill link(s) need attention — run: sassy-skills sync`); return 1; }
|
|
112
|
+
ok(`${skills.length} skills correctly linked (${NAME}@${VERSION})`);
|
|
113
|
+
return 0;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function detectPM() {
|
|
117
|
+
if (existsSync(join(ROOT, "bun.lock")) || existsSync(join(ROOT, "bun.lockb"))) return "bun";
|
|
118
|
+
if (existsSync(join(ROOT, "pnpm-lock.yaml"))) return "pnpm";
|
|
119
|
+
if (existsSync(join(ROOT, "yarn.lock"))) return "yarn";
|
|
120
|
+
if (existsSync(join(ROOT, "package-lock.json"))) return "npm";
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function update() {
|
|
125
|
+
const pm = detectPM();
|
|
126
|
+
if (!pm) die("no lockfile found — cannot tell which package manager to update with");
|
|
127
|
+
const spec = `${NAME}@latest`;
|
|
128
|
+
const cmd = {
|
|
129
|
+
bun: ["bun", ["add", "--dev", "--exact", spec]],
|
|
130
|
+
npm: ["npm", ["install", "--save-dev", "--save-exact", spec]],
|
|
131
|
+
pnpm: ["pnpm", ["add", "--save-dev", "--save-exact", spec]],
|
|
132
|
+
yarn: ["yarn", ["add", "--dev", "--exact", spec]],
|
|
133
|
+
}[pm];
|
|
134
|
+
log(`Updating ${NAME} with ${pm}…`);
|
|
135
|
+
execFileSync(cmd[0], cmd[1], { cwd: ROOT, stdio: "inherit" });
|
|
136
|
+
return sync();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function newerThan(a, b) {
|
|
140
|
+
const pa = String(a).split(/[.+-]/).map((x) => parseInt(x, 10) || 0);
|
|
141
|
+
const pb = String(b).split(/[.+-]/).map((x) => parseInt(x, 10) || 0);
|
|
142
|
+
for (let i = 0; i < 3; i++) {
|
|
143
|
+
if ((pa[i] ?? 0) !== (pb[i] ?? 0)) return (pa[i] ?? 0) > (pb[i] ?? 0);
|
|
144
|
+
}
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function updateHint() {
|
|
149
|
+
const pm = detectPM();
|
|
150
|
+
const script = pm === "bun" ? "bun run skills:update"
|
|
151
|
+
: pm ? `${pm} run skills:update`
|
|
152
|
+
: "sassy-skills update";
|
|
153
|
+
return script;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Non-blocking version nudge. Throttled to once a day per machine, silent when
|
|
157
|
+
// offline, and it never fails a run — a stale pin is worth mentioning, not
|
|
158
|
+
// worth blocking work over.
|
|
159
|
+
async function outdated(force = false) {
|
|
160
|
+
const cacheFile = join(tmpdir(), "sassy-skills-version-check.json");
|
|
161
|
+
const TTL = 24 * 60 * 60 * 1000;
|
|
162
|
+
let cached = null;
|
|
163
|
+
try { cached = JSON.parse(readFileSync(cacheFile, "utf8")); } catch { /* first run */ }
|
|
164
|
+
|
|
165
|
+
let latest = null;
|
|
166
|
+
const fresh = cached && typeof cached.checkedAt === "number" && Date.now() - cached.checkedAt < TTL;
|
|
167
|
+
if (fresh && !force) {
|
|
168
|
+
latest = cached.latest ?? null;
|
|
169
|
+
} else {
|
|
170
|
+
try {
|
|
171
|
+
const res = await fetch(`${REGISTRY}/${encodeURIComponent(NAME)}/latest`, {
|
|
172
|
+
signal: AbortSignal.timeout(2000),
|
|
173
|
+
headers: { accept: "application/json" },
|
|
174
|
+
});
|
|
175
|
+
const body = await res.json();
|
|
176
|
+
latest = typeof body?.version === "string" ? body.version : null;
|
|
177
|
+
} catch { latest = null; }
|
|
178
|
+
try { writeFileSync(cacheFile, JSON.stringify({ checkedAt: Date.now(), latest })); } catch { /* read-only tmp */ }
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (!latest || !newerThan(latest, VERSION)) return 0;
|
|
182
|
+
console.log(` ↑ ${NAME} ${VERSION} → ${latest} available — ${updateHint()}`);
|
|
183
|
+
return 0;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
switch (command) {
|
|
187
|
+
case "outdated": process.exit(await outdated(args.includes("--force")));
|
|
188
|
+
case "sync": process.exit(sync());
|
|
189
|
+
case "check": process.exit(check());
|
|
190
|
+
case "update": process.exit(update());
|
|
191
|
+
default:
|
|
192
|
+
die(`unknown command: ${command} (try --help)`);
|
|
193
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,43 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sassywares/skills",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Lean, explicit-invocation Agent Skills for shipping fast: plan, work, review, validate, ship.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Sassywares",
|
|
8
|
+
"homepage": "https://github.com/sassywares/sassy-skills#readme",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/sassywares/sassy-skills.git"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/sassywares/sassy-skills/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"pi-package",
|
|
18
|
+
"pi",
|
|
19
|
+
"agent-skills",
|
|
20
|
+
"skills",
|
|
21
|
+
"workflow",
|
|
22
|
+
"planning",
|
|
23
|
+
"code-review"
|
|
24
|
+
],
|
|
25
|
+
"files": [
|
|
26
|
+
"skills",
|
|
27
|
+
"bin",
|
|
28
|
+
"README.md",
|
|
29
|
+
"CONCEPTS.md",
|
|
30
|
+
"LICENSE"
|
|
31
|
+
],
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"pi": {
|
|
36
|
+
"skills": [
|
|
37
|
+
"./skills"
|
|
38
|
+
]
|
|
39
|
+
},
|
|
40
|
+
"bin": {
|
|
41
|
+
"sassy-skills": "./bin/sassy-skills.mjs"
|
|
42
|
+
}
|
|
43
|
+
}
|