@sassywares/skills 0.5.4 → 0.5.6
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 +1 -1
- package/README.md +11 -1
- package/package.json +1 -1
- package/skills/sassy-commit/SKILL.md +5 -3
- package/skills/sassy-commit-push-pr/SKILL.md +2 -2
- package/skills/sassy-debug/SKILL.md +8 -2
- package/skills/sassy-lfg/SKILL.md +12 -11
- package/skills/sassy-setup/SKILL.md +1 -0
- package/skills/sassy-setup/assets/sassywares-readme.md +1 -1
- package/skills/sassy-setup/references/branching.md +97 -0
- package/skills/sassy-setup/references/config.md +43 -4
- package/skills/sassy-setup/scripts/detect.sh +145 -5
- package/skills/sassy-ship/SKILL.md +6 -3
- package/skills/sassy-skill/SKILL.md +1 -1
- package/skills/sassy-work/SKILL.md +19 -18
package/CONCEPTS.md
CHANGED
|
@@ -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
|
|
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 bumps the patch version, commits, pushes `main`, then tags and pushes the tag, which publishes to npm. It installs nothing: `npx skills add` reads this repo directly, and the npm package follows the tag the script creates.
|
package/README.md
CHANGED
|
@@ -259,6 +259,14 @@ The fix gate offers four options:
|
|
|
259
259
|
|
|
260
260
|
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`.
|
|
261
261
|
|
|
262
|
+
## Branching
|
|
263
|
+
|
|
264
|
+
`sassy-work`, `sassy-lfg`, and `sassy-debug` decide the branch **before they touch code**, from `commit.branch` (default `new`) compared against `base_map`. On `new`, the run cuts a branch and every commit — phases, docs, ship — lands on it, so no change ever exists on a base branch. A run that is already on a branch that is not a base reuses it, which is how a handed-off phase, a resumed run, or `sassy-lfg` after `sassy-work` converges instead of cutting a second branch.
|
|
265
|
+
|
|
266
|
+
A base is **any `base_map` value**, not just the remote default: in a repo whose `"*"` targets `staging`, standing on `staging` counts as standing on a base. That distinction is the whole point of the key — see [`skills/sassy-setup/references/branching.md`](skills/sassy-setup/references/branching.md).
|
|
267
|
+
|
|
268
|
+
The three skills take `branch:infer|current|new` to override the config for one run: `infer` (default) means the config decides, `new` forces a branch, `current` commits where you stand. `sassy-ship` never creates a branch; finding the head on a base branch with unpublished commits is a stop-and-ask.
|
|
269
|
+
|
|
262
270
|
## Repository layout
|
|
263
271
|
|
|
264
272
|
```text
|
|
@@ -313,12 +321,14 @@ pi install npm:@sassywares/skills
|
|
|
313
321
|
|
|
314
322
|
## Repository maintenance
|
|
315
323
|
|
|
316
|
-
`scripts/sync.sh`
|
|
324
|
+
`scripts/sync.sh` bumps the patch version, commits, pushes `main`, then tags and pushes the tag — so a change to a skill is released in one step. Any agent that changes a skill must run it immediately, without asking for confirmation. It installs nothing anywhere; consumers get a change when they run `npx skills update` after a push. `AGENTS.md` documents the workflow for agents working on this repo.
|
|
317
325
|
|
|
318
326
|
### Releases
|
|
319
327
|
|
|
320
328
|
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.
|
|
321
329
|
|
|
330
|
+
`scripts/sync.sh` does this for you on `main`. The manual sequence is the fallback for recovering a failed run:
|
|
331
|
+
|
|
322
332
|
```bash
|
|
323
333
|
npm version patch --no-git-tag-version
|
|
324
334
|
git add -A && git commit -m "chore: release v$(node -p "require('./package.json').version")"
|
package/package.json
CHANGED
|
@@ -28,9 +28,11 @@ 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 the `sassy-setup` skill's `references/config.md`.
|
|
31
|
+
**Config.** Resolve `.sassywares/config.yaml` (nearest, walking up) per the `sassy-setup` skill's `references/config.md`. `commit.branch` supplies the default.
|
|
32
32
|
|
|
33
|
-
**
|
|
33
|
+
**Who may pass a token.** Precedence is explicit token > session instruction > config > skill default, and a token counts as explicit only when the **caller** supplied it — the user typed it, or an earlier gate of the same run authorized it. A skill must never pass its own default as a token: that promotes a default over the project's config and makes `commit.branch` unreachable. When the entry skill (`sassy-work`, `sassy-lfg`, `sassy-debug`) has already resolved the branch through `sassy-setup`'s `references/branching.md`, it passes `branch:current`, because the branch exists and the work belongs on it.
|
|
34
|
+
|
|
35
|
+
**Branch option.** `branch:current|new [<name>]`. `current` commits on the checked-out branch. `new` cuts a branch from `origin/<base>` — `<base>` is `base_map` from the repo's own config when it has one, else `repos[<dir>]` from the workspace config, else the probed base **confirmed with the user** — named `<name>` when given, else from the change. Use `new` only when no branch has been cut yet; the entry skills cut at the start of a run, so a `new` from them means the cut was skipped. `sassy-commit-push-pr` forwards its own `branch` choice here.
|
|
34
36
|
|
|
35
37
|
**Not in a workspace root, not outside git.** With `git.repo: workspace` there is no local commit to make: refuse in one line and name the snapshot instead. With `git.repo: none`, refuse and say the project is deliberately outside git. Inside a nested repo, a normal commit is correct — the nearest work tree is the repo.
|
|
36
38
|
|
|
@@ -38,7 +40,7 @@ Treat the reads as a snapshot; re-read branch and staged set right before commit
|
|
|
38
40
|
|
|
39
41
|
0. **Gather** the context above.
|
|
40
42
|
1. **Nothing to commit** — if there are no staged, modified, or untracked files, say so and stop.
|
|
41
|
-
2. **Branch** — honor the `branch` option. With `branch:new`, cut from the base: `git fetch <remote> <base>`, then `git checkout -b <name> <remote>/<base>`, then confirm `git merge-base HEAD <remote>/<base>` is `<remote>/<base>`. If that ancestry check fails, the branch was cut from the wrong base — recreate it, never push it. If the name exists,
|
|
43
|
+
2. **Branch** — honor the `branch` option. With `branch:new`, cut from the base: `git fetch <remote> <base>`, then `git checkout -b <name> <remote>/<base>`, then confirm `git merge-base HEAD <remote>/<base>` is `<remote>/<base>`. If that ancestry check fails, the branch was cut from the wrong base — recreate it, never push it. If the name already exists, reuse it when it descends from the base; fork a suffix only when it has diverged, and never cut a second branch off the base for the same work. With `branch:current`, commit on the checked-out branch. With no token and no `commit.branch`, treat it as `new` and cut one. **Base means any `base_map` value, not just `origin/HEAD`**: `bash .agents/skills/sassy-setup/scripts/detect.sh --branch` answers it, and a branch it reports as `on-base` must be named in the summary you return — a warning nobody sees protects nothing.
|
|
42
44
|
3. **Convention** — match the project's existing commit style; else the recent log pattern; else conventional commits (`type(scope): description`). When `fix`/`feat` both fit, default to `fix:`.
|
|
43
45
|
4. **Logical commits** — if changed files clearly split into distinct concerns, make separate commits (file level, 2–3 max, never `git add -p`). If ambiguous, one commit.
|
|
44
46
|
5. **Message** — imperative subject naming the outcome, not the file list. Body only when the motivation is not obvious.
|
|
@@ -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 the `sassy-setup` skill's `references/config.md`.
|
|
19
|
+
**Config.** Resolve `.sassywares/config.yaml` (nearest, walking up) per the `sassy-setup` skill's `references/config.md`. `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. Precedence is explicit token > session instruction > config > skill default, and a token is explicit only when the **caller** supplied it; never pass this skill's own default as a token.
|
|
20
20
|
|
|
21
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.
|
|
22
22
|
|
|
@@ -37,7 +37,7 @@ gh pr list --head <branch> --state open --json url,number
|
|
|
37
37
|
|
|
38
38
|
Only an exit-0 empty list means "no open PR". A non-zero exit means **unknown** — resolve auth/connectivity first, never treat it as "none". Probe output is a snapshot; re-check branch/remote/PR state right before the push and the PR create.
|
|
39
39
|
|
|
40
|
-
**Branch policy.**
|
|
40
|
+
**Branch policy.** The run's branch is normally already decided: `sassy-work` and `sassy-lfg` cut it at the start via `sassy-setup`'s `references/branching.md`, and `sassy-ship` forwards `branch:current`. So the default here is to commit on the checked-out branch when it is not a base. Only when it *is* a base or a detached HEAD — a bare invocation with no orchestrator above it — create a feature branch named from the work and say so. `branch:current` or a plain request keeps the checked-out branch (that run pushes and skips the PR). Never ask whether to branch. Forward the resolved choice to `sassy-commit`: `branch:new <name>` only when this run creates a branch, else `branch:current`.
|
|
41
41
|
|
|
42
42
|
**Base branch.** `<base>` is `base_map[<current head>]` from the nearest config — a nested repo's own config wins over the workspace's `repos[<dir>]` — falling back to `base_map["*"]`, else the probed remote HEAD **confirmed with the user before use**. The map is the reason a release PR targets `main` while the work sits on `staging`, with no token passed. An explicit `base:<branch>` token wins over all of them — use it when this PR targets something other than the day-to-day base, such as a release PR aimed at `main` while work lands on `staging`. **Never use `defaultBranchRef` as the PR base when the config names one.** Pass it explicitly: `gh pr create --base <base>`.
|
|
43
43
|
|
|
@@ -5,7 +5,7 @@ description: >-
|
|
|
5
5
|
through sassy-cycle (fix, review, validate), and finally ship it
|
|
6
6
|
through sassy-ship after code approval. Never auto-commits. Use when asked to
|
|
7
7
|
debug or fix a failure.
|
|
8
|
-
argument-hint: "[issue reference, error message, test path, or description]"
|
|
8
|
+
argument-hint: "[issue reference, error message, test path, or description] [branch:infer|current|new]"
|
|
9
9
|
disable-model-invocation: true
|
|
10
10
|
---
|
|
11
11
|
|
|
@@ -15,6 +15,8 @@ Find the root cause of a failure, get approval for the fix, fix it through the s
|
|
|
15
15
|
|
|
16
16
|
**Done when:** the full causal chain is stated with `file:line` evidence, the fix gate was answered with one of its four options, and either the fix has passed the quality cycle and shipped after code approval, or the run ended at the fix gate.
|
|
17
17
|
|
|
18
|
+
**Config.** Resolve `.sassywares/config.yaml` (nearest, walking up) per the `sassy-setup` skill's `references/config.md`. `commit.branch` decides whether the fix needs its own branch; `sassy-work` makes that decision when the fix starts, because the branch belongs to the work and not to the diagnosis.
|
|
19
|
+
|
|
18
20
|
**Escalate rather than persist:** 2–3 hypotheses exhausted without confirmation, or 3 failed fix attempts, means diagnose *why* instead of trying again. One hypothesis, one change at a time — never shotgun-debug. Before escalating, re-search the prior learnings with the keywords of what you just ruled out: an exhausted hypothesis list is often a documented trap.
|
|
19
21
|
|
|
20
22
|
## Phase 0 — Triage
|
|
@@ -57,7 +59,11 @@ This gate is mandatory even when the original request said "fix the bug". Do not
|
|
|
57
59
|
|
|
58
60
|
### Option 1 — Fix it now
|
|
59
61
|
|
|
60
|
-
Load the `sassy-work` skill with the fix brief as the work brief and the diagnosis from Phase 1 as the intent reference. Work runs one cycle, presents its own code approval gate, commits, and stops before ship.
|
|
62
|
+
Load the `sassy-work` skill with the fix brief as the work brief and the diagnosis from Phase 1 as the intent reference, forwarding this run's `branch:` token if it was given. Work runs one cycle, presents its own code approval gate, commits, and stops before ship.
|
|
63
|
+
|
|
64
|
+
The branch decision belongs to work, not to this phase: diagnosis writes nothing, so cutting a branch before the fix gate would leave an empty branch every time the gate ends in *Write a plan*, *Hand off*, or *Stop here*. Work resolves it per `sassy-setup`'s `references/branching.md` before it touches code, so the fix is on its own branch from the first edit and still never lands on a base branch.
|
|
65
|
+
|
|
66
|
+
Continue to Phase 3.
|
|
61
67
|
|
|
62
68
|
### Option 2 — Write a plan
|
|
63
69
|
|
|
@@ -4,7 +4,7 @@ description: >-
|
|
|
4
4
|
Execute a whole sassy plan phase by phase, or one settled in-context brief,
|
|
5
5
|
then close out via sassy-ship after code approval. The orchestrator only
|
|
6
6
|
coordinates. Best for small plans that one context can hold.
|
|
7
|
-
argument-hint: "<plan path | brief>"
|
|
7
|
+
argument-hint: "<plan path | brief> [branch:infer|current|new]"
|
|
8
8
|
disable-model-invocation: true
|
|
9
9
|
---
|
|
10
10
|
|
|
@@ -14,7 +14,7 @@ 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 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.
|
|
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; `commit.branch` decides whether the run needs its own branch.
|
|
18
18
|
|
|
19
19
|
## Input
|
|
20
20
|
|
|
@@ -38,18 +38,17 @@ A brief has no QA table, so its gate is code approval alone. Never invent, enabl
|
|
|
38
38
|
## Steps
|
|
39
39
|
|
|
40
40
|
1. **Load the plan.** Read the plan and its Phase tracking table. If phases are already `In progress` from an interrupted run, ask once whether to resume at the gate or rerun those cycles, then continue from the answer.
|
|
41
|
-
2. **
|
|
41
|
+
2. **Resolve the branch, before touching code.** Follow the `sassy-setup` skill's `references/branching.md` with this run's `branch:` token, if any. LFG runs every phase in one session, so the branch is cut once here and every phase commits onto it. Report the branch and its start point in the gate.
|
|
42
|
+
3. **Run every phase, in order.** For each phase not `Done`:
|
|
42
43
|
- Set the phase `In progress`, then load the `sassy-cycle` skill and follow it, with work brief = the phase's goal/work/"Done when", and intent reference = the plan path and phase.
|
|
43
44
|
- On `Status: done` and `Validator: Conforms`, leave the phase `In progress` — its QA rows are still owed, and only the gate closes them.
|
|
44
45
|
- On `Status: blocked`, mark it `Blocked` and stop the run.
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
4. **The gate.** After the last phase, report: the plan path, each phase's status, what changed per phase, and the validator/reviewer summaries. Then give every phase's QA checklist verbatim — row ID, check, how to run it, what passing looks like — with unreported rows `Pending`. Ask for both decisions: code approval and the QA result per row. Wait. Do not commit.
|
|
47
|
+
5. **Resolve the gate.** Three outcomes:
|
|
47
48
|
- **Code approved and every QA row passes** → mark the rows `Done` with the user's notes, mark the phases `Done`, then run the close-out below.
|
|
48
49
|
- **Code approved, QA still pending** → tick nothing, mark nothing `Done`, commit nothing, ship nothing. Report which rows are outstanding and how to resume (`/skill:sassy-lfg <plan>`), and stop.
|
|
49
50
|
- **Any QA row fails** → run one more `sassy-cycle` on that phase with the failing rows as the intent reference, then present the gate again. A row that still fails is `Blocked` with the reason and stops the run.
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
Work commits to the current branch. `sassy-ship` creates the shipping branch at close-out when the checked-out branch is the repo default.
|
|
51
|
+
6. **Close-out.** On both gates passing, load the `sassy-ship` skill and follow it. Do only the close-out steps the user approves. Babysit options (`merge:on|off`, `delete-branch:on|off`) are forwarded through `sassy-ship` to `sassy-babysit`. The branch from step 2 already carries the work, so ship commits and opens the PR on it; ship never creates the branch.
|
|
53
52
|
|
|
54
53
|
## Brief constraints
|
|
55
54
|
|
|
@@ -63,15 +62,17 @@ Every worker brief this run dispatches must include:
|
|
|
63
62
|
|
|
64
63
|
When the argument is not a plan path, the brief is the one phase:
|
|
65
64
|
|
|
66
|
-
1.
|
|
67
|
-
2.
|
|
68
|
-
3.
|
|
65
|
+
1. Resolve the branch exactly as the plan path does — `references/branching.md`, this run's `branch:` token — before the cycle runs.
|
|
66
|
+
2. Run one `sassy-cycle` with the brief as the work brief and its decisions / done-when as the intent reference. Do not split it into multiple phases.
|
|
67
|
+
3. Present the code approval gate with no QA checklist, and wait.
|
|
68
|
+
4. On code approval, run `sassy-ship`. Do not run `sassy-plan` or write a plan file.
|
|
69
69
|
|
|
70
70
|
## Hard rules
|
|
71
71
|
|
|
72
72
|
| Rule | Requirement |
|
|
73
73
|
|------|-------------|
|
|
74
74
|
| Orchestrator only | Delegate every phase to `sassy-cycle`. Never read source or edit code. |
|
|
75
|
+
| Branch before code | Resolve the branch per `references/branching.md` before any phase runs, so no change lands on a base branch. |
|
|
75
76
|
| Phases in plan order | Phases run one after another, in plan order. |
|
|
76
77
|
| Done needs both gates | Mark a phase `Done` only when the user approves the code and every QA row for it is `Done`. |
|
|
77
78
|
| QA rows follow the user | Tick a QA row `Done` only from the user's explicit confirmation; keep unreported rows `Pending`; log a failed row as a finding for one more cycle. |
|
|
@@ -27,6 +27,7 @@ disable-model-invocation: true
|
|
|
27
27
|
- **Artifacts** — offer the detected paths as the default; take overrides.
|
|
28
28
|
- **Knowledge** — `knowledge.read` defaults to the solutions dir plus the concepts file. Ask whether there are extra folders to read (a hand-written `notes/learnings/`, a `docs/adr/`).
|
|
29
29
|
- **Base map** — detection proposes `"*"` from the probed remote default. Ask where ordinary work branches from, and whether the default branch itself releases to another branch (a `staging: main` rung). Never write `main` as a guess. In `workspace` mode, show the `repos` map and get a yes on every row — a probed base is only the remote's default.
|
|
30
|
+
- **`commit.branch`** — default `new`: the run cuts a branch before the first change and never commits on a base branch. Offer `current` for a repo that genuinely wants work committed where it stands, and say plainly that `current` lets a run commit onto its base branch.
|
|
30
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.
|
|
31
32
|
- **Hooks** — offer, do not impose. Ask whether the project wants steps injected at `before_create`, `after_create`, `before_merge`, or `after_merge`, and under which head branch (`"*"` for all). Entries may carry args (`<skill> <args…>`); a hook skill's args are its own vocabulary. Leave them `[]` when there is 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.
|
|
32
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.
|
|
@@ -19,7 +19,7 @@ The vendored skills and the lock are committed; `.claude/skills` is a link and i
|
|
|
19
19
|
|
|
20
20
|
`.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.
|
|
21
21
|
|
|
22
|
-
If a skill tells you another skill is missing, run `skills
|
|
22
|
+
If a skill tells you another skill is missing, run `npx skills add sassywares/sassy-skills` (or `npx skills experimental_install` to restore exactly what the lock pins) and restart.
|
|
23
23
|
|
|
24
24
|
## Config
|
|
25
25
|
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Branching
|
|
2
|
+
|
|
3
|
+
One branch decision per run, made **before** the first change. `sassy-work`,
|
|
4
|
+
`sassy-lfg`, and `sassy-debug` all resolve it here, through one detector, so the
|
|
5
|
+
three cannot drift apart the way three hand-copied paragraphs would.
|
|
6
|
+
|
|
7
|
+
## Resolve
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
bash .agents/skills/sassy-setup/scripts/detect.sh --branch [--policy current|new]
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
`--policy` is the `branch:` token: `infer` (the default), `current`, or `new`.
|
|
14
|
+
Omit it to let `commit.branch` decide; `branch:infer` means exactly that and is
|
|
15
|
+
passed only to be explicit.
|
|
16
|
+
|
|
17
|
+
Output:
|
|
18
|
+
|
|
19
|
+
| Key | Meaning |
|
|
20
|
+
|---|---|
|
|
21
|
+
| `topology` | `repo`, `workspace`, or `none` |
|
|
22
|
+
| `configured` | `commit.branch` from the config, or `none` |
|
|
23
|
+
| `policy` | what actually applies: the token, else the config, else `new` |
|
|
24
|
+
| `head` | the checked-out branch, or `none` when detached |
|
|
25
|
+
| `state` | `on-base`, `on-branch`, `detached`, or `no-git` |
|
|
26
|
+
| `action` | `create`, `reuse`, `commit-here`, or `refuse` |
|
|
27
|
+
| `start-point` | where a new branch is cut from — `origin/<head>`, never local `HEAD` |
|
|
28
|
+
| `unpublished` | commits on local `<head>` that are not on `origin/<head>` |
|
|
29
|
+
| `pr-base` | the base a PR from this run targets |
|
|
30
|
+
| `pr-base-source` | `config`, `probed`, or `unknown` — a probed base is confirmed with the user before use |
|
|
31
|
+
|
|
32
|
+
A base branch is **any `base_map` value**, plus the remote default. That is why
|
|
33
|
+
`staging` counts as a base in a repo whose `base_map` routes `"*"` to it: the
|
|
34
|
+
remote default alone is not the base, it is only one of them.
|
|
35
|
+
|
|
36
|
+
## Token authorship
|
|
37
|
+
|
|
38
|
+
Pass `--policy` only when the **caller** asked for it — the user typed
|
|
39
|
+
`branch:new`, or an earlier gate of this same run authorized it.
|
|
40
|
+
|
|
41
|
+
Never pass a token that is merely this skill's own default. Doing that launders
|
|
42
|
+
a default into "explicit token", which outranks the project's config and makes
|
|
43
|
+
`commit.branch` unreachable. Omitting `--policy` is how a skill defers to
|
|
44
|
+
config. This applies to every token these skills forward, not just `branch`.
|
|
45
|
+
|
|
46
|
+
## Act on `action`
|
|
47
|
+
|
|
48
|
+
### `create`
|
|
49
|
+
|
|
50
|
+
1. `unpublished` must be `0`. If not, **stop and ask**: cutting from local HEAD
|
|
51
|
+
would carry commits stranded on the base into the new branch, and cutting
|
|
52
|
+
from `origin/<head>` would abandon them. Name the count, the branch, and the
|
|
53
|
+
choice.
|
|
54
|
+
2. Name it `<type>/<slug>` from the plan filename or the topic, deterministically,
|
|
55
|
+
so a retried run converges on the same name instead of forking `-2`.
|
|
56
|
+
3. If that name already exists: reuse it when it descends from `start-point`;
|
|
57
|
+
stop and ask when it has diverged.
|
|
58
|
+
4. `git fetch <remote> <head>`, then `git checkout -b <name> <start-point>`.
|
|
59
|
+
5. Report the branch, the start point, and `pr-base`.
|
|
60
|
+
|
|
61
|
+
### `reuse`
|
|
62
|
+
|
|
63
|
+
Report `head` and carry on. Do **not** re-cut because `commit.branch` is `new`:
|
|
64
|
+
a second phase, a resumed run, or a handed-off session is already standing on
|
|
65
|
+
the branch the first one cut. That is the whole reason this is inferred rather
|
|
66
|
+
than forced.
|
|
67
|
+
|
|
68
|
+
### `commit-here`
|
|
69
|
+
|
|
70
|
+
Only reachable with `policy: current`, which is an explicit statement that
|
|
71
|
+
committing on the base branch is intended. Proceed, and say in the run's gate
|
|
72
|
+
summary that the commits are landing on `<head>` and that `pr-base` is
|
|
73
|
+
`<pr-base>`. Never infer this from a skill default.
|
|
74
|
+
|
|
75
|
+
### `refuse`
|
|
76
|
+
|
|
77
|
+
Detached HEAD with `policy: current`, or `topology: none`. Report and stop;
|
|
78
|
+
there is no branch to commit to.
|
|
79
|
+
|
|
80
|
+
## Workspace
|
|
81
|
+
|
|
82
|
+
Resolve once at the workspace root; the run uses **one branch name across every
|
|
83
|
+
nested repo**. The detector prints each repo's head and state, so cut inside the
|
|
84
|
+
repos whose state is `on-base` and reuse the rest. A nested repo carrying its own
|
|
85
|
+
`commit.branch` that disagrees is printed as `drift` — that is a config bug to
|
|
86
|
+
report, not a per-repo override, because one run cannot follow two policies.
|
|
87
|
+
|
|
88
|
+
## After the branch exists
|
|
89
|
+
|
|
90
|
+
Downstream steps forward `branch:current` to `sassy-commit`, because the decision
|
|
91
|
+
has already been made and the commits belong on that branch. `sassy-ship` never
|
|
92
|
+
creates the branch — by the time it runs, the branch carries the work. If it ever
|
|
93
|
+
finds the run's head on a base branch with unpublished commits, that is a
|
|
94
|
+
mis-run: stop and ask, never relocate commits silently.
|
|
95
|
+
|
|
96
|
+
Report the branch and its start point in every gate summary. A decision the user
|
|
97
|
+
cannot see is a decision they cannot correct.
|
|
@@ -25,6 +25,15 @@ explicit token in the request > instruction in this session
|
|
|
25
25
|
> .sassywares/config.yaml > skill default
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
+
**A token is explicit only when the caller supplied it** — the user typed it, or
|
|
29
|
+
an earlier gate of the same run authorized that side effect. A skill must never
|
|
30
|
+
pass its own default as a token, to another skill or to itself: once a default
|
|
31
|
+
sits in the token slot, nothing downstream can tell it apart from a user's
|
|
32
|
+
choice, so it silently outranks the project's config. A skill defers to config
|
|
33
|
+
by **omitting** the token, never by passing a value it invented. That is how
|
|
34
|
+
`commit.branch` became unreachable while every skill still read as if it
|
|
35
|
+
explained the rule correctly.
|
|
36
|
+
|
|
28
37
|
Read it once per run. An orchestrator resolves every config-backed value and
|
|
29
38
|
passes the resolved values into subagent briefs; subagents never re-read the
|
|
30
39
|
file. A skill invoked directly resolves it itself.
|
|
@@ -76,7 +85,7 @@ hooks:
|
|
|
76
85
|
| `git.remote` | mirror of `git.repo` | Workspace only: mirror remote (URL or `owner/name`) |
|
|
77
86
|
| `git.mirror_branch` | `main` | Workspace only: the branch the mirror pushes to |
|
|
78
87
|
| `repos` | `{}` | Workspace only: repo dir name → the base its PRs target |
|
|
79
|
-
| `commit.branch` | `
|
|
88
|
+
| `commit.branch` | `new` | Resolved once per run, before the first change. `new` never commits on a base branch: `sassy-work`, `sassy-lfg`, and `sassy-debug` cut a branch first. `current` commits where you stand, base branch included |
|
|
80
89
|
| `pr.babysit` | `true` | Whether close-out babysits the PR it opened |
|
|
81
90
|
| `pr.merge` | `false` | `true` authorizes merging a single babysat PR |
|
|
82
91
|
| `pr.delete_branch` | `false` | Delete the head branch once merged |
|
|
@@ -143,6 +152,35 @@ without asking, so the version line is how staleness surfaces.
|
|
|
143
152
|
In `workspace` and `none`, changes still land on disk. Nothing is silently
|
|
144
153
|
dropped; the refusal names why and what to run instead.
|
|
145
154
|
|
|
155
|
+
## `commit.branch` — decide once, before code
|
|
156
|
+
|
|
157
|
+
Resolved once per run, by the entry skill (`sassy-work`, `sassy-lfg`,
|
|
158
|
+
`sassy-debug`), **before the first change**, through
|
|
159
|
+
`sassy-setup/references/branching.md`. Not at commit time: committing first and
|
|
160
|
+
re-branching later leaves the commits on a base branch, and cutting the new
|
|
161
|
+
branch from `origin/<base>` then leaves them behind entirely.
|
|
162
|
+
|
|
163
|
+
| Value | Meaning |
|
|
164
|
+
|-------|---------|
|
|
165
|
+
| `current` | Commit where you stand, even on a base branch. An explicit statement that this repo's work in progress is acceptable |
|
|
166
|
+
| `new` (default) | Never commit on a base branch. Cut one first, then stay on it for the rest of the run |
|
|
167
|
+
|
|
168
|
+
A branch that is already checked out and is **not** a base is reused, whatever
|
|
169
|
+
the config says. That is what makes a second phase, a resumed run, or a
|
|
170
|
+
handed-off session converge on the branch the first one cut instead of cutting
|
|
171
|
+
another.
|
|
172
|
+
|
|
173
|
+
A base is **any `base_map` value**, plus the remote default — not the remote
|
|
174
|
+
default alone. In the config above `staging` is a base because `"*"` targets it,
|
|
175
|
+
even though `origin/HEAD` is `main`. Comparing only against `origin/HEAD` is how
|
|
176
|
+
phase work once got committed straight onto `staging`.
|
|
177
|
+
|
|
178
|
+
The `branch:infer|current|new` token on those skills overrides this key for one
|
|
179
|
+
run; `infer` means this key decides. In a workspace the key is resolved from the
|
|
180
|
+
workspace config for the whole run, one branch name in every repo; a nested
|
|
181
|
+
repo whose own `commit.branch` disagrees is drift, because one run cannot follow
|
|
182
|
+
two policies.
|
|
183
|
+
|
|
146
184
|
## Resolution chains
|
|
147
185
|
|
|
148
186
|
**Effective repo.** Inside a git work tree, the nearest toplevel is the repo —
|
|
@@ -186,9 +224,10 @@ config to. When a repo has both, the nested config wins and `--check` reports
|
|
|
186
224
|
the disagreement rather than picking silently. A nested config needs no ladder
|
|
187
225
|
of its own unless its PRs target more than one branch.
|
|
188
226
|
|
|
189
|
-
**Branch name**, one per run. Derived once from the plan slug or topic
|
|
190
|
-
|
|
191
|
-
|
|
227
|
+
**Branch name**, one per run. Derived once from the plan slug or topic when the
|
|
228
|
+
run starts, then used in every repo. The branch is cut at the start of the run,
|
|
229
|
+
not at commit time, so every repo sits on it before any change exists; each
|
|
230
|
+
repo cuts from its own upstream base and reuses the name in the others.
|
|
192
231
|
|
|
193
232
|
Cut from the base, then verify:
|
|
194
233
|
|
|
@@ -7,12 +7,19 @@
|
|
|
7
7
|
# Usage:
|
|
8
8
|
# detect.sh [--root DIR] print a proposed config to stdout
|
|
9
9
|
# detect.sh [--head BRANCH] resolve the base and hooks for one head
|
|
10
|
+
# detect.sh --branch [--policy P] resolve the branch decision for this run
|
|
10
11
|
# detect.sh [--root DIR] --check compare the existing config to reality
|
|
12
|
+
#
|
|
13
|
+
# --policy takes infer|current|new: the skill-level token. infer (the default)
|
|
14
|
+
# defers to commit.branch in the config, and new is the fallback when the config
|
|
15
|
+
# is silent. --branch is read-only; it prints the decision, it never cuts.
|
|
11
16
|
set -euo pipefail
|
|
12
17
|
|
|
13
18
|
ROOT_ARG=""
|
|
14
19
|
HEAD_ARG=""
|
|
20
|
+
POLICY_ARG=""
|
|
15
21
|
CHECK=0
|
|
22
|
+
BRANCH=0
|
|
16
23
|
|
|
17
24
|
while (( $# )); do
|
|
18
25
|
case "$1" in
|
|
@@ -20,8 +27,11 @@ while (( $# )); do
|
|
|
20
27
|
--root=*) ROOT_ARG="${1#*=}" ;;
|
|
21
28
|
--head) shift; [[ $# -ge 1 ]] || { echo "missing BRANCH after --head" >&2; exit 2; }; HEAD_ARG="$1" ;;
|
|
22
29
|
--head=*) HEAD_ARG="${1#*=}" ;;
|
|
30
|
+
--policy) shift; [[ $# -ge 1 ]] || { echo "missing POLICY after --policy" >&2; exit 2; }; POLICY_ARG="$1" ;;
|
|
31
|
+
--policy=*) POLICY_ARG="${1#*=}" ;;
|
|
32
|
+
--branch) BRANCH=1 ;;
|
|
23
33
|
--check) CHECK=1 ;;
|
|
24
|
-
-h|--help) sed -n '2,
|
|
34
|
+
-h|--help) sed -n '2,14p' "${BASH_SOURCE[0]}" | sed 's|^# \{0,1\}||'; exit 0 ;;
|
|
25
35
|
*) echo "unknown argument: $1" >&2; exit 2 ;;
|
|
26
36
|
esac
|
|
27
37
|
shift
|
|
@@ -414,6 +424,136 @@ if (( CHECK )); then
|
|
|
414
424
|
exit 0
|
|
415
425
|
fi
|
|
416
426
|
|
|
427
|
+
# --- Branch decision -------------------------------------------------------
|
|
428
|
+
# Read-only. Resolves commit.branch into the single decision the entry skills
|
|
429
|
+
# (sassy-work, sassy-lfg, sassy-debug) make before they start: cut a branch,
|
|
430
|
+
# reuse the one checked out, or commit where you stand.
|
|
431
|
+
#
|
|
432
|
+
# Every base_map VALUE counts as a base branch, not just the remote default.
|
|
433
|
+
# tap2's day-to-day base is `staging`, so comparing only against origin/HEAD
|
|
434
|
+
# declared staging "not a base" and let phase work commit onto it.
|
|
435
|
+
if (( BRANCH )); then
|
|
436
|
+
policy="${POLICY_ARG:-infer}"
|
|
437
|
+
case "$policy" in
|
|
438
|
+
infer|current|new) ;;
|
|
439
|
+
*) echo "unknown --policy: $policy (expected infer, current, or new)" >&2; exit 2 ;;
|
|
440
|
+
esac
|
|
441
|
+
configured="$(yflat "commit${US}branch" 2>/dev/null || true)"
|
|
442
|
+
[[ "$policy" == "infer" ]] && policy="${configured:-new}"
|
|
443
|
+
|
|
444
|
+
# base_heads prints every branch this config treats as a base: each base_map
|
|
445
|
+
# value, plus the remote default it inherits from. With no config to speak,
|
|
446
|
+
# the probed remote HEAD is the only base there is.
|
|
447
|
+
base_heads() {
|
|
448
|
+
local v out=""
|
|
449
|
+
out="$(yaml_flat "$CONFIG" 2>/dev/null | awk -F'\t' -v us="$US" \
|
|
450
|
+
'{n=split($1,a,us); if (n==3 && a[1]=="git" && a[2]=="base_map") print $2}' | sort -u || true)"
|
|
451
|
+
if [[ "$TOPOLOGY" == "repo" ]]; then
|
|
452
|
+
v="$(git -C "$ROOT" symbolic-ref --short refs/remotes/origin/HEAD 2>/dev/null | sed 's|^origin/||' || true)"
|
|
453
|
+
[[ -n "$v" ]] && out="$out"$'\n'"$v"
|
|
454
|
+
fi
|
|
455
|
+
out="$(printf '%s\n' "$out" | grep -v '^$' | sort -u || true)"
|
|
456
|
+
# Probing through gh costs a network call, so only when nothing else said.
|
|
457
|
+
[[ -n "$out" ]] || out="$(detect_base "$ROOT" 2>/dev/null || true)"
|
|
458
|
+
printf '%s\n' "$out" | grep -v '^$' || true
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
# in_base_heads BRANCH — membership without a pipe: `base_heads | grep -q`
|
|
462
|
+
# trips pipefail, because grep exits on the first match and SIGPIPEs the
|
|
463
|
+
# producer, so a branch that IS a base reported as if it were not.
|
|
464
|
+
in_base_heads() {
|
|
465
|
+
local want="$1" b
|
|
466
|
+
while IFS= read -r b; do
|
|
467
|
+
[[ "$b" == "$want" ]] && return 0
|
|
468
|
+
done < <(base_heads)
|
|
469
|
+
return 1
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
printf 'topology: %s\n' "$TOPOLOGY"
|
|
473
|
+
printf 'config: %s\n' "${CONFIG:-none}"
|
|
474
|
+
printf 'configured: %s\n' "${configured:-none}"
|
|
475
|
+
printf 'policy: %s\n' "$policy"
|
|
476
|
+
|
|
477
|
+
if [[ "$TOPOLOGY" == "none" ]]; then
|
|
478
|
+
printf 'state: no-git\naction: refuse\n'
|
|
479
|
+
exit 0
|
|
480
|
+
fi
|
|
481
|
+
|
|
482
|
+
if [[ "$TOPOLOGY" == "workspace" ]]; then
|
|
483
|
+
# The run shares one branch name across every repo, so the decision is made
|
|
484
|
+
# once, here, and applied inside each nested repo. A nested repo carrying
|
|
485
|
+
# its own commit.branch that disagrees is drift, not an override: it would
|
|
486
|
+
# give one run two branch policies.
|
|
487
|
+
while IFS= read -r dir; do
|
|
488
|
+
[[ -n "$dir" ]] || continue
|
|
489
|
+
head="$(git -C "$ROOT/$dir" branch --show-current 2>/dev/null || true)"
|
|
490
|
+
if [[ -z "$head" ]]; then
|
|
491
|
+
printf 'repo %s: head=detached state=detached\n' "$dir"
|
|
492
|
+
elif in_base_heads "$head"; then
|
|
493
|
+
printf 'repo %s: head=%s state=on-base\n' "$dir" "$head"
|
|
494
|
+
else
|
|
495
|
+
printf 'repo %s: head=%s state=on-branch\n' "$dir" "$head"
|
|
496
|
+
fi
|
|
497
|
+
own=""
|
|
498
|
+
if [[ -f "$ROOT/$dir/.sassywares/config.yaml" ]]; then
|
|
499
|
+
own="$(yflat_in "$ROOT/$dir/.sassywares/config.yaml" "commit${US}branch" 2>/dev/null || true)"
|
|
500
|
+
fi
|
|
501
|
+
[[ -n "$own" && "$own" != "$policy" ]] && printf 'drift %s: commit.branch=%s\n' "$dir" "$own"
|
|
502
|
+
done < <(nested_repos)
|
|
503
|
+
exit 0
|
|
504
|
+
fi
|
|
505
|
+
|
|
506
|
+
head="${HEAD_ARG:-$(git -C "$ROOT" branch --show-current 2>/dev/null || true)}"
|
|
507
|
+
printf 'head: %s\n' "${head:-none}"
|
|
508
|
+
|
|
509
|
+
if [[ -z "$head" ]]; then
|
|
510
|
+
state=detached
|
|
511
|
+
elif in_base_heads "$head"; then
|
|
512
|
+
state=on-base
|
|
513
|
+
else
|
|
514
|
+
state=on-branch
|
|
515
|
+
fi
|
|
516
|
+
printf 'state: %s\n' "$state"
|
|
517
|
+
|
|
518
|
+
case "$state:$policy" in
|
|
519
|
+
on-branch:*) action=reuse ;;
|
|
520
|
+
on-base:new) action=create ;;
|
|
521
|
+
on-base:current) action=commit-here ;;
|
|
522
|
+
detached:new) action=create ;;
|
|
523
|
+
*) action=refuse ;;
|
|
524
|
+
esac
|
|
525
|
+
printf 'action: %s\n' "$action"
|
|
526
|
+
|
|
527
|
+
# A new branch starts at the upstream tip, never at local HEAD: commits that
|
|
528
|
+
# were stranded on the base earlier must not ride along silently.
|
|
529
|
+
if [[ -n "$head" ]] && git -C "$ROOT" rev-parse --verify --quiet "refs/remotes/origin/$head" >/dev/null 2>&1; then
|
|
530
|
+
printf 'start-point: origin/%s\n' "$head"
|
|
531
|
+
printf 'unpublished: %s\n' "$(git -C "$ROOT" rev-list --count "origin/$head..$head" 2>/dev/null || echo 0)"
|
|
532
|
+
else
|
|
533
|
+
printf 'start-point: %s\n' "${head:-none}"
|
|
534
|
+
printf 'unpublished: unknown\n'
|
|
535
|
+
fi
|
|
536
|
+
|
|
537
|
+
if [[ "$action" == "create" ]]; then
|
|
538
|
+
# The head will be a brand-new name, so only the "*" rung can apply to it.
|
|
539
|
+
pr_base="$(yflat "git${US}base_map${US}*" 2>/dev/null || true)"
|
|
540
|
+
pr_src=config
|
|
541
|
+
else
|
|
542
|
+
pr_base="$(base_for_head "$head" 2>/dev/null || true)"
|
|
543
|
+
pr_src=config
|
|
544
|
+
fi
|
|
545
|
+
if [[ -z "$pr_base" ]]; then
|
|
546
|
+
# No config named a base. A probed value is not a decision: the skills must
|
|
547
|
+
# confirm it with the user before it becomes a PR target.
|
|
548
|
+
pr_base="$(detect_base "$ROOT" 2>/dev/null || true)"
|
|
549
|
+
pr_src=probed
|
|
550
|
+
fi
|
|
551
|
+
[[ -n "$pr_base" ]] || pr_src=unknown
|
|
552
|
+
printf 'pr-base: %s\n' "$pr_base"
|
|
553
|
+
printf 'pr-base-source: %s\n' "$pr_src"
|
|
554
|
+
exit 0
|
|
555
|
+
fi
|
|
556
|
+
|
|
417
557
|
# --- Emit a proposal -------------------------------------------------------
|
|
418
558
|
# Every key carries its meaning and its allowed values. The file is the
|
|
419
559
|
# documentation a teammate reads, so the comments are part of the output, not
|
|
@@ -532,12 +672,12 @@ esac
|
|
|
532
672
|
cat <<'EOF'
|
|
533
673
|
|
|
534
674
|
# --- Commit ---------------------------------------------------------------
|
|
535
|
-
# current: commit on the checked-out branch.
|
|
536
|
-
# new:
|
|
537
|
-
#
|
|
675
|
+
# current: commit on the checked-out branch, even on a base branch.
|
|
676
|
+
# new: never commit on a base branch — sassy-work, sassy-lfg, and sassy-debug
|
|
677
|
+
# cut a branch at the start of the run and stay on it.
|
|
538
678
|
commit:
|
|
539
679
|
EOF
|
|
540
|
-
kv branch
|
|
680
|
+
kv branch new 34 '# current | new'
|
|
541
681
|
cat <<'EOF'
|
|
542
682
|
|
|
543
683
|
# --- Pull requests --------------------------------------------------------
|
|
@@ -26,8 +26,10 @@ disable-model-invocation: true
|
|
|
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.
|
|
29
|
-
- **`git.repo` is a remote.** Dispatch a `worker`: *load the `sassy-commit` skill and follow it
|
|
30
|
-
|
|
29
|
+
- **`git.repo` is a remote.** Dispatch a `worker`: *load the `sassy-commit` skill and follow it with `branch:current`*, 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. The branch already exists: `sassy-work` or `sassy-lfg` cut it before the first change, so the docs belong on it with the phase commits. It returns the hash and subject.
|
|
30
|
+
|
|
31
|
+
**Ship never creates the branch.** If this run's head is a base branch with unpublished commits, that is a mis-run — `sassy-work` was supposed to cut before touching code. Stop and ask, naming the branch and the count; never relocate commits silently, never push to the base.
|
|
32
|
+
- **`git.repo: workspace`.** Commit code inside each affected repo on the run's shared branch — cut once at the start of the run, `branch:current` from then on — 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
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`, forwarding `base:<branch>` when the caller supplied one* (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 own `base_map` when it carries a `.sassywares/config.yaml`, else its `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
|
|
|
@@ -46,7 +48,8 @@ disable-model-invocation: true
|
|
|
46
48
|
| Delegate everything | The orchestrator runs no git, gh, or file work itself. |
|
|
47
49
|
| Conditional docs | Compound and handbook are skipped when they do not qualify. |
|
|
48
50
|
| Docs in the commit | New docs and pointer lines ship in the same change. |
|
|
49
|
-
| One branch |
|
|
51
|
+
| One branch | The branch is cut once, before the first change, by `sassy-work` or `sassy-lfg`. Ship and its docs commit land on it. |
|
|
52
|
+
| Never create the branch | Ship never cuts a branch. Finding the run's head on a base branch with unpublished commits is a stop-and-ask. |
|
|
50
53
|
| Never default | Never push directly to the repo base branch. |
|
|
51
54
|
| Snapshot after approval | In a workspace, the snapshot runs every time, immediately after the commit approval — never before it, never skipped. |
|
|
52
55
|
| One PR per run | `pr.babysit` and `pr.merge` are single-PR. A fan-out gets one babysit run per PR and no automatic merge. |
|
|
@@ -37,7 +37,7 @@ Ask which one, unless the request already made it obvious. The answer changes th
|
|
|
37
37
|
|
|
38
38
|
| Location | Path | Who gets it | Extra work |
|
|
39
39
|
|---|---|---|---|
|
|
40
|
-
| **Internal** — part of this suite | `skills/<name>/` in the sassy-skills repo | Everyone, once pushed to `main` | Name it `sassy-<name>`, run `scripts/sync.sh
|
|
40
|
+
| **Internal** — part of this suite | `skills/<name>/` in the sassy-skills repo | Everyone, once pushed to `main` | Name it `sassy-<name>`, run `scripts/sync.sh` — it bumps the patch version, tags, and publishes |
|
|
41
41
|
| **Project** — team workflow | `<repo>/.agents/skills/<name>/` | Everyone who clones that repo | Commit it; prefix the project (`tap2-…`) so it can't collide with a suite skill or a harness built-in |
|
|
42
42
|
| **User** — personal workflow | `~/.agents/skills/<name>/` | Only this person, every repo on this machine | None — but nothing gates it either; never put something a teammate needs here |
|
|
43
43
|
|
|
@@ -5,7 +5,7 @@ description: >-
|
|
|
5
5
|
hand off to a fresh session for the next phase (or close out via sassy-ship if
|
|
6
6
|
it was the last). Also runs one settled in-context brief and stops before ship.
|
|
7
7
|
The orchestrator only coordinates.
|
|
8
|
-
argument-hint: "<plan path | brief> [phase:<n>]"
|
|
8
|
+
argument-hint: "<plan path | brief> [phase:<n>] [branch:infer|current|new]"
|
|
9
9
|
disable-model-invocation: true
|
|
10
10
|
---
|
|
11
11
|
|
|
@@ -17,7 +17,7 @@ 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 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.
|
|
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, and `commit.branch` decides whether this run needs its own branch.
|
|
21
21
|
|
|
22
22
|
## Input
|
|
23
23
|
|
|
@@ -46,26 +46,25 @@ Always gate before committing. One worker fixes findings, one re-check, then pre
|
|
|
46
46
|
|
|
47
47
|
1. **Load the phase.** Read the plan and only the selected phase's section. If it is already `Done`, stop and report.
|
|
48
48
|
|
|
49
|
-
If the phase is `In progress`, an earlier run was interrupted before both gates passed. Ask one question — resume at the gate, or re-run the cycle from scratch — and take the answer. Resuming skips straight to step
|
|
50
|
-
2. **
|
|
49
|
+
If the phase is `In progress`, an earlier run was interrupted before both gates passed. Ask one question — resume at the gate, or re-run the cycle from scratch — and take the answer. Resuming skips straight to step 5 and does not rebuild the phase.
|
|
50
|
+
2. **Resolve the branch, before touching code.** Follow the `sassy-setup` skill's `references/branching.md` with this run's `branch:` token, if any. On `create`, cut the branch now — not at commit time — so no change ever exists on a base branch. On `reuse`, stay where you are, which is how a handed-off later phase or a resumed run lands on the branch the first phase cut. Carry the branch and its start point into every gate summary you present. Stop and ask when the reference says to.
|
|
51
|
+
3. **Run the cycle.** Set the phase `In progress` in the Phase tracking table, then load the `sassy-cycle` skill and follow it, with:
|
|
51
52
|
- work brief = the phase's goal, work items, and "Done when" criteria
|
|
52
53
|
- intent reference = the plan path and phase
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
- **Code approved and every QA row passes** → go to step
|
|
54
|
+
4. **Evaluate the phase.** On `Status: done` with `Validator: Conforms` and no critical review findings, note that the code is ready but leave the phase `In progress` — both gates must pass before it closes. On `Status: blocked`, set it `Blocked` in the Phase tracking table and stop; a blocked phase is a real outcome and needs no approval.
|
|
55
|
+
5. **Present the phase gate.** Report the phase, what changed, findings raised/resolved, the validator grade, and the reviewer summary. Then give the QA checklist verbatim: each row's ID, check, how to run it, and what passing looks like, with unreported rows `Pending`. Ask for both decisions — code approval and the QA result per row. Wait.
|
|
56
|
+
6. **Resolve the gate.** Three outcomes, nothing in between:
|
|
57
|
+
- **Code approved and every QA row passes** → go to step 7.
|
|
57
58
|
- **Code approved, QA still pending** → record that code is approved but leave the phase `In progress`, tick nothing, commit nothing, hand off nothing, and report that the phase is waiting on manual QA plus the command that resumes it.
|
|
58
59
|
- **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
|
-
|
|
60
|
+
7. **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
61
|
|
|
61
|
-
- **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
|
-
- **`workspace`** — dispatch a `worker` per affected repo to commit that repo's code on the run's shared branch
|
|
62
|
+
- **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. The branch already exists: step 2 cut it, so committing on it is the point of `branch:current` here.
|
|
63
|
+
- **`workspace`** — dispatch a `worker` per affected repo to commit that repo's code on the run's shared branch, 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
64
|
- **`none`** — nothing is persisted. Report that the phase's work is uncommitted and stop; never hand off on top of it.
|
|
64
65
|
|
|
65
|
-
Work commits on the current branch. `sassy-ship` creates the shipping branch at close-out when the checked-out branch is the repo base.
|
|
66
|
-
|
|
67
66
|
Do not hand off or close out until the commit or snapshot succeeds.
|
|
68
|
-
|
|
67
|
+
8. **On commit success:**
|
|
69
68
|
- **A next phase exists** → hand off so it starts in a fresh session:
|
|
70
69
|
|
|
71
70
|
```text
|
|
@@ -82,16 +81,18 @@ Always gate before committing. One worker fixes findings, one re-check, then pre
|
|
|
82
81
|
|
|
83
82
|
When the argument is not a plan path, it is one settled brief:
|
|
84
83
|
|
|
85
|
-
1.
|
|
86
|
-
2.
|
|
87
|
-
3.
|
|
88
|
-
4.
|
|
84
|
+
1. Resolve the branch exactly as the plan path does — `references/branching.md`, this run's `branch:` token — before the cycle runs.
|
|
85
|
+
2. Run one `sassy-cycle` with the brief as the work brief and its decisions / done-when as the intent reference. Do not split it into multiple cycles.
|
|
86
|
+
3. Present the code approval gate with no QA checklist, and wait.
|
|
87
|
+
4. On code approval, persist the brief's changes the same way the plan path does: a `sassy-commit` delegation with `branch:current` in a remote repo, or the workspace snapshot in a workspace.
|
|
88
|
+
5. Stop and report. Shipping is a separate step (`sassy-ship`), or `sassy-lfg` would have taken it all the way. Never create or update a plan file.
|
|
89
89
|
|
|
90
90
|
## Hard rules
|
|
91
91
|
|
|
92
92
|
| Rule | Requirement |
|
|
93
93
|
|------|-------------|
|
|
94
94
|
| Orchestrator only | Delegate all implementation and quality work to `sassy-cycle`. |
|
|
95
|
+
| Branch before code | Resolve the branch per `references/branching.md` before the cycle runs, so no change ever lands on a base branch. |
|
|
95
96
|
| One phase per run | Do exactly one phase. Never continue into the next phase in this session. |
|
|
96
97
|
| Gate before commit and handoff | Do not commit, hand off, or close out until the user approves the code and the phase QA rows pass. |
|
|
97
98
|
| Commit per phase | Commit the phase, its QA results, and the plan update via `sassy-commit` before handoff or close-out. |
|