vinta-ai-workflows 0.1.7 → 0.2.0-alpha2
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/CHANGELOG.md +204 -0
- package/README.md +3 -1
- package/package.json +1 -1
- package/schemas/vinta-ai-workflows-config.v1.schema.json +106 -1
- package/skills/vinta-bootstrap-ai-tools/SKILL.md +47 -4
- package/skills/vinta-derive-skills/SKILL.md +61 -2
- package/skills/vinta-derive-skills/resources/amend-plan-template.md +4 -1
- package/skills/vinta-derive-skills/resources/dependency-license-block.md +60 -0
- package/skills/vinta-derive-skills/resources/foundation-skills/prepare-worktree/SKILL.md +327 -0
- package/skills/vinta-derive-skills/resources/implement-plan-template-modular-substitutions.md +198 -0
- package/skills/vinta-derive-skills/resources/implement-plan-template.md +76 -35
- package/skills/vinta-install-ai-tools-setup/resources/setup-ai-tools.mjs +13 -3
- package/skills/vinta-write-agents-md/SKILL.md +25 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# Modular-commits substitutions for `implement-plan-template.md`
|
|
2
|
+
|
|
3
|
+
This file holds the substitution bodies for the `{{COMMIT_STRATEGY_*}}` placeholder family when `policies.commit_strategy = modular-commits`. The `stacked-branches` substitutions are inline in the parent placeholder table in [SKILL.md](../SKILL.md). The `ask` variant emits **both** the stacked and the modular bodies gated by inline runtime markers — see [Phase 4 ask-mode substitutions](#ask-mode-substitutions) at the bottom.
|
|
4
|
+
|
|
5
|
+
Each section below is delimited by `<!-- substitution: {{PLACEHOLDER}} -->` markers so derive-skills can extract the block by name. The block's content is everything between the open and close markers, **excluding** the markers themselves.
|
|
6
|
+
|
|
7
|
+
## `{{BRANCH_NAMING_BLOCK}}` (modular)
|
|
8
|
+
|
|
9
|
+
<!-- substitution-begin: BRANCH_NAMING_BLOCK -->
|
|
10
|
+
Branch naming: `plan/{plan-id-kebab}` (one branch for the whole plan — no per-phase suffix).
|
|
11
|
+
|
|
12
|
+
**First executed phase** (branches from `{{DEFAULT_BRANCH}}`):
|
|
13
|
+
```bash
|
|
14
|
+
git checkout {{DEFAULT_BRANCH}}
|
|
15
|
+
git pull --ff-only
|
|
16
|
+
git checkout -b plan/{plan-id-kebab}
|
|
17
|
+
# subagent's atomic unit commits land on this branch
|
|
18
|
+
git push -u origin plan/{plan-id-kebab}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**Subsequent phases** (stay on the same plan branch — no new branch):
|
|
22
|
+
```bash
|
|
23
|
+
git checkout plan/{plan-id-kebab}
|
|
24
|
+
# subagent's atomic unit commits land on this branch
|
|
25
|
+
git push origin plan/{plan-id-kebab}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The branch carries every phase's commits in plan order. Reviewers read the commit log top-to-bottom as a table of contents of the implementation.
|
|
29
|
+
<!-- substitution-end: BRANCH_NAMING_BLOCK -->
|
|
30
|
+
|
|
31
|
+
## `{{PER_PHASE_COMMIT_BLOCK}}` (modular)
|
|
32
|
+
|
|
33
|
+
<!-- substitution-begin: PER_PHASE_COMMIT_BLOCK -->
|
|
34
|
+
7. **Plan commit units before staging.** List the logical units this phase produces (e.g. `3 services + 1 use case update + 1 init export`). Each unit = **one** commit. Tests for that unit travel **in the same commit** as the code they test — never a separate commit.
|
|
35
|
+
8. For each unit, in order:
|
|
36
|
+
a. Stage exactly that unit's files: `git add <explicit paths>` (NEVER `git add -A` — {{ANTI_GIT_ADD_ALL_REASON}}). Tests for the unit go in the same `git add`.
|
|
37
|
+
b. Commit with the repo's commit_style — see the **Commit Boundaries** + **Commit Message Format** tables below.
|
|
38
|
+
c. Don't bundle two units in one commit. If the commit message needs the word "and" to cover the diff, **split** — see **Red Flags** below.
|
|
39
|
+
9. {{COAUTHOR_INSTRUCTION_LINE}}
|
|
40
|
+
10. {{PUSH_INSTRUCTION_LINE}} — push all unit commits at once at end of phase.
|
|
41
|
+
|
|
42
|
+
### Modular-commits discipline (load-bearing — re-read every phase)
|
|
43
|
+
|
|
44
|
+
Commit each logical unit independently as you complete it. One service = one commit. One use-case update = one commit. Tests travel with the code they test.
|
|
45
|
+
|
|
46
|
+
The commit list becomes a **table of contents** for reviewers — they can read the commit titles before touching any code and already understand the shape and sequence of the implementation.
|
|
47
|
+
|
|
48
|
+
#### Commit Boundaries
|
|
49
|
+
|
|
50
|
+
| Unit | When to commit | Example commit message |
|
|
51
|
+
|------|---------------|------------------------|
|
|
52
|
+
| New service | Service + its unit tests complete | {{MODULAR_EXAMPLE_NEW_SERVICE}} |
|
|
53
|
+
| Use case update | Use case wires in new services, with integration tests | {{MODULAR_EXAMPLE_USE_CASE}} |
|
|
54
|
+
| Init / exports | After exposing new symbols | {{MODULAR_EXAMPLE_INIT}} |
|
|
55
|
+
| Serializer field | Field + validation + tests | {{MODULAR_EXAMPLE_SERIALIZER}} |
|
|
56
|
+
| Refactor / cleanup | Standalone cleanup pass only | {{MODULAR_EXAMPLE_REFACTOR}} |
|
|
57
|
+
| Bug fix | Fix + regression test | {{MODULAR_EXAMPLE_BUGFIX}} |
|
|
58
|
+
|
|
59
|
+
Tests for a unit belong **in the same commit** as that unit. Never commit tests separately.
|
|
60
|
+
|
|
61
|
+
#### Commit Message Format
|
|
62
|
+
|
|
63
|
+
{{MODULAR_COMMIT_MESSAGE_FORMAT_BLOCK}}
|
|
64
|
+
|
|
65
|
+
#### Red Flags — Split the Commit
|
|
66
|
+
|
|
67
|
+
- Commit message needs "and" to cover everything in it.
|
|
68
|
+
- You are staging files from two different units.
|
|
69
|
+
- A reviewer cannot understand the diff without seeing the other commits first.
|
|
70
|
+
|
|
71
|
+
#### Common Rationalizations
|
|
72
|
+
|
|
73
|
+
| Rationalization | Reality |
|
|
74
|
+
|----------------|---------|
|
|
75
|
+
| "I'll commit everything at the end" | Reviewers read commit-by-commit; one giant diff hides intent. |
|
|
76
|
+
| "The user can squash later" | Squashing destroys the logical history this discipline exists to preserve. |
|
|
77
|
+
| "It's faster to do one commit" | Planning units takes 2 minutes; reviewing a 2000-line blob takes much longer. |
|
|
78
|
+
| "The changes are all related" | Related ≠ same unit. Services that depend on each other still get separate commits. |
|
|
79
|
+
<!-- substitution-end: PER_PHASE_COMMIT_BLOCK -->
|
|
80
|
+
|
|
81
|
+
### Conditional example messages (driven by `policies.commit_style`)
|
|
82
|
+
|
|
83
|
+
derive-skills resolves the six `{{MODULAR_EXAMPLE_*}}` placeholders + the `{{MODULAR_COMMIT_MESSAGE_FORMAT_BLOCK}}` from this table when rendering `{{PER_PHASE_COMMIT_BLOCK}}`:
|
|
84
|
+
|
|
85
|
+
| Placeholder | `commit_style = conventional` | `commit_style = imperative` | `commit_style = other` |
|
|
86
|
+
|---|---|---|---|
|
|
87
|
+
| `{{MODULAR_EXAMPLE_NEW_SERVICE}}` | `` `feat(record-copy): add service to copy files between records` `` | `Created service to copy files between records` | both — `` `feat(record-copy): add service to copy files between records` `` (Conventional) or `Created service to copy files between records` (imperative) |
|
|
88
|
+
| `{{MODULAR_EXAMPLE_USE_CASE}}` | `` `feat(record-copy): wire optional fields into record copy use case` `` | `Updated record copy use case to call optional fields services` | both |
|
|
89
|
+
| `{{MODULAR_EXAMPLE_INIT}}` | `` `chore(record-copy): expose new services in init file` `` | `Updated init file to expose new copy services` | both |
|
|
90
|
+
| `{{MODULAR_EXAMPLE_SERIALIZER}}` | `` `feat(record-copy): add copy flag for tags to serializer` `` | `Added copy flag for tags to serializer` | both |
|
|
91
|
+
| `{{MODULAR_EXAMPLE_REFACTOR}}` | `` `refactor(record-copy): apply shared batch size to copy services` `` | `Applied batch size constant to all copy services` | both |
|
|
92
|
+
| `{{MODULAR_EXAMPLE_BUGFIX}}` | `` `fix(reports): include archived rows in summary section` `` | `Fixed missing archived rows in summary section` | both |
|
|
93
|
+
| `{{MODULAR_COMMIT_MESSAGE_FORMAT_BLOCK}}` | the **Conventional Commits** block below | the **Verb-first imperative** block below | both blocks under `### Option A` / `### Option B` headings + a `Pick one style and use it consistently within the plan branch` lead-in |
|
|
94
|
+
|
|
95
|
+
#### Conventional Commits block (used when `commit_style = conventional` or `other`)
|
|
96
|
+
|
|
97
|
+
Spec: [conventionalcommits.org](https://www.conventionalcommits.org/en/v1.0.0/)
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
<type>(<scope>): <description>
|
|
101
|
+
|
|
102
|
+
[optional body: non-obvious why, constraints, or side effects — omit if obvious]
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
| Type | Use for |
|
|
106
|
+
|------|---------|
|
|
107
|
+
| `feat` | New feature or capability |
|
|
108
|
+
| `fix` | Bug fix |
|
|
109
|
+
| `refactor` | Code restructuring without behavior change |
|
|
110
|
+
| `chore` | Maintenance — init files, exports, config |
|
|
111
|
+
| `docs` | Documentation only |
|
|
112
|
+
|
|
113
|
+
**Scope:** the feature area or module (e.g. `reports`, `record-copy`). Optional but recommended.
|
|
114
|
+
|
|
115
|
+
**Breaking changes:** append `!` before the colon — `feat(auth)!: replace session tokens`.
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
feat(record-copy): add service to copy files between records
|
|
119
|
+
feat(record-copy): add service to copy tags between records
|
|
120
|
+
feat(record-copy): wire optional fields into record copy use case
|
|
121
|
+
chore(record-copy): expose new services in init file
|
|
122
|
+
refactor(record-copy): apply shared batch size to copy services
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
#### Verb-first imperative block (used when `commit_style = imperative` or `other`)
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
<Verb> <what was done> [for/to/between <context>]
|
|
129
|
+
|
|
130
|
+
[optional body: non-obvious why, constraints, or side effects — omit if obvious]
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**Preferred verbs:** Created, Updated, Added, Removed, Fixed, Refactored.
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
Created service to copy files between records
|
|
137
|
+
Created service to copy tags between records
|
|
138
|
+
Updated record copy use case to call optional fields services
|
|
139
|
+
Updated init file to expose new copy services
|
|
140
|
+
Applied batch size constant to all copy services
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
#### Bad (both styles)
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
WIP
|
|
147
|
+
add stuff
|
|
148
|
+
Implement full record copy feature ← too broad, should be split
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## `{{PR_OPEN_TIMING_BLOCK}}` (modular)
|
|
152
|
+
|
|
153
|
+
<!-- substitution-begin: PR_OPEN_TIMING_BLOCK -->
|
|
154
|
+
**PR opens once — after Phase 1 passes review.** Subsequent phases push their atomic unit commits to the same plan branch (`plan/{plan-id-kebab}`); the orchestrator re-runs [open-pr.sh](../foundation-skills/open-pr-from-context/scripts/open-pr.sh) against the same plan-level prs-context file at `.vinta-ai-workflows/prs-context/{feature-kebab}/plan.md`. The script is idempotent for already-open PRs — it updates the body, appends new inline comments, and posts an `Phase {N} complete — pushed M commits` PR comment.
|
|
155
|
+
|
|
156
|
+
<!-- substitution-end: PR_OPEN_TIMING_BLOCK -->
|
|
157
|
+
|
|
158
|
+
## `{{COMMIT_STRATEGY_CHECKLIST_BLOCK}}` (modular)
|
|
159
|
+
|
|
160
|
+
<!-- substitution-begin: COMMIT_STRATEGY_CHECKLIST_BLOCK -->
|
|
161
|
+
- [ ] Commit units listed upfront before any staging.
|
|
162
|
+
- [ ] Each commit covers exactly one logical unit (no "and" in commit messages).
|
|
163
|
+
- [ ] Tests landed in the same commit as the code they cover (never a separate test-only commit).
|
|
164
|
+
- [ ] All unit commits pushed to `plan/{plan-id-kebab}` at end of phase.
|
|
165
|
+
<!-- substitution-end: COMMIT_STRATEGY_CHECKLIST_BLOCK -->
|
|
166
|
+
|
|
167
|
+
## Other modular substitutions (single-line — listed for completeness)
|
|
168
|
+
|
|
169
|
+
| Placeholder | Modular substitution |
|
|
170
|
+
|---|---|
|
|
171
|
+
| `{{BRANCH_PUSH_HEADING}}` | `Push to plan branch` |
|
|
172
|
+
| `{{BRANCH_NAMING_PATTERN_SUMMARY}}` | ``plan branch (one branch for whole plan: `plan/{plan-id-kebab}`)`` |
|
|
173
|
+
| `{{PRS_CONTEXT_FILE_PATH_DESCRIPTION}}` | `` a single `.vinta-ai-workflows/prs-context/{feature-kebab}/plan.md` file (one PR per plan, not per phase) `` |
|
|
174
|
+
| `{{TRACKING_BRANCH_FIELD}}` | `top-level \`plan_branch:\` field` |
|
|
175
|
+
| `{{TRACKING_PHASE_BRANCH_FIELD}}` | empty string (no per-phase branch under modular) |
|
|
176
|
+
| `{{FINAL_REPORT_BRANCH_SUMMARY}}` | ``single plan branch `plan/{plan-id-kebab}` with commit log organized by phase`` |
|
|
177
|
+
| `{{BRANCH_CHECKLIST_LINE}}` | `Plan branch updated with phase commits; pushed.` |
|
|
178
|
+
| `{{COMMIT_STRATEGY_STEP0_QUESTION}}` | empty string (modular is set as the project default; nothing to ask) |
|
|
179
|
+
| `{{COMMIT_STRATEGY_STEP0_TRAILER}}` | empty string |
|
|
180
|
+
| `{{COMMIT_STRATEGY_CONFIRM_NOTE}}` | empty string |
|
|
181
|
+
|
|
182
|
+
## Ask-mode substitutions
|
|
183
|
+
|
|
184
|
+
When `policies.commit_strategy = ask`, derive-skills renders **both** the stacked-branches and the modular-commits bodies for each multi-line placeholder, gated by inline runtime markers the agent reads at execution time:
|
|
185
|
+
|
|
186
|
+
```markdown
|
|
187
|
+
**If `run_options.commit_strategy_resolved = "modular-commits"`:**
|
|
188
|
+
|
|
189
|
+
<modular substitution body here>
|
|
190
|
+
|
|
191
|
+
**Else (`run_options.commit_strategy_resolved = "stacked-branches"`):**
|
|
192
|
+
|
|
193
|
+
<stacked substitution body here>
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Single-line placeholders under ask use the same gating pattern but inline (`If modular: <a> · Else (stacked): <b>`).
|
|
197
|
+
|
|
198
|
+
For the Step 0 question itself (`{{COMMIT_STRATEGY_STEP0_QUESTION}}`), the ask substitution is the AskUserQuestion block documented in the parent placeholder table in [SKILL.md](../SKILL.md) — same content used to populate the resolved value.
|
|
@@ -34,17 +34,44 @@ Parse once, reuse for every phase:
|
|
|
34
34
|
- **Phased Rollout** section — parse into phase records: `{ id, title, goal, body, spec_use_case, suggested_model_tier, reusable_skills, has_e2e, acceptance, is_cross_repo, is_flag_removal }`.
|
|
35
35
|
- **Risk & Rollout Notes**, **Open Questions**, **Touch List** sections — keep available; include in phase prompts only when relevant.
|
|
36
36
|
3. **Classify each phase**: `is_cross_repo`, `is_flag_removal` — orchestrator does NOT auto-execute these.
|
|
37
|
-
4. **Ask the user
|
|
37
|
+
4. **Ask the user three opt-in questions** via `AskUserQuestion`. Defaults are project-specific (see below); record every answer in tracking under `run_options`:
|
|
38
38
|
|
|
39
39
|
a. **Pause between phases?** *"Do you want me to pause and wait for confirmation after each phase, before starting the next one? Lets you review the diff / branch / PR / tracking summary before moving on."* Options: `Auto-flow (default) — keep going phase to phase`, `Pause between phases — wait for go after each one`.
|
|
40
40
|
|
|
41
41
|
b. **Draft inline review comments per phase?** *"On top of the standard PR description, do you want me to scan each phase's diff and add 3–10 inline comments calling out non-obvious decisions (subtle invariants, feature-flag short-circuits, cross-phase coupling, upstream-contract naming)? Off by default — say yes when reviewers will appreciate annotated diffs."* Options: `Yes — include inline comments`, `No — PR description only`.
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
c. **Run phases in a worktree?** *"Do you want every phase's subagent to work inside an isolated git worktree (its own runnable copy of the app with its own dev + test DB, env files, docker-compose project name) instead of sharing your main checkout? Lets you keep using `{{DEFAULT_BRANCH}}` for unrelated work while this plan runs; survives parallel plans on the same repo without DB / port / docker collisions. Costs one extra checkout's worth of disk + the time it takes [prepare-worktree](../prepare-worktree/SKILL.md) to provision it."* Options: `No — run in current checkout`, `Yes — provision one shared worktree for the whole plan`. Default = value of `run_options.implement-plan.use_worktree` in `.vinta-ai-workflows.yaml` (`No` when unset).
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
When `Yes`: **the same worktree is used for every executable phase** — all phase branches stack inside it. The skill never provisions a second worktree mid-plan. If the user wants per-phase worktrees, that's a different workflow (split the plan into independent plans).
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
Skip this question entirely when `foundation_skills.prepare-worktree` is `disabled` in `.vinta-ai-workflows.yaml`: record `run_options.use_worktree = false`; surface a one-line note that worktree isolation is available if the team opts in via [vinta-sync-ai-tools](../../skills/vinta-sync-ai-tools/SKILL.md).
|
|
48
|
+
|
|
49
|
+
PR opening itself is **not** asked here — it's governed by the project's PR creation policy captured at bootstrap (see `{{PR_POLICY_BLOCK}}` above). When that policy = "agents create PRs", the [Open PR via context file](#1f-open-pr-via-context-file) step always opens the PR via [open-pr.sh](../foundation-skills/open-pr-from-context/scripts/open-pr.sh) regardless of the comment opt-in.{{COMMIT_STRATEGY_STEP0_QUESTION}}
|
|
50
|
+
|
|
51
|
+
5. **Confirm with user before starting.** Show plan path, phase list (id + title + tier + cross-repo/flag-removal flags + e2e flag), phases this skill will execute vs defer, {{BRANCH_NAMING_PATTERN_SUMMARY}}, captured `run_options.pause_between_phases` + `run_options.generate_inline_comments` + `run_options.use_worktree`{{COMMIT_STRATEGY_CONFIRM_NOTE}}{{PR_REMINDER_LINE}}.
|
|
52
|
+
|
|
53
|
+
Wait for "go". After that, the per-phase pause behavior follows `run_options.pause_between_phases`. Inline-comment drafting follows `run_options.generate_inline_comments`. Worktree isolation follows `run_options.use_worktree`.{{COMMIT_STRATEGY_STEP0_TRAILER}}
|
|
54
|
+
|
|
55
|
+
## Step 0.5 — Provision worktree (when `run_options.use_worktree = true`)
|
|
56
|
+
|
|
57
|
+
Skip when `run_options.use_worktree = false`; jump to Step 1.
|
|
58
|
+
|
|
59
|
+
When true, invoke [prepare-worktree](../prepare-worktree/SKILL.md) **once**, before any phase runs:
|
|
60
|
+
|
|
61
|
+
1. **Inputs.** Plan path (so prepare-worktree can read it for deps / migrations / env / compose churn — see prepare-worktree's **Plan inspection** step), suggested worktree name = `plan-{plan-id-kebab}`, plan-driven mode.
|
|
62
|
+
2. **Pre-run sanity.** Confirm no existing worktree at the target path (`git worktree list | grep <name>` — refuse if collision). Confirm `git -C <main> status` of the main checkout (warn if dirty; defer to prepare-worktree's **Sanity checks** step for the call).
|
|
63
|
+
3. **Run prepare-worktree.** Pass the plan file + worktree name. The skill returns:
|
|
64
|
+
- `worktree_path` — absolute path the phase subagents will `cd` into.
|
|
65
|
+
- `worktree_branch` — the base branch prepare-worktree created; phase branches stack off this.
|
|
66
|
+
- `worktree_summary` — `.vinta-ai-workflows/worktrees/<name>.yaml` (read by teardown).
|
|
67
|
+
4. **Persist to tracking.** Write `run_options.worktree_path`, `run_options.worktree_branch`, `run_options.worktree_summary` into `{{PLAN_DIR}}/TRACKING_{plan-id}.md` (Step 1g schema gains these three fields). All later phases read them — never re-provision mid-plan.
|
|
68
|
+
5. **Report to user.** Quote the prepare-worktree summary back: which dirs symlinked vs copied vs forked; which DB(s) forked + their names; compose project name; teardown command. Hold here until the user confirms (`AskUserQuestion`: `Looks good — start phase 1`, `Stop — let me adjust`).
|
|
69
|
+
|
|
70
|
+
**Worktree topology rule.** When `use_worktree = true`, every phase branches off the previous phase **inside the worktree**, not off `{{DEFAULT_BRANCH}}` in the main checkout. The first phase branches off `<worktree_branch>` (which prepare-worktree based on `origin/{{DEFAULT_BRANCH}}`); subsequent phases stack as usual. All `git` calls in the **{{BRANCH_PUSH_HEADING}}** step use `git -C <worktree_path>` (or `cd` into the worktree first). All inner / outer test commands in the [Prepare agent prompt](#1a-prepare-agent-prompt-token-efficient) step's working-instructions block run inside the worktree so they hit the forked DB / env / compose stack.
|
|
71
|
+
|
|
72
|
+
Failure modes:
|
|
73
|
+
- **prepare-worktree returns an error** (disk full, branch exists, DB clone failed) → surface to the user; do NOT fall back to "just run in the main checkout" silently — that defeats the opt-in. Ask: `Retry`, `Run in main checkout instead (flip use_worktree to false)`, `Stop`.
|
|
74
|
+
- **User cancels at the confirmation gate** → tear the worktree down (run the teardown command from prepare-worktree's report) before exiting, so the next run starts clean.
|
|
48
75
|
|
|
49
76
|
## Step 1 — Per-phase loop
|
|
50
77
|
|
|
@@ -60,9 +87,20 @@ You are implementing {phase.id}: {phase.title} of plan {plan.id}.
|
|
|
60
87
|
## Repo
|
|
61
88
|
{{PROJECT_NAME}} ({{STACK_SUMMARY}}).
|
|
62
89
|
|
|
90
|
+
{If run_options.use_worktree = true:}
|
|
91
|
+
## Worktree
|
|
92
|
+
Work entirely inside this worktree: `<run_options.worktree_path>`.
|
|
93
|
+
`cd` into it before any command. Every `git`, every lint / test / build / migrate
|
|
94
|
+
call runs there. Do NOT touch the main checkout — its DB, env, and compose stack
|
|
95
|
+
are intentionally separated. See `<run_options.worktree_path>/WORKTREE.md` for
|
|
96
|
+
what's forked vs shared (deps, dev DB, test DB, compose project name, env file).
|
|
97
|
+
Branch base for this phase: `<phase-specific base>` — orchestrator already
|
|
98
|
+
created your phase branch there; commit straight to it.
|
|
99
|
+
|
|
63
100
|
## Read first
|
|
64
101
|
1. AGENTS.md — repo conventions.
|
|
65
102
|
2. {{PLAN_DIR}}/{plan-filename}, the **Goals + Non-goals**, **Guiding Decisions**, **Data Model Changes** sections and YOUR phase body inside **Phased Rollout**.
|
|
103
|
+
{If run_options.use_worktree = true:} 3. `WORKTREE.md` at the worktree root — fork map (which dirs symlink to main vs are independent copies).
|
|
66
104
|
|
|
67
105
|
## Plan-level decisions (from Goals + Non-goals + Guiding Decisions)
|
|
68
106
|
{Goals + Non-goals verbatim}
|
|
@@ -82,6 +120,8 @@ You are implementing {phase.id}: {phase.title} of plan {plan.id}.
|
|
|
82
120
|
|
|
83
121
|
Project skills available: {{PROJECT_SKILLS_LIST}}
|
|
84
122
|
|
|
123
|
+
{{DEPENDENCY_LICENSE_BLOCK}}
|
|
124
|
+
|
|
85
125
|
## Working instructions
|
|
86
126
|
1. Read existing code paths your changes touch — do not write before reading.
|
|
87
127
|
2. Implement using Read/Edit/Write. Match existing patterns.
|
|
@@ -95,10 +135,7 @@ Project skills available: {{PROJECT_SKILLS_LIST}}
|
|
|
95
135
|
b. **Full test suite:** `{{TEST_CMD}}`.
|
|
96
136
|
{{E2E_OUTER_GATE_LINE}}
|
|
97
137
|
6. Outer gate fails → return step 2 (fix regression), re-run inner loop, then 5a/5b/5c. **Never** commit, push, or proceed while any gate is red.
|
|
98
|
-
|
|
99
|
-
8. Commit with the repo's style — look at `git log -10 --oneline` first. {{COMMIT_STYLE_LINE}}.
|
|
100
|
-
9. {{COAUTHOR_INSTRUCTION_LINE}}
|
|
101
|
-
10. {{PUSH_INSTRUCTION_LINE}}
|
|
138
|
+
{{PER_PHASE_COMMIT_BLOCK}}
|
|
102
139
|
|
|
103
140
|
## Required output (single final report)
|
|
104
141
|
- Status: SUCCESS or FAILURE (and why).
|
|
@@ -156,6 +193,12 @@ Three layers, all required, in order. The orchestrator never edits — every iss
|
|
|
156
193
|
3. **Verify the outer gate** ran + green. Look in the report for explicit confirmation that `{{BUILD_CMD}}` AND `{{TEST_CMD}}` were executed + passed{{E2E_LAYER1_NOTE}}. Vague confirmation → **re-run yourself**.
|
|
157
194
|
4. **Scope creep**: file touched outside expected surface area? Unrelated formatting churn? Surface it.
|
|
158
195
|
5. **No-secrets scan**: `git diff` for `password|secret|token|api_key|AKIA|BEGIN [A-Z]+ KEY`.
|
|
196
|
+
6. **Stray main-checkout writes (only when `run_options.use_worktree = true`)**: a subagent is told to work inside the worktree, but a buggy agent (often a smaller model) can resolve an absolute path back to the **main checkout** and silently edit files there (worktrees have independent working trees, so those edits never reach the phase commit — they sit as uncommitted thrash in the main checkout, and the "missing" edits read as a silent fixer/implementer failure). After **every** implementer **and** fixer subagent returns, run `git -C <main-checkout-path> status --short | grep -vE '^\?\?'` (tracked modifications only). Any output is a BLOCKER for this phase:
|
|
197
|
+
- Diff the stray files (`git -C <main-checkout-path> diff -- <path>`) to recover intent.
|
|
198
|
+
- If the edit belongs in the worktree, re-dispatch the fixer/implementer with an explicit instruction to write to the worktree path (the change is missing from the phase commit until it does).
|
|
199
|
+
- Once recovered (or confirmed superseded by the correctly-committed worktree version), discard the stray edits with `git -C <main-checkout-path> restore -- <path>` so the main checkout returns clean. Never leave the main checkout dirty between phases — a later phase's Layer 1 can't tell new thrash from old.
|
|
200
|
+
`<main-checkout-path>` is the repo root the skill was invoked from (NOT `run_options.worktree_path`). Skip this check entirely when `run_options.use_worktree = false`.
|
|
201
|
+
{{DEPENDENCY_LICENSE_LAYER1_CHECK}}
|
|
159
202
|
{{COAUTHOR_LAYER1_CHECK}}
|
|
160
203
|
|
|
161
204
|
#### Layer 2 — Plan compliance walkthrough
|
|
@@ -191,29 +234,17 @@ Reviewer finds nothing on a >300-LoC multi-file phase → suspicious. Read once
|
|
|
191
234
|
|
|
192
235
|
### 1e. {{BRANCH_PUSH_HEADING}}
|
|
193
236
|
|
|
194
|
-
|
|
237
|
+
**Worktree path rule (applies to whichever commit strategy `{{BRANCH_NAMING_BLOCK}}` renders below).** When `run_options.use_worktree = false`, every `git` command runs in the main checkout exactly as written. When `true`, run every `git` command below inside the worktree — prefix with `git -C <run_options.worktree_path>` (or `cd` into it first), and the first executed phase branches off `<run_options.worktree_branch>` instead of `{{DEFAULT_BRANCH}}`. Branches / commits stack inside the worktree; nothing touches the main checkout's working tree.
|
|
195
238
|
|
|
196
|
-
|
|
197
|
-
```bash
|
|
198
|
-
git checkout {{DEFAULT_BRANCH}}
|
|
199
|
-
git pull --ff-only
|
|
200
|
-
git checkout -b plan/{plan-id-kebab}/phase-{phase.id}
|
|
201
|
-
# subagent's commits land on this branch
|
|
202
|
-
git push -u origin plan/{plan-id-kebab}/phase-{phase.id}
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
**Subsequent phases** (stacked on the previous phase's branch):
|
|
206
|
-
```bash
|
|
207
|
-
git checkout plan/{plan-id-kebab}/phase-{prev.id}
|
|
208
|
-
git checkout -b plan/{plan-id-kebab}/phase-{phase.id}
|
|
209
|
-
git push -u origin plan/{plan-id-kebab}/phase-{phase.id}
|
|
210
|
-
```
|
|
239
|
+
{{BRANCH_NAMING_BLOCK}}
|
|
211
240
|
|
|
212
241
|
PR opening lives in the [Open PR via context file](#1f-open-pr-via-context-file) step below (single flow — context file + `open-pr.sh`). Subagents never open PRs themselves; the orchestrator does, after review passes.
|
|
213
242
|
|
|
214
243
|
### 1f. Open PR via context file
|
|
215
244
|
|
|
216
|
-
This is the **only** PR-creation path. PRs always go through
|
|
245
|
+
This is the **only** PR-creation path. PRs always go through {{PRS_CONTEXT_FILE_PATH_DESCRIPTION}} + the bundled [open-pr.sh](../foundation-skills/open-pr-from-context/scripts/open-pr.sh) script — even when inline comments are not requested. The file is the durable record; the script is the publisher.
|
|
246
|
+
|
|
247
|
+
{{PR_OPEN_TIMING_BLOCK}}
|
|
217
248
|
|
|
218
249
|
Two project-level signals decide the actual behavior:
|
|
219
250
|
|
|
@@ -273,7 +304,7 @@ Two project-level signals decide the actual behavior:
|
|
|
273
304
|
|
|
274
305
|
Tracking lives at `{{PLAN_DIR}}/TRACKING_{plan-id}.md`. Commit on the **current** phase's branch — deletion in Step 3.
|
|
275
306
|
|
|
276
|
-
Schema: feature-name, plan path, started/last-updated dates, optional feature-flag info, completed-phases (with status, model,
|
|
307
|
+
Schema: feature-name, plan path, started/last-updated dates, optional feature-flag info, **run options** (`pause_between_phases`, `generate_inline_comments`, `use_worktree`, `worktree_path`, `worktree_branch`, `worktree_summary` — last three only when `use_worktree = true`), {{TRACKING_BRANCH_FIELD}}, completed-phases (with status, model{{TRACKING_PHASE_BRANCH_FIELD}}, e2e+screenshots if any, 5–15 line summary), current phase, remaining phases, deferred phases.
|
|
277
308
|
|
|
278
309
|
The orchestrator writes this from the git diff + the agent's summary — not from the agent's narration.
|
|
279
310
|
|
|
@@ -314,17 +345,21 @@ What this skill does instead:
|
|
|
314
345
|
|
|
315
346
|
User invokes the skill against a partially-done plan:
|
|
316
347
|
|
|
317
|
-
1. Read `{{PLAN_DIR}}/TRACKING_{plan-id}.md` if present.
|
|
318
|
-
2.
|
|
319
|
-
|
|
320
|
-
|
|
348
|
+
1. Read `{{PLAN_DIR}}/TRACKING_{plan-id}.md` if present. Extract `run_options.*` — including `worktree_path` / `worktree_branch` / `worktree_summary` when set. Never re-prompt the Step 0 opt-in questions on resume; the original answers stick.
|
|
349
|
+
2. **Worktree resume.** When `run_options.use_worktree = true`:
|
|
350
|
+
- Confirm the worktree still exists (`git worktree list | grep <worktree_path>`). Missing → ask user: `Reprovision (run prepare-worktree again with the same name)`, `Switch to main checkout (flip use_worktree to false for the rest of the run)`, `Stop`.
|
|
351
|
+
- Confirm the worktree summary file still parses; if not, regenerate from the existing worktree state.
|
|
352
|
+
- All resumed phases use the existing worktree — do not provision a second one.
|
|
353
|
+
3. `git -C <path> branch -a | grep plan/{plan-id-kebab}` to detect already-pushed phase branches (`<path>` = main checkout or worktree per `run_options.use_worktree`).
|
|
354
|
+
4. Cross-reference with the plan's phase list.
|
|
355
|
+
5. Confirm resumption point with the user.
|
|
321
356
|
|
|
322
357
|
## Step 2 — Final report
|
|
323
358
|
|
|
324
359
|
After all executable phases complete:
|
|
325
360
|
|
|
326
361
|
1. **Delete `TRACKING_{plan-id}.md`** on the last phase's branch. Commit. The plan file stays.
|
|
327
|
-
2. Send the user a final summary:
|
|
362
|
+
2. Send the user a final summary: {{FINAL_REPORT_BRANCH_SUMMARY}}; for UI-flow phases — list of `pr-screenshots/` files (if applicable); deferred phases (cross-repo + flag-removal); next steps for the human. When `run_options.use_worktree = true`: include the worktree path + branch + summary file path + the teardown command (`git worktree remove <path>` + the per-engine drop-db / `docker compose -p <project> down -v` lines from `<worktree_summary>`). Do NOT auto-run teardown — the user may still want the worktree to debug review feedback or land follow-ups.
|
|
328
363
|
{{FINAL_REPORT_PR_NOTE}}
|
|
329
364
|
3. Flag-removal phase deferred → end with `/schedule` offer for the dedicated flag-removal skill.
|
|
330
365
|
|
|
@@ -344,8 +379,12 @@ After all executable phases complete:
|
|
|
344
379
|
- **Feature flags = gates, not toggles for tests.**
|
|
345
380
|
- **Never remove a feature flag from this skill.**
|
|
346
381
|
- **Stop on Tier-4 failure.**
|
|
347
|
-
- **Honor opt-in flags.** `run_options.pause_between_phases` controls the [Per-phase pause gate](#1i-per-phase-pause-gate-opt-in); `run_options.generate_inline_comments` controls whether the [Open PR via context file](#1f-open-pr-via-context-file) step drafts inline comments (always writes the file when that step runs at all — empty comments when off).
|
|
382
|
+
- **Honor opt-in flags.** `run_options.pause_between_phases` controls the [Per-phase pause gate](#1i-per-phase-pause-gate-opt-in); `run_options.generate_inline_comments` controls whether the [Open PR via context file](#1f-open-pr-via-context-file) step drafts inline comments (always writes the file when that step runs at all — empty comments when off); `run_options.use_worktree` controls whether the [Provision worktree](#step-05--provision-worktree-when-run_optionsuse_worktree--true) step runs and whether every later `git` / lint / test / build / migrate call uses the worktree path.
|
|
383
|
+
- **One worktree per plan run.** When `use_worktree = true`, provision once in the [Provision worktree](#step-05--provision-worktree-when-run_optionsuse_worktree--true) step and reuse for every phase. Never spawn a second worktree mid-plan; never silently fall back to the main checkout on prepare-worktree failure (ask the user).
|
|
384
|
+
- **Don't auto-tear-down the worktree.** Step 2 surfaces the teardown command; the user runs it when they're ready (after reviewer feedback is addressed, after the PR merges, etc.).
|
|
385
|
+
- **Catch stray main-checkout writes when `use_worktree = true`.** A subagent can resolve a path back to the main checkout and silently edit there, leaving the change out of the phase commit. Layer 1 runs `git -C <main-checkout-path> status --short` after every implementer and fixer; any tracked modification is a BLOCKER — recover the intent, re-dispatch to the worktree, then restore the main checkout clean. `<main-checkout-path>` is the repo root the skill was invoked from, never `run_options.worktree_path`.
|
|
348
386
|
- **PR-context file + `open-pr.sh` is the only PR-creation path.** No raw `gh pr create` / `glab mr create` calls outside the bundled script. The file is durable; the script is the publisher.
|
|
387
|
+
{{DEPENDENCY_LICENSE_RULE_LINE}}
|
|
349
388
|
- **[Open PR via context file](#1f-open-pr-via-context-file) gating** = combination of project PR policy ({{PR_POLICY_BLOCK}}) and `generate_inline_comments`. See the matrix in that step. Skip it entirely only when policy = branches only AND comments = off.
|
|
350
389
|
- **Never use `§N` shorthand to point at sections** — neither in this skill body nor in any rendered file (tracking, prs-context, branch description). Always use the section's full name with a markdown link when possible. `§N` references are hard to read for humans and brittle when section numbering shifts.
|
|
351
390
|
|
|
@@ -353,16 +392,18 @@ After all executable phases complete:
|
|
|
353
392
|
|
|
354
393
|
- [ ] Plan parsed; structured fields cached.
|
|
355
394
|
- [ ] Cross-repo + flag-removal phases identified + deferred.
|
|
356
|
-
- [ ]
|
|
395
|
+
- [ ] `run_options.use_worktree` resolved; [Provision worktree](#step-05--provision-worktree-when-run_optionsuse_worktree--true) ran when `true` (worktree provisioned + summary captured + user confirmed); skipped when `false`.
|
|
396
|
+
- [ ] Current phase: prompt composed with **Goals + Non-goals** + **Guiding Decisions** + relevant **Data Model Changes** subsection + tracking summaries + this phase's body (plus the worktree block when `use_worktree = true`).
|
|
357
397
|
- [ ] Model picked from `**Suggested AI model**:` line (cheapest available); plan tier recorded.
|
|
358
398
|
- [ ] Subagent spawned, report received.
|
|
359
399
|
- [ ] Inner loop green: scoped lint + new tests individually + scoped suite.
|
|
360
400
|
- [ ] **Outer gate green:** `{{BUILD_CMD}}` AND `{{TEST_CMD}}`{{E2E_OUTER_GATE_CHECKLIST}} both passed.
|
|
361
|
-
- [ ] Layer 1 review: full diff read; no scope creep; no secrets; outer gate confirmed{{COAUTHOR_CHECKLIST_NOTE}}.
|
|
401
|
+
- [ ] Layer 1 review: full diff read; no scope creep; no secrets; outer gate confirmed{{COAUTHOR_CHECKLIST_NOTE}}; when `use_worktree = true`, `git -C <main-checkout-path> status --short` clean (no stray main-checkout writes) after the implementer and after every fixer.
|
|
362
402
|
- [ ] Layer 2 review: every "Changes" ticked; every "Tests" materialized; acceptance line satisfiable; conventions, reusable skills, e2e + screenshot compliance (if applicable), flag wiring all checked.
|
|
363
403
|
- [ ] Layer 3 review: adversarial review run; BLOCKERs fixed; SHOULD-FIX either fixed or noted.
|
|
364
404
|
- [ ] After any fix-up: Layers 1 + 2 + outer gate re-run.
|
|
365
|
-
- [ ]
|
|
405
|
+
- [ ] {{BRANCH_CHECKLIST_LINE}}{{PR_CHECKLIST_NOTE}}
|
|
406
|
+
{{COMMIT_STRATEGY_CHECKLIST_BLOCK}}
|
|
366
407
|
- [ ] **Open PR via context file** decision applied per matrix (PR policy + `generate_inline_comments`):
|
|
367
408
|
- [ ] PR-context file written when at least one of policy=create / comments=true holds.
|
|
368
409
|
- [ ] `open-pr.sh` run when policy=create AND deps available; PR URL captured.
|
|
@@ -204,13 +204,23 @@ function autoGenHeader(srcPath, comment = '#') {
|
|
|
204
204
|
|
|
205
205
|
// ── Vendor emitters ──────────────────────────────────────────────────────
|
|
206
206
|
|
|
207
|
+
// Fold a (possibly multi-line) value into a YAML-safe single-line scalar.
|
|
208
|
+
// The agent `description` in the source YAML is authored as multi-line prose;
|
|
209
|
+
// interpolated raw into markdown frontmatter it produces invalid YAML
|
|
210
|
+
// (unindented continuation lines + embedded colons), so the harness silently
|
|
211
|
+
// skips the agent. A JSON string is also a valid YAML double-quoted flow
|
|
212
|
+
// scalar, so JSON.stringify gives correct escaping for free.
|
|
213
|
+
function yamlInlineScalar(value) {
|
|
214
|
+
return JSON.stringify(String(value).replace(/\s+/g, ' ').trim());
|
|
215
|
+
}
|
|
216
|
+
|
|
207
217
|
function emitClaude(name, doc, body) {
|
|
208
218
|
const cfg = resolveVendorConfig(doc, 'claude');
|
|
209
219
|
const lines = [
|
|
210
220
|
'---',
|
|
211
221
|
autoGenHeader(`ai-tools/agents/${name}.yaml`),
|
|
212
222
|
`name: ${doc.name}`,
|
|
213
|
-
`description: ${doc.description}`,
|
|
223
|
+
`description: ${yamlInlineScalar(doc.description)}`,
|
|
214
224
|
];
|
|
215
225
|
if (cfg.tools) lines.push(`tools: ${cfg.tools}`);
|
|
216
226
|
if (cfg.model) lines.push(`model: ${cfg.model}`);
|
|
@@ -224,7 +234,7 @@ function emitCursor(name, doc, body) {
|
|
|
224
234
|
'---',
|
|
225
235
|
autoGenHeader(`ai-tools/agents/${name}.yaml`),
|
|
226
236
|
`name: ${doc.name}`,
|
|
227
|
-
`description: ${doc.description}`,
|
|
237
|
+
`description: ${yamlInlineScalar(doc.description)}`,
|
|
228
238
|
];
|
|
229
239
|
if (cfg.model) lines.push(`model: ${cfg.model}`);
|
|
230
240
|
if (cfg.readonly !== undefined) lines.push(`readonly: ${cfg.readonly}`);
|
|
@@ -238,7 +248,7 @@ function emitCopilot(name, doc, body) {
|
|
|
238
248
|
const lines = [
|
|
239
249
|
'---',
|
|
240
250
|
autoGenHeader(`ai-tools/agents/${name}.yaml`),
|
|
241
|
-
`description: ${doc.description}`,
|
|
251
|
+
`description: ${yamlInlineScalar(doc.description)}`,
|
|
242
252
|
];
|
|
243
253
|
if (cfg.tools) lines.push(`tools: ${JSON.stringify(cfg.tools)}`);
|
|
244
254
|
if (cfg.model) lines.push(`model: ${cfg.model}`);
|
|
@@ -19,6 +19,7 @@ The canonical project-conventions document. Read by every AI tool. Single source
|
|
|
19
19
|
- **Keep as-is, link from AGENTS.md** — leave the file untouched; add a `## See also` reference to it from the new AGENTS.md and copy only the high-level pointer (one sentence + link).
|
|
20
20
|
- **Replace from scratch** — discard the old content; draft fresh from inventory + interview. Note the discarded files in the run summary so the user can verify nothing important was lost.
|
|
21
21
|
3. Interview answers (Step 0 below).
|
|
22
|
+
4. `.vinta-ai-workflows.yaml` (`policies.dependency_licenses`) — drives the **Dependency licenses** section. The bootstrap interview captures enforcement + forbidden SPDX list + per-package overrides + free-form notes; this skill only reads and renders. Don't re-interview the user about license policy — if the config block is missing, route the user back to [vinta-bootstrap-ai-tools](../vinta-bootstrap-ai-tools/SKILL.md) Step 0 (Project conventions → license policy).
|
|
22
23
|
|
|
23
24
|
## Step 0 — Interview before drafting
|
|
24
25
|
|
|
@@ -134,6 +135,30 @@ VAR_TWO
|
|
|
134
135
|
...
|
|
135
136
|
```
|
|
136
137
|
|
|
138
|
+
## Dependency licenses
|
|
139
|
+
|
|
140
|
+
{Rendered from `policies.dependency_licenses` in `.vinta-ai-workflows.yaml`. Skip when the block is absent. When `enforcement: off`, render a single line: "No license check enforced." When `enforcement: block` or `warn`, render:}
|
|
141
|
+
|
|
142
|
+
**Enforcement:** {`block` — refuse install + ask user before override / `warn` — proceed but flag in phase report}.
|
|
143
|
+
|
|
144
|
+
**Forbidden SPDX licenses** (any new third-party dep matching these must be checked before install):
|
|
145
|
+
|
|
146
|
+
{Bullet list of `forbidden_spdx` entries as inline SPDX IDs.}
|
|
147
|
+
|
|
148
|
+
**Pre-install check.** Before running `npm add` / `pnpm add` / `pip install` / `poetry add` / `uv add` / `cargo add` / `go get` (or equivalent) for any new dep, look up the package's declared license (`npm view <pkg> license`, PyPI metadata, `cargo metadata`, the package's repo `LICENSE`) and compare to the list above. If the license is in the list and the `(package, license)` pair is not in **Approved overrides**, stop and surface the conflict to the user.
|
|
149
|
+
|
|
150
|
+
**Unknown / undeclared license.** When the lookup returns no license, an empty value, `UNKNOWN`, `SEE LICENSE IN <file>`, or only an unstructured `LICENSE` file with no SPDX identifier, **stop and ask the user** regardless of the enforcement mode above. Don't guess, don't fall back to "assume MIT" — the package may be all-rights-reserved by default. The user picks one of: skip the dep, treat as forbidden, or record an `allowed_overrides` entry with the SPDX they independently confirmed off-channel.
|
|
151
|
+
|
|
152
|
+
{When `allowed_overrides` is non-empty:}
|
|
153
|
+
|
|
154
|
+
**Approved overrides** (these forbidden-license deps are explicitly accepted):
|
|
155
|
+
|
|
156
|
+
| Package | License | Reason |
|
|
157
|
+
|---|---|---|
|
|
158
|
+
| {package} | {SPDX} | {reason} |
|
|
159
|
+
|
|
160
|
+
{When `notes` is non-empty, render verbatim under a `**Notes.**` paragraph.}
|
|
161
|
+
|
|
137
162
|
## Error / Exception Tracking
|
|
138
163
|
|
|
139
164
|
{If Sentry / Bugsnag / Datadog detected: one section with init pattern + tracing examples. Else: omit.}
|