@sassywares/skills 0.5.1 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -133,8 +133,11 @@ knowledge:
133
133
  write: true # false = this project takes no learnings
134
134
  git:
135
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
136
+ base_map: # head branch -> the base it targets
137
+ "*": staging # ordinary work branches from and targets staging
138
+ staging: main # staging releases to main
139
+ # remote: owner/backup # workspace only: the snapshot mirror
140
+ # mirror_branch: main # workspace only: the mirror's branch
138
141
  repos: # workspace only: each nested repo -> the base its PRs target
139
142
  fe: dev
140
143
  commit:
@@ -143,17 +146,20 @@ pr:
143
146
  babysit: true
144
147
  merge: false # true authorizes merging every babysat PR here
145
148
  delete_branch: false
146
- hooks: # project steps injected into the PR pipeline
149
+ hooks:
147
150
  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
151
+ "*": # keyed by the PR's HEAD branch
152
+ before_create: [tap2-version mode:auto] # after the commit, before the PR opens
153
+ after_create: [] # the moment the PR exists
154
+ before_merge: [] # this run is about to merge
155
+ after_merge: [] # this run observed the merge
156
+ staging: # a per-point override for one head
157
+ before_create: []
152
158
  ```
153
159
 
154
160
  ### Extending the pipeline
155
161
 
156
- A project adds its own steps by naming a skill that lives in its own `.agents/skills/`. Nothing declared means nothing runs.
162
+ A project adds its own steps by naming a skill that lives in its own `.agents/skills/`. Nothing declared means nothing runs. Keys are the **head** branch — the one a PR comes from — and every point can be overridden per head; entries may carry args (`<skill> <args…>`).
157
163
 
158
164
  | Hook | Runs | Typical use |
159
165
  |---|---|---|
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sassywares/skills",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
4
4
  "description": "Lean, explicit-invocation Agent Skills for shipping fast: plan, work, review, validate, ship.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -21,7 +21,9 @@ 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 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.
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`; merge hooks come from `hooks.pr` for this run's head.
25
+
26
+ **Keys are the head branch.** Resolve per head: the exact head, then the longest matching family (`release/*`), then `"*"`. Each point overrides independently. An entry is `<skill> <args…>` — split on the first whitespace and pass the args verbatim.
25
27
 
26
28
  **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
29
 
@@ -35,7 +37,7 @@ An explicit token always beats the config.
35
37
 
36
38
  Snapshot the PR state first, then act in this order:
37
39
 
38
- 1. **Terminal** — `MERGED`/`CLOSED` → if it is `MERGED` and `hooks.pr.after_merge` is declared, run those hooks once (see Step 3) before stopping.
40
+ 1. **Terminal** — `MERGED`/`CLOSED` → if it is `MERGED` and `after_merge` resolves for this PR's head, run those hooks once (see Step 3) before stopping.
39
41
  2. **Capture the head SHA** — a later push invalidates this snapshot's CI.
40
42
  3. **Feedback before CI** — fetch open review threads (`gh pr view --json reviewThreads,comments`). Address each actionable thread: reply, then fix. Use `sassy-debug` for a real defect. Never wait for a reviewer who is "still reviewing" before acting on feedback already posted.
41
43
  4. **Stale-SHA cancellation** — if the head moved since step 2, skip the CI work for this snapshot.
@@ -50,7 +52,7 @@ Stop when any holds:
50
52
  - **Terminal** — merged or closed.
51
53
  - **Looks ready** — `MERGEABLE`, `CLEAN`, all checks terminal, no open review threads, no currency blocker, zero `needs-human` items. With `merge:on`, run the merge (and `delete-branch:on` branch deletion if set) before treating this as a stop; the merge turns it into the **Terminal** stop.
52
54
 
53
- **Merge hooks.** Immediately before merging, run `hooks.pr.before_merge` in declared order — one `worker` per hook, with the same brief every hook gets: plan path, changed files, artifact paths, base, branch, commit hash, and the PR URL plus head SHA. These run before the merge, so they must leave the tree clean. A missing skill or a `blocked` result means **do not merge**: report it and stop at Blocked. After a successful merge, run `hooks.pr.after_merge` the same way; these follow the merge, so they must leave the tree clean. In a workspace fan-out, everything is per PR.
55
+ **Merge hooks.** Immediately before merging, resolve `before_merge` for this PR's head and run each entry in declared order — one `worker` per hook, with the same brief every hook gets: plan path, changed files, artifact paths, base, branch, commit hash, and the PR URL plus head SHA. These run before the merge, so they must leave the tree clean. A missing skill or a `blocked` result means **do not merge**: report it and stop at Blocked. After a successful merge, resolve `after_merge` for this PR's head and run each entry the same way; these follow the merge, so they must leave the tree clean. In a workspace fan-out, everything is per PR.
54
56
 
55
57
  **Which merge hook fires.** `before_merge` needs a merge this run will perform. `after_merge` fires whenever this run observes the PR merged, including one that was already merged when the run started — so it must be idempotent.
56
58
 
@@ -16,7 +16,9 @@ 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 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.
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` supplies the `before_create` / `after_create` steps for this run's head.
20
+
21
+ **Keys are the head branch.** Resolve per head: the exact head, then the longest matching family (`release/*`), then `"*"`. Each point overrides independently. An entry is `<skill> <args…>` — split on the first whitespace and pass the args verbatim.
20
22
 
21
23
  ## Context
22
24
 
@@ -44,7 +46,7 @@ Only an exit-0 empty list means "no open PR". A non-zero exit means **unknown**
44
46
  ## Steps
45
47
 
46
48
  1. **Commit** — follow `sassy-commit` with the branch choice from the policy above (named files only, outcome-stating message). Honor an `exclude:<paths>` token if present.
47
- 2. **`before_create` hooks.** Read `hooks.pr.before_create` from the config; empty means skip. Run each name in declared order, one `worker` per hook, whose task is *read `<repo>/.agents/skills/<name>/SKILL.md` and follow it* plus a brief: plan path, changed files, artifact paths, base, branch, and the commit hash. This point runs **after** the commit, so a hook may write and commit its own artifacts — push whatever ends up on the branch. A missing skill or a `blocked` result stops the run and names the hook; a `needs-decision` result comes back to you to present.
49
+ 2. **`before_create` hooks.** Resolve `before_create` for the head branch (`base_map` decides the base, the head is the branch you are on); empty means skip. Split each entry on the first whitespace — the first token is the skill, the rest are its args. Run each name in declared order, one `worker` per hook, whose task is *read `<repo>/.agents/skills/<name>/SKILL.md` and follow it* plus a brief: plan path, changed files, artifact paths, base, branch, and the commit hash. This point runs **after** the commit, so a hook may write and commit its own artifacts — push whatever ends up on the branch. A missing skill or a `blocked` result stops the run and names the hook; a `needs-decision` result comes back to you to present.
48
50
  3. **Pre-push gate** — resolve any pre-push or review-ready requirement from the project's active instructions. Only evidence valid for the exact commit being sent satisfies it; otherwise stop and report what is missing. If none, proceed.
49
51
  4. **Push** — `git push -u origin <branch>` for a new branch, else `git push`. Re-verify branch and remote first, and re-check that the branch is ahead of origin after any hook commits.
50
52
  5. **Compose the PR title and body**
@@ -31,4 +31,4 @@ If a skill tells you another skill is missing, run `skills:sync` and restart. Su
31
31
 
32
32
  ## Hooks
33
33
 
34
- `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.
34
+ `hooks.pr` names project skills that run inside the PR pipeline, keyed by the PR's **head** branch: `"*"` for every PR, an exact branch, or a family such as `release/*`, overriding per point (`before_create`, `after_create`, `before_merge`, `after_merge`). An entry is `<skill> <args…>`; the args mean whatever that skill says they mean — `mode:auto` pre-authorizes the step. Hooks are ordinary skills in `.agents/skills/`, which makes them **code**: review them like code, and never point a hook at a copy of a suite skill.
@@ -157,8 +157,9 @@ feature/x --base_map["*"]--> staging --base_map[staging]--> main
157
157
  ```
158
158
 
159
159
  1. `base_map[<head>]` — the exact rung
160
- 2. `base_map["*"]` — ordinary work
161
- 3. stop and ask; never guess
160
+ 2. the longest matching family key (`release/*`) — a nested rung
161
+ 3. `base_map["*"]` — ordinary work
162
+ 4. stop and ask; never guess
162
163
 
163
164
  A rung is a release edge: standing on `staging`, a PR targets `main` without
164
165
  anyone passing `base:main`. Add a rung for any branch that releases elsewhere; a
@@ -169,6 +170,9 @@ The probed remote default is only useful as a hint for `"*"`: it tells you where
169
170
  the repository considers work to land, not what every branch targets. Confirm it
170
171
  before writing.
171
172
 
173
+ `--head <branch>` prints the resolution for one head: the base (repo topology)
174
+ and all four hook points. Use it instead of merging maps by hand.
175
+
172
176
  In a `workspace`, `base_map` does not apply — there are no branches at the
173
177
  workspace level. Nested repos carry their own bases in `repos`, and a nested repo
174
178
  that needs a ladder gets its own `.sassywares/config.yaml` rather than a nested
@@ -230,9 +234,9 @@ Per point, independently:
230
234
  3. `"*"`
231
235
 
232
236
  The first rule that declares a value for that point wins and the rest are not
233
- merged. So a `staging` entry that sets only `before_create` still inherits
234
- `after_merge` from `"*"`. Declaring the same point in two families that both
235
- match one head is a configuration error, not a precedence puzzle.
237
+ merged, so a `staging` entry that sets only `before_create` still inherits
238
+ `after_merge` from `"*"`. When two families match one head, the longer prefix
239
+ wins — `release/v2/*` beats `release/*`.
236
240
 
237
241
  ### Entries carry arguments
238
242
 
@@ -6,6 +6,7 @@
6
6
  #
7
7
  # Usage:
8
8
  # detect.sh [--root DIR] print a proposed config to stdout
9
+ # detect.sh [--head BRANCH] resolve the base and hooks for one head
9
10
  # detect.sh [--root DIR] --check compare the existing config to reality
10
11
  set -euo pipefail
11
12
 
@@ -290,6 +291,9 @@ if (( CHECK )); then
290
291
  fi
291
292
 
292
293
  if [[ "$TOPOLOGY" == "repo" ]]; then
294
+ if [[ -n "$(yflat "git${US}remote")" ]]; then
295
+ bad "git.remote is workspace-only — a repo config names its remote in git.repo"
296
+ fi
293
297
  if [[ -z "$(yflat "git${US}base_map${US}*")" ]]; then
294
298
  bad "git.base_map has no \"*\" entry — it is the base for ordinary work"
295
299
  else
@@ -302,7 +306,8 @@ if (( CHECK )); then
302
306
  done < <(yaml_flat | awk -F'\t' -v us="$US" '{n=split($1,a,us); if (n==3 && a[1]=="git" && a[2]=="base_map") print a[3]}' | sort -u)
303
307
  head_ref="${HEAD_ARG:-$(git -C "$ROOT" branch --show-current 2>/dev/null || true)}"
304
308
  if [[ -n "$head_ref" ]]; then
305
- note " base for '${head_ref}': $(base_for_head "$head_ref")"
309
+ # the --head readout below prints this line; avoid printing it twice
310
+ [[ -n "$HEAD_ARG" ]] || note " base for '${head_ref}': $(base_for_head "$head_ref")"
306
311
  cur="$head_ref"; hops=0
307
312
  while [[ -n "$cur" ]] && (( hops < 8 )); do
308
313
  next="$(yflat "git${US}base_map${US}${cur}")"
@@ -453,7 +458,8 @@ EOF
453
458
  else
454
459
  printf ' "*": %s # base for ordinary work; never guessed\n' "<confirm by hand>"
455
460
  fi
456
- printf ' # staging: main # uncomment when a branch releases somewhere else\n'
461
+ printf ' # staging: main # uncomment when a branch releases somewhere else\n'
462
+ printf ' # release/*: main # a family of heads, longest prefix wins\n'
457
463
  cat <<'EOF'
458
464
 
459
465
  # workspace only: each nested repo folder -> the base its PRs target
@@ -22,7 +22,7 @@ disable-model-invocation: true
22
22
 
23
23
  ## Steps (in order)
24
24
 
25
- 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.
25
+ 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 resolved for this run's head — point, skill, and args — or that none are declared** — and wait. In `mode:auto`, skip this gate.
26
26
  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`.
27
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: *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
28
  4. **Commit or snapshot.** After the review gate — never before it — the docs and any remaining code become durable.