@sassywares/skills 0.1.0 → 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 CHANGED
@@ -2,11 +2,11 @@
2
2
 
3
3
  ## Source of truth
4
4
 
5
- The repo's `skills/` directory is the only copy of the skills. `~/.agents/skills` is a symlink to it. Changes are picked up by pi immediately; the sync service handles git commit and push.
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
6
 
7
7
  ## Skill
8
8
 
9
- A directory containing `SKILL.md`, discovered by pi from `~/.agents/skills`. Skills are explicit workflows, not always-on instructions. Most have `disable-model-invocation: true` and are invoked with `/skill:<name>`.
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
10
 
11
11
  ## Agent
12
12
 
@@ -56,4 +56,4 @@ A settled in-context brief produced by `sassy-plan` when the user picks "start w
56
56
 
57
57
  ## Sync
58
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 repairs the directory symlink, commits skill changes, rebases on `origin/main`, and pushes. New and removed skills need no per-skill symlink work.
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/README.md CHANGED
@@ -271,52 +271,55 @@ sassy-skills/
271
271
 
272
272
  ## Installation
273
273
 
274
- Sassy Skills are plain Agent Skills. Install them as a pi package from npm, or clone the repo and symlink the whole suite or copy only the skills you want.
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
275
 
276
- ### Option 0 — install as a pi package
276
+ ### As a project dependency (recommended for teams)
277
277
 
278
278
  ```bash
279
- pi install npm:@sassywares/skills
279
+ bun add -d @sassywares/skills
280
280
  ```
281
281
 
282
- This pins the published version and reconciles on `pi update --extensions`. Use `pi install git:github.com/sassywares/sassy-skills` to track the repo instead of a release.
283
-
284
- ### Clone
282
+ Nothing scans `node_modules` on its own, so give your harness the directory it does read:
285
283
 
286
284
  ```bash
287
- git clone https://github.com/sassywares/sassy-skills.git ~/sassy-skills
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
288
289
  ```
289
290
 
290
- ### Option A — symlink the whole suite
291
+ `.agents/skills/` is read by Cursor and pi. Claude Code reads `.claude/skills/` only, so add one directory symlink:
291
292
 
292
293
  ```bash
293
- ln -s ~/sassy-skills/skills ~/.agents/skills
294
+ mkdir -p .claude && ln -sfn ../.agents/skills .claude/skills
294
295
  ```
295
296
 
296
- Replace `~/.agents/skills` with your harness's skills directory if it uses a different path.
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
297
300
 
298
- ### Option B — symlink individual skills
301
+ pi resolves packages directly, so no links are needed:
299
302
 
300
303
  ```bash
301
- mkdir -p /path/to/your/skills
302
- ln -s ~/sassy-skills/skills/sassy-plan /path/to/your/skills/sassy-plan
303
- ln -s ~/sassy-skills/skills/sassy-work /path/to/your/skills/sassy-work
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
304
306
  ```
305
307
 
306
- ### Option C — copy individual skills
308
+ ### While editing the suite
309
+
310
+ Point a consumer at your checkout instead of a published version:
307
311
 
308
312
  ```bash
309
- mkdir -p /path/to/your/skills
310
- cp -R ~/sassy-skills/skills/sassy-plan /path/to/your/skills/
313
+ bun link @sassywares/skills
311
314
  ```
312
315
 
313
- Use this when you only want a few workflows and do not want to track the repo.
316
+ Inside this repo, `.pi/settings.json` already declares `./skills`, so pi loads the working copy directly.
314
317
 
315
- Invoke the skills using your harness's skill syntax.
318
+ Get the skills however they arrive — package, link, or vendored copy — and invoke them with your harness's skill syntax.
316
319
 
317
320
  ## Repository maintenance
318
321
 
319
- `scripts/sync.sh` repairs the symlink, commits skill changes, rebases on `origin/main`, and pushes. Any agent that changes a skill must run it immediately, without asking for confirmation. `AGENTS.md` documents the workflow for agents working on this repo.
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.
320
323
 
321
324
  ### Releases
322
325
 
@@ -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,6 @@
1
1
  {
2
2
  "name": "@sassywares/skills",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Lean, explicit-invocation Agent Skills for shipping fast: plan, work, review, validate, ship.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -24,6 +24,7 @@
24
24
  ],
25
25
  "files": [
26
26
  "skills",
27
+ "bin",
27
28
  "README.md",
28
29
  "CONCEPTS.md",
29
30
  "LICENSE"
@@ -35,5 +36,8 @@
35
36
  "skills": [
36
37
  "./skills"
37
38
  ]
39
+ },
40
+ "bin": {
41
+ "sassy-skills": "./bin/sassy-skills.mjs"
38
42
  }
39
43
  }
@@ -21,7 +21,7 @@ Keep an open PR moving toward merge by reacting to three streams as each arrives
21
21
 
22
22
  An explicit token always beats the config.
23
23
 
24
- **Config.** Resolve `.sassywares/config.yaml` (nearest, walking up) per `~/.agents/skills/sassy-setup/references/config.md`. Precedence: explicit token > session instruction > config > skill default. `pr.*` seeds `merge` and `delete_branch`; `hooks.pr.before_merge` and `hooks.pr.after_merge` inject project steps around a merge.
24
+ **Config.** Resolve `.sassywares/config.yaml` (nearest, walking up) per the `sassy-setup` skill's `references/config.md`. Precedence: explicit token > session instruction > config > skill default. `pr.*` seeds `merge` and `delete_branch`; `hooks.pr.before_merge` and `hooks.pr.after_merge` inject project steps around a merge.
25
25
 
26
26
  **One PR per run.** This skill watches the PR resolved from the argument or the current branch. A workspace fan-out is N runs, one per PR, and `pr.merge` never merges a fan-out — that needs an explicit say-so per PR.
27
27
 
@@ -28,7 +28,7 @@ Treat the reads as a snapshot; re-read branch and staged set right before commit
28
28
 
29
29
  **Never bypass hooks.** No `--no-verify`, and never pass it through to a project script this run invokes. A repo's git hooks are the layer that cannot be skipped, so disabling them silently removes an enforcement the project relies on.
30
30
 
31
- **Config.** Resolve `.sassywares/config.yaml` (nearest, walking up) per `~/.agents/skills/sassy-setup/references/config.md`. Precedence: explicit token > session instruction > config > skill default. `commit.branch` supplies the default.
31
+ **Config.** Resolve `.sassywares/config.yaml` (nearest, walking up) per the `sassy-setup` skill's `references/config.md`. Precedence: explicit token > session instruction > config > skill default. `commit.branch` supplies the default.
32
32
 
33
33
  **Branch option.** `branch:current|new [<name>]`. `current` commits on the checked-out branch. `new` cuts a branch from `origin/<base>` — `<base>` is `repos[<dir>]` in a workspace config, else `git.default_branch`, else the probed base — named `<name>` when given, else from the change. `sassy-commit-push-pr` forwards its own `branch` choice here.
34
34
 
@@ -16,7 +16,7 @@ disable-model-invocation: true
16
16
 
17
17
  **Options.** `branch:new|current` selects the branch policy below. `babysit:on|off` (default `on`) controls the closing babysit. `checkpoint` bounds that babysit to a single tick. `merge:on|off` and `delete-branch:on|off` (both default `off`) are forwarded unchanged to `sassy-babysit`.
18
18
 
19
- **Config.** Resolve `.sassywares/config.yaml` (nearest, walking up) per `~/.agents/skills/sassy-setup/references/config.md`. Precedence: explicit token > session instruction > config > skill default. `commit.branch` seeds the branch policy, `pr.*` seeds the babysit options, and `hooks.pr.before_create` / `hooks.pr.after_create` inject project steps around the PR.
19
+ **Config.** Resolve `.sassywares/config.yaml` (nearest, walking up) per the `sassy-setup` skill's `references/config.md`. Precedence: explicit token > session instruction > config > skill default. `commit.branch` seeds the branch policy, `pr.*` seeds the babysit options, and `hooks.pr.before_create` / `hooks.pr.after_create` inject project steps around the PR.
20
20
 
21
21
  ## Context
22
22
 
@@ -15,7 +15,7 @@ disable-model-invocation: true
15
15
 
16
16
  **Modes.** Default `mode:manual` asks before the `AGENTS.md` pointer edit; `mode:auto` applies that edit silently. Ship passes `mode:auto` after its own gate. Never toggle it yourself.
17
17
 
18
- **Config.** Resolve `.sassywares/config.yaml` (nearest, walking up) per `~/.agents/skills/sassy-setup/references/config.md`. Precedence: explicit token > session instruction > config > skill default. Paths come from `artifacts.solutions` and `artifacts.concepts`; never hardcode them.
18
+ **Config.** Resolve `.sassywares/config.yaml` (nearest, walking up) per the `sassy-setup` skill's `references/config.md`. Precedence: explicit token > session instruction > config > skill default. Paths come from `artifacts.solutions` and `artifacts.concepts`; never hardcode them.
19
19
 
20
20
  ## When it qualifies — the durable bar
21
21
 
@@ -23,7 +23,9 @@ Establish the exact input, the observed symptom, the expected behavior, and how
23
23
 
24
24
  Trivial bugs whose cause is readable from the input may skip the scout, but still state the causal chain and pass the fix gate before changing code, then run the fix through `sassy-cycle`.
25
25
 
26
- **Search prior learnings first.** Resolve `.sassywares/config.yaml` (nearest, walking up) per `~/.agents/skills/sassy-setup/references/config.md`. For every existing path in `knowledge.read`, search the docs for the symptom's keywords, the module, and the failure type. A matching doc **seeds or confirms** a hypothesis — it never replaces the causal chain or the reproduction. Name the docs you consulted, and say plainly when none matched: no match is not evidence that nothing similar has happened.
26
+ **Search prior learnings first.** Resolve `.sassywares/config.yaml` (nearest, walking up) per the `sassy-setup` skill's `references/config.md`. For every existing path in `knowledge.read`, search the docs for the symptom's keywords, the module, and the failure type. A matching doc **seeds or confirms** a hypothesis — it never replaces the causal chain or the reproduction. Name the docs you consulted, and say plainly when none matched: no match is not evidence that nothing similar has happened.
27
+
28
+ **Update nudge.** Once per run, if `node_modules/.bin/sassy-skills` exists, run `sassy-skills outdated`. It is throttled to once a day per machine and silently does nothing offline. Pass any newer version on in one line; never block, retry, or fail on it.
27
29
 
28
30
  ## Phase 1 — Diagnose inline, with `scout` as a helper
29
31
 
@@ -15,7 +15,7 @@ disable-model-invocation: true
15
15
 
16
16
  **Not this skill:** durable gotcha learnings (`sassy-compound`), implementation plans, or shipping code.
17
17
 
18
- **Config.** Resolve `.sassywares/config.yaml` (nearest, walking up) per `~/.agents/skills/sassy-setup/references/config.md`. Handbooks land in `artifacts.handbooks`.
18
+ **Config.** Resolve `.sassywares/config.yaml` (nearest, walking up) per the `sassy-setup` skill's `references/config.md`. Handbooks land in `artifacts.handbooks`.
19
19
 
20
20
  ## When to run
21
21
 
@@ -14,7 +14,9 @@ disable-model-invocation: true
14
14
 
15
15
  **The orchestrator does not do work.** Read the plan, dispatch `sassy-cycle` per phase, then `sassy-ship`. Never edit application code. Keep the main context small — all heavy work happens in subagents.
16
16
 
17
- **Config.** Resolve `.sassywares/config.yaml` (nearest, walking up) per `~/.agents/skills/sassy-setup/references/config.md`. Precedence: explicit token > session instruction > config > skill default. Plans live under `artifacts.plans`; `git.repo` decides how the close-out persists.
17
+ **Config.** Resolve `.sassywares/config.yaml` (nearest, walking up) per the `sassy-setup` skill's `references/config.md`. Precedence: explicit token > session instruction > config > skill default. Plans live under `artifacts.plans`; `git.repo` decides how the close-out persists.
18
+
19
+ **Update nudge.** Once per run, if `node_modules/.bin/sassy-skills` exists, run `sassy-skills outdated`. It is throttled to once a day per machine and silently does nothing offline. Pass any newer version on in one line; never block, retry, or fail on it.
18
20
 
19
21
  ## Input
20
22
 
@@ -15,7 +15,9 @@ disable-model-invocation: true
15
15
 
16
16
  **Does not implement.** Planning never implements. Execution belongs to `sassy-work` or `sassy-lfg`, and only after the user picks.
17
17
 
18
- **Config.** Resolve `.sassywares/config.yaml` (nearest, walking up) per `~/.agents/skills/sassy-setup/references/config.md`. Precedence: explicit token > session instruction > config > skill default. Plans land in `artifacts.plans`.
18
+ **Config.** Resolve `.sassywares/config.yaml` (nearest, walking up) per the `sassy-setup` skill's `references/config.md`. Precedence: explicit token > session instruction > config > skill default. Plans land in `artifacts.plans`.
19
+
20
+ **Update nudge.** Once per run, if `node_modules/.bin/sassy-skills` exists, run `sassy-skills outdated`. It is throttled to once a day per machine and silently does nothing offline. Pass any newer version on in one line; never block, retry, or fail on it.
19
21
 
20
22
  ## Phase A — Discuss (brainstorm)
21
23
 
@@ -0,0 +1 @@
1
+ keep
@@ -13,6 +13,8 @@ disable-model-invocation: true
13
13
 
14
14
  **Outcome:** the scope reviewed with concrete findings; the approved fixes applied; everything left uncommitted.
15
15
 
16
+ **Update nudge.** Once per run, if `node_modules/.bin/sassy-skills` exists, run `sassy-skills outdated`. It is throttled to once a day per machine and silently does nothing offline. Pass any newer version on in one line; never block, retry, or fail on it.
17
+
16
18
  **Orchestrator only.** Never edit code yourself. The `reviewer` finds; the `worker` fixes.
17
19
 
18
20
  ## Review dimensions
@@ -12,7 +12,7 @@ disable-model-invocation: true
12
12
 
13
13
  # Sassy Setup
14
14
 
15
- **Outcome:** `.sassywares/config.yaml` at the project root, matching the tree it describes. In `workspace` mode, also `.sassywares/workspace/snapshot` and `.sassywares/workspace/.gitignore`.
15
+ **Outcome:** `.sassywares/config.yaml` at the project root matching the tree it describes, the skills wired as a pinned dependency and visible to the harness, and — in `workspace` mode — the snapshot tooling installed.
16
16
 
17
17
  **Read `references/config.md` first.** It is the schema and the resolution rules; this skill only decides and writes.
18
18
 
@@ -20,7 +20,7 @@ disable-model-invocation: true
20
20
 
21
21
  ## Steps
22
22
 
23
- 1. **Resolve the start.** The argument's path, else the working directory. If a `.sassywares/config.yaml` is already found walking up, this is a **verify run**: skip to step 5 with `--check`.
23
+ 1. **Resolve the start.** The argument's path, else the working directory. If a `.sassywares/config.yaml` is already found walking up, this is a **verify run**: run the checks in step 7 and report, without rewriting anything.
24
24
  2. **Detect.** Run `scripts/detect.sh --root <path>`. It prints a proposed config: topology (`repo`, `workspace`, `none`), the remote name, a probed base branch, nested repos with their bases, and which artifact paths exist.
25
25
  3. **Ask only what detection cannot answer.** Never re-ask what the tree already said.
26
26
  - **`workspace`** — the snapshot mirror: create a **private** GitHub repo, or connect an existing one (`owner/name` or URL)? For creating it, get explicit approval and name the slug before running anything. Automatic creation is `gh repo create` inside `snapshot`, and only after that approval.
@@ -31,7 +31,18 @@ disable-model-invocation: true
31
31
  - **`pr.*`** — default `babysit: true`, `merge: false`, `delete_branch: false`. Say plainly that `merge: true` authorizes merging every babysat PR in this project without asking.
32
32
  - **Hooks** — offer, do not impose. Ask whether the project wants steps injected at `before_create`, `after_create`, `before_merge`, or `after_merge`, and write the skills they name. Leave them `[]` when they have nothing to add. **Never write a hook skill yourself** — hooks are project skills, and copying one of ours into that role is the fork this mechanism exists to prevent.
33
33
  4. **Write the config.** Build the file from the proposal plus the answers. It is written **fully commented**: every key keeps a description and its allowed values, exactly as the detector emits them. Never strip those comments to save lines — for a teammate opening the file, the comments are the documentation. If a config already exists, show what changes and ask before writing — never silently overwrite. Keep every key; an unset key falls back to its documented default, so a short file is legal when nothing deviates.
34
- 5. **Install, `workspace` only.** The layout is fixed, and everything the snapshot owns lives under one directory:
34
+ 5. **Wire the skills.** The suite is the package `@sassywares/skills`, and its own CLI creates the links. Never copy skill files into a project.
35
+
36
+ - **It needs a `package.json`.** With none, say so and ask whether to add a minimal manifest or skip the wiring. Do not create one unasked, and do not invent a second distribution mechanism for non-JS projects.
37
+ - **Detect the package manager** from the lockfile: `bun.lock`/`bun.lockb`, `pnpm-lock.yaml`, `yarn.lock`, `package-lock.json`. Ask when several disagree or none exists.
38
+ - **Add the dependency pinned exactly** — `bun add --dev --exact @sassywares/skills@<version>`, `npm install --save-dev --save-exact …`, `pnpm add --save-dev --save-exact …`, `yarn add --dev --exact …`. Exact, because one pinned version for every teammate and every harness is the entire point.
39
+ - **Scripts**, adding only what is missing: `"skills:sync": "sassy-skills sync"`, `"skills:update": "sassy-skills update"`, and `"postinstall": "sassy-skills sync"`. **Never overwrite an existing `postinstall`** — if one is there, leave it alone and tell the user to chain `&& sassy-skills sync`, so setup cannot erase someone's install steps.
40
+ - **Run `sassy-skills sync`.** The package's CLI is now in `node_modules/.bin`; it links `.agents/skills/sassy-*` and creates `.claude/skills → ../.agents/skills`. The linking rules travel with the lockfile, so they upgrade with the suite instead of drifting in the repo.
41
+ - **Ignore the derived links.** Add `/.agents/skills/sassy-*` and `/.claude/skills` to `.gitignore` when missing. Create `.gitignore` only when there is none.
42
+ - **Write `.sassywares/README.md`** from `assets/sassywares-readme.md` when absent. Never overwrite it — it may carry project notes.
43
+ - **Tell the user to `/reload`.** The links are invisible to the harness until it rescans.
44
+
45
+ 6. **Install the snapshot, `workspace` only.** The layout is fixed, and everything the snapshot owns lives under one directory:
35
46
 
36
47
  ```text
37
48
  .sassywares/
@@ -46,8 +57,8 @@ disable-model-invocation: true
46
57
  - Write `.sassywares/workspace/.gitignore` for excluded paths (vendored trees, generated dirs, `.run/`). One pattern per line, `.gitignore` style. Skip it when there is nothing to exclude; the snapshot syncs without it. The mirror itself needs no line — the script excludes it.
47
58
  - Do **not** add a `.gitignore` at the project root, and do not modify one that exists.
48
59
  - Do **not** run the snapshot during setup. It clones and pushes; that is the user's call. Offer `--dry-run` and run it only if they ask.
49
- 6. **Verify.** Run `scripts/detect.sh --root <path> --check`. Report every `drift:` line it prints — including an outdated `snapshot` version — or that the config matches the tree.
50
- 7. **Report.** The config path, the topology, the resolved artifact and knowledge paths, and in `workspace` mode what was installed. **Commit nothing.**
60
+ 7. **Verify.** Run `scripts/detect.sh --root <path> --check`, then `sassy-skills check` for the links. Report every `drift:` line it prints — including an outdated `snapshot` version — or that the config matches the tree.
61
+ 8. **Report.** The config path, the topology, the resolved artifact and knowledge paths, the skills version pinned and that `/reload` is needed, and in `workspace` mode what was installed. **Commit nothing.**
51
62
 
52
63
  ## Hard rules
53
64
 
@@ -58,7 +69,9 @@ disable-model-invocation: true
58
69
  | No silent overwrite | An existing config or an existing `.sassywares/workspace/snapshot` is shown and asked about, never replaced blind. |
59
70
  | Version is reported | Setup names the snapshot version it installed; `--check` flags an installed copy older than the skill's. |
60
71
  | Never commit | Setup writes files and stops. Committing is `sassy-commit`. |
61
- | Write boundary | Only `.sassywares/config.yaml`, `.sassywares/workspace/snapshot`, and `.sassywares/workspace/.gitignore`. Nothing at the project root. |
72
+ | Skills are a dependency | Pin `@sassywares/skills`; never copy skill files into a project or maintain hand-made links. |
73
+ | Scripts are shared | Adding a missing script is fine; rewriting an existing `postinstall` or `skills:*` entry is not. |
74
+ | Write boundary | Only `.sassywares/`, the `package.json` scripts and devDependency, the lockfile, and two `.gitignore` lines. Never skill content. |
62
75
  | Detect, don't guess | An unknown base branch stays unknown and gets asked about. A probed value is reported as probed. |
63
76
  | Comments are part of the file | Every key keeps its description and allowed values. A stripped config is an incomplete config. |
64
77
  | Hooks are named, not written | Setup records hook skill names. Creating those skills is the project's work. |
@@ -0,0 +1,30 @@
1
+ # .sassywares
2
+
3
+ Project config and tooling for the Sassy Skills suite. Written by `sassy-setup`.
4
+
5
+ ## Skills
6
+
7
+ Installed as the npm package `@sassywares/skills`, pinned exactly in `package.json`. The entries in `.agents/skills/` and the `.claude/skills` symlink are **derived** from that package — never edit them by hand and never commit them.
8
+
9
+ ```bash
10
+ bun run skills:sync # recreate the links after a fresh clone
11
+ bun run skills:update # bump to the latest published version, then relink
12
+ ```
13
+
14
+ Skills print a one-line nudge when a newer version is published — once a day, and silently nothing when offline. It is never a blocker.
15
+
16
+ `.agents/skills/` is read by pi and Cursor; Claude Code reads `.claude/skills/` only, which is why that symlink exists. After any change, `/reload` your harness.
17
+
18
+ If a skill tells you another skill is missing, run `skills:sync` and restart. Substituted package managers: run the same script through `npm`, `pnpm`, or `yarn`.
19
+
20
+ ## Config
21
+
22
+ `config.yaml` holds the project's paths, git topology, and PR defaults. Every key is documented inline, including its allowed values, and the full schema is in the `sassy-setup` skill's `references/config.md`. Verify the file still matches the tree:
23
+
24
+ ```bash
25
+ node_modules/@sassywares/skills/skills/sassy-setup/scripts/detect.sh --root . --check
26
+ ```
27
+
28
+ ## Hooks
29
+
30
+ `hooks.pr.*` names project skills that run inside the PR pipeline: `before_create`, `after_create`, `before_merge`, `after_merge`. They are ordinary skills in `.agents/skills/`, which means they are **code** — review them like code, and never point a hook at a copy of a suite skill.
@@ -16,23 +16,25 @@ disable-model-invocation: true
16
16
 
17
17
  **Modes.** `mode:manual` (default) reviews the whole close-out before any side effect; `mode:auto` is pre-authorized and skips that review. Never toggle it on your own.
18
18
 
19
- **Config.** Resolve `.sassywares/config.yaml` (nearest, walking up) per `~/.agents/skills/sassy-setup/references/config.md`. Precedence: explicit token > session instruction > config > skill default. `pr.babysit`, `pr.merge`, and `pr.delete_branch` seed the close-out; `git.repo` decides whether this run commits, snapshots, or refuses; `hooks.pr.*` adds project steps to the PR pipeline.
19
+ **Config.** Resolve `.sassywares/config.yaml` (nearest, walking up) per the `sassy-setup` skill's `references/config.md`. Precedence: explicit token > session instruction > config > skill default. `pr.babysit`, `pr.merge`, and `pr.delete_branch` seed the close-out; `git.repo` decides whether this run commits, snapshots, or refuses; `hooks.pr.*` adds project steps to the PR pipeline.
20
+
21
+ **Update nudge.** Once per run, if `node_modules/.bin/sassy-skills` exists, run `sassy-skills outdated`. It is throttled to once a day per machine and silently does nothing offline. Pass any newer version on in one line; never block, retry, or fail on it.
20
22
 
21
23
  **Subagent-maximal by design.** The orchestrator delegates every heavy step (docs, tree reads, git, gh) to subagents and only receives compact summaries. It never reads diffs, file contents, or the tree itself.
22
24
 
23
25
  ## Steps (in order)
24
26
 
25
27
  1. **Review gate.** In `mode:manual` only: present a compact shipping summary — the plan or topic, the changes from the cycle results, the docs the close-out expects to write, the branch/PR target, the babysit options, and **the hooks this run will trigger, by point and name, or that none are declared** — and wait. In `mode:auto`, skip this gate.
26
- 2. **Compound (conditional).** If the work produced a qualifying learning, dispatch a `worker` whose task is: *read `~/.agents/skills/sassy-compound/SKILL.md` and follow it with `mode:auto`* — with a compact learnings brief (the solved problems, root causes, and solutions taken from the cycle results, not from re-reading the tree). It always gets `mode:auto` because ship's own gate already covered the approval. Skip cleanly when nothing qualifies, and skip entirely when `knowledge.write` is `false`.
27
- 3. **Handbook (conditional).** If the feature is self-contained and independent enough that a teammate would benefit from a handover, dispatch a `worker` whose task is: *read `~/.agents/skills/sassy-handbook/SKILL.md` and follow it* — with the plan path and topic. The worker reads the tree itself. Skip for small or tightly-coupled changes.
28
+ 2. **Compound (conditional).** If the work produced a qualifying learning, dispatch a `worker` whose task is: *load the `sassy-compound` skill and follow it with `mode:auto`* — with a compact learnings brief (the solved problems, root causes, and solutions taken from the cycle results, not from re-reading the tree). It always gets `mode:auto` because ship's own gate already covered the approval. Skip cleanly when nothing qualifies, and skip entirely when `knowledge.write` is `false`.
29
+ 3. **Handbook (conditional).** If the feature is self-contained and independent enough that a teammate would benefit from a handover, dispatch a `worker` whose task is: *load the `sassy-handbook` skill and follow it* — with the plan path and topic. The worker reads the tree itself. Skip for small or tightly-coupled changes.
28
30
  4. **Commit or snapshot.** After the review gate — never before it — the docs and any remaining code become durable.
29
- - **`git.repo` is a remote.** Dispatch a `worker`: *read `~/.agents/skills/sassy-commit/SKILL.md` and follow it*, naming that the commit must include the plan, any new solutions docs, the concepts file, `AGENTS.md`, and any handbook just written — resolved from `artifacts.*`, never hardcoded. Use `branch:current` when already on a feature branch, so the docs land with the phase commits. When the checked-out branch is the base branch, use `branch:new <name>` so the branch is created from `origin/<base>` and carries every phase commit; a workspace run passes the run's shared branch name. Never push directly to the base branch. It returns the hash and subject.
31
+ - **`git.repo` is a remote.** Dispatch a `worker`: *load the `sassy-commit` skill and follow it*, naming that the commit must include the plan, any new solutions docs, the concepts file, `AGENTS.md`, and any handbook just written — resolved from `artifacts.*`, never hardcoded. Use `branch:current` when already on a feature branch, so the docs land with the phase commits. When the checked-out branch is the base branch, use `branch:new <name>` so the branch is created from `origin/<base>` and carries every phase commit; a workspace run passes the run's shared branch name. Never push directly to the base branch. It returns the hash and subject.
30
32
  - **`git.repo: workspace`.** Commit code inside each affected repo on the run's shared branch (cut once on the run's first commit, `branch:current` after), then run `.sassywares/workspace/snapshot`. The snapshot is the workspace's commit and it runs every time, right after approval. A missing or outdated `.sassywares/workspace/snapshot` is a blocker: report it and say to run `sassy-setup`.
31
33
  - **`git.repo: none`.** No commit exists here. Report that the work is not persisted and stop.
32
- 5. **PR.** In a `remote` repo, dispatch a `worker`: *read `~/.agents/skills/sassy-commit-push-pr/SKILL.md` and follow it with `branch:current` and `babysit:off`* (step 6 owns the babysit). It returns the PR URL. In a `workspace`, open one PR per affected repo — the same branch name in each, `--base` from that repo's `repos` entry — and return a table. A touched repo missing from `repos`, or a base that cannot be resolved, is a stop-and-ask, never a guess. `none` opens nothing.
34
+ 5. **PR.** In a `remote` repo, dispatch a `worker`: *load the `sassy-commit-push-pr` skill and follow it with `branch:current` and `babysit:off`* (step 6 owns the babysit). It returns the PR URL. In a `workspace`, open one PR per affected repo — the same branch name in each, `--base` from that repo's `repos` entry — and return a table. A touched repo missing from `repos`, or a base that cannot be resolved, is a stop-and-ask, never a guess. `none` opens nothing.
33
35
 
34
36
  **The PR step owns the PR hooks.** `before_create` and `after_create` run inside `sassy-commit-push-pr`, once per PR; ship neither runs nor repeats them.
35
- 6. **Babysit.** With `pr.babysit` (default `true`), dispatch a `worker`: *read `~/.agents/skills/sassy-babysit/SKILL.md` and follow it for the PR just created, one checkpoint*, forwarding any `merge:on|off` and `delete-branch:on|off` the caller supplied, else `pr.merge` and `pr.delete_branch`. It reports looks-ready or blocked. For a continuous watch, tell the user to run `sassy-babysit` in-session.
37
+ 6. **Babysit.** With `pr.babysit` (default `true`), dispatch a `worker`: *load the `sassy-babysit` skill and follow it for the PR just created, one checkpoint*, forwarding any `merge:on|off` and `delete-branch:on|off` the caller supplied, else `pr.merge` and `pr.delete_branch`. It reports looks-ready or blocked. For a continuous watch, tell the user to run `sassy-babysit` in-session.
36
38
 
37
39
  **One PR per run.** Babysitting a workspace fan-out means one `sassy-babysit` run per PR, and a fan-out is never auto-merged: `pr.merge` and `pr.merge:on` apply to a single-PR run only. A multi-repo merge needs an explicit per-PR say-so.
38
40
 
@@ -23,6 +23,7 @@ disable-model-invocation: true
23
23
  - **Frontmatter fields:** `name`, `description` (required); optional `license`, `compatibility`, `metadata`, `allowed-tools`, `disable-model-invocation`.
24
24
  - Use `disable-model-invocation: true` for skills that should run only via explicit `/skill:name`.
25
25
  - **Locations:** user `~/.agents/skills/<name>/`; project `<repo>/.agents/skills/<name>/` (discovered from cwd up to the repo root). Also the `skills` array in settings and pi packages.
26
+ - **Reference other skills by name, never by absolute path.** The suite can live in `~/.agents/skills/`, a project `.agents/skills/`, or inside `node_modules/`, so "load the sassy-commit skill" is portable where a `~/…` path is not. Name a bundled file as "the sassy-setup skill's `references/config.md`".
26
27
  - Pi **validates at startup**; malformed or descriptionless skills warn. `/reload` applies changes.
27
28
  - Pi implements the **Agent Skills specification** (agentskills.io). For the full spec, pi's own `docs/skills.md` is authoritative — read it when the rules here are not enough.
28
29
 
@@ -13,6 +13,8 @@ disable-model-invocation: true
13
13
 
14
14
  **Outcome:** a grounded conformance judgment against an intent; the approved gaps closed; everything left uncommitted.
15
15
 
16
+ **Update nudge.** Once per run, if `node_modules/.bin/sassy-skills` exists, run `sassy-skills outdated`. It is throttled to once a day per machine and silently does nothing offline. Pass any newer version on in one line; never block, retry, or fail on it.
17
+
16
18
  **Orchestrator only.** Never edit code yourself. The `validator` finds gaps; the `worker` closes them.
17
19
 
18
20
  **This skill needs an intent.** Validation without something to validate against is meaningless. If the user gave no plan path or intent, ask for one before doing anything.
@@ -17,7 +17,9 @@ disable-model-invocation: true
17
17
 
18
18
  **The orchestrator does not do work.** Read the plan, dispatch `sassy-cycle`, apply its result, update the plan's tracking table, dispatch `sassy-commit` or the snapshot, and report. Never edit application code.
19
19
 
20
- **Config.** Resolve `.sassywares/config.yaml` (nearest, walking up) per `~/.agents/skills/sassy-setup/references/config.md`. Precedence: explicit token > session instruction > config > skill default. `git.repo` decides what the phase commit does, `artifacts.plans` decides where the plan lives.
20
+ **Config.** Resolve `.sassywares/config.yaml` (nearest, walking up) per the `sassy-setup` skill's `references/config.md`. Precedence: explicit token > session instruction > config > skill default. `git.repo` decides what the phase commit does, `artifacts.plans` decides where the plan lives.
21
+
22
+ **Update nudge.** Once per run, if `node_modules/.bin/sassy-skills` exists, run `sassy-skills outdated`. It is throttled to once a day per machine and silently does nothing offline. Pass any newer version on in one line; never block, retry, or fail on it.
21
23
 
22
24
  ## Input
23
25
 
@@ -58,7 +60,7 @@ Always gate before committing. One worker fixes findings, one re-check, then pre
58
60
  - **Any QA row fails** → run `sassy-cycle` once more on the same phase, with the failing rows as the intent reference and a work brief that fixes exactly those failures. Then present the gate again. If a row still fails after that one pass, mark it `Blocked` with the reason and stop.
59
61
  6. **Commit the phase.** On both gates passing, first mark the phase's QA rows `Done` with the user's notes and the phase `Done` in the Phase tracking table, then persist according to `git.repo`:
60
62
 
61
- - **A remote** — dispatch a `worker` whose task is: *read `~/.agents/skills/sassy-commit/SKILL.md` and follow it with `branch:current`*, naming that the commit must include the phase's code changes, the QA table results, and the now-updated Phase tracking table. It returns the hash and subject.
63
+ - **A remote** — dispatch a `worker` whose task is: *load the `sassy-commit` skill and follow it with `branch:current`*, naming that the commit must include the phase's code changes, the QA table results, and the now-updated Phase tracking table. It returns the hash and subject.
62
64
  - **`workspace`** — dispatch a `worker` per affected repo to commit that repo's code on the run's shared branch (cut once on the run's first commit with `branch:new <name>`, `branch:current` after), then run `.sassywares/workspace/snapshot` for the plan, QA results, and docs at the root. The gate's approval is the commit approval; the snapshot runs right after it and is never skipped. A missing or outdated `.sassywares/workspace/snapshot` is a blocker — say to run `sassy-setup`.
63
65
  - **`none`** — nothing is persisted. Report that the phase's work is uncommitted and stop; never hand off on top of it.
64
66