specrails 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/openspec-apply-change/SKILL.md +156 -0
- package/.claude/skills/openspec-archive-change/SKILL.md +114 -0
- package/.claude/skills/openspec-bulk-archive-change/SKILL.md +246 -0
- package/.claude/skills/openspec-continue-change/SKILL.md +118 -0
- package/.claude/skills/openspec-explore/SKILL.md +290 -0
- package/.claude/skills/openspec-ff-change/SKILL.md +101 -0
- package/.claude/skills/openspec-new-change/SKILL.md +74 -0
- package/.claude/skills/openspec-onboard/SKILL.md +529 -0
- package/.claude/skills/openspec-sync-specs/SKILL.md +138 -0
- package/.claude/skills/openspec-verify-change/SKILL.md +168 -0
- package/README.md +226 -0
- package/VERSION +1 -0
- package/bin/specrails.js +41 -0
- package/commands/setup.md +851 -0
- package/install.sh +488 -0
- package/package.json +34 -0
- package/prompts/analyze-codebase.md +87 -0
- package/prompts/generate-personas.md +61 -0
- package/prompts/infer-conventions.md +72 -0
- package/templates/agents/sr-architect.md +194 -0
- package/templates/agents/sr-backend-developer.md +54 -0
- package/templates/agents/sr-backend-reviewer.md +139 -0
- package/templates/agents/sr-developer.md +146 -0
- package/templates/agents/sr-doc-sync.md +167 -0
- package/templates/agents/sr-frontend-developer.md +48 -0
- package/templates/agents/sr-frontend-reviewer.md +132 -0
- package/templates/agents/sr-product-analyst.md +36 -0
- package/templates/agents/sr-product-manager.md +148 -0
- package/templates/agents/sr-reviewer.md +265 -0
- package/templates/agents/sr-security-reviewer.md +178 -0
- package/templates/agents/sr-test-writer.md +163 -0
- package/templates/claude-md/root.md +50 -0
- package/templates/commands/sr/batch-implement.md +282 -0
- package/templates/commands/sr/compat-check.md +271 -0
- package/templates/commands/sr/health-check.md +396 -0
- package/templates/commands/sr/implement.md +972 -0
- package/templates/commands/sr/product-backlog.md +195 -0
- package/templates/commands/sr/refactor-recommender.md +169 -0
- package/templates/commands/sr/update-product-driven-backlog.md +272 -0
- package/templates/commands/sr/why.md +96 -0
- package/templates/personas/persona.md +43 -0
- package/templates/personas/the-maintainer.md +78 -0
- package/templates/rules/layer.md +8 -0
- package/templates/security/security-exemptions.yaml +20 -0
- package/templates/settings/confidence-config.json +17 -0
- package/templates/settings/settings.json +15 -0
- package/templates/web-manager/README.md +107 -0
- package/templates/web-manager/client/index.html +12 -0
- package/templates/web-manager/client/package-lock.json +1727 -0
- package/templates/web-manager/client/package.json +20 -0
- package/templates/web-manager/client/src/App.tsx +83 -0
- package/templates/web-manager/client/src/components/AgentActivity.tsx +19 -0
- package/templates/web-manager/client/src/components/CommandInput.tsx +81 -0
- package/templates/web-manager/client/src/components/LogStream.tsx +57 -0
- package/templates/web-manager/client/src/components/PipelineSidebar.tsx +65 -0
- package/templates/web-manager/client/src/components/SearchBox.tsx +34 -0
- package/templates/web-manager/client/src/hooks/usePipeline.ts +62 -0
- package/templates/web-manager/client/src/hooks/useWebSocket.ts +59 -0
- package/templates/web-manager/client/src/main.tsx +9 -0
- package/templates/web-manager/client/tsconfig.json +21 -0
- package/templates/web-manager/client/tsconfig.node.json +11 -0
- package/templates/web-manager/client/vite.config.ts +13 -0
- package/templates/web-manager/package-lock.json +3327 -0
- package/templates/web-manager/package.json +30 -0
- package/templates/web-manager/server/hooks.test.ts +196 -0
- package/templates/web-manager/server/hooks.ts +71 -0
- package/templates/web-manager/server/index.test.ts +186 -0
- package/templates/web-manager/server/index.ts +99 -0
- package/templates/web-manager/server/spawner.test.ts +319 -0
- package/templates/web-manager/server/spawner.ts +89 -0
- package/templates/web-manager/server/types.ts +46 -0
- package/templates/web-manager/tsconfig.json +14 -0
- package/templates/web-manager/vitest.config.ts +8 -0
- package/update.sh +877 -0
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
# Batch Implementation Orchestrator
|
|
2
|
+
|
|
3
|
+
Macro-orchestrator above `/sr:implement`. Accepts a set of feature references, computes a dependency-aware wave execution plan, invokes `/sr:implement` per wave, and produces a batch-level progress dashboard and final report. All per-feature pipeline work (sr-architect, sr-developer, sr-reviewer, git, CI) is fully delegated to `/sr:implement`.
|
|
4
|
+
|
|
5
|
+
**MANDATORY: Always follow this pipeline exactly as written. NEVER skip, shortcut, or "optimize away" any phase — even if the batch seems small enough to handle directly. The orchestrator MUST compute waves, confirm with the user, and invoke `/sr:implement` per wave as specified. Do NOT implement any feature yourself in the main conversation. No exceptions.**
|
|
6
|
+
|
|
7
|
+
**Input:** $ARGUMENTS — one or more feature references with optional flags:
|
|
8
|
+
|
|
9
|
+
- **Feature refs**: `#85 #71 #63` (GitHub issue numbers) — required, at least two
|
|
10
|
+
- **`--deps "<spec>"`**: inline dependency spec, e.g. `"#71 -> #85, #63 -> #85"` (meaning #71 and #63 must complete before #85)
|
|
11
|
+
- **`--concurrency N`**: max features running in parallel across waves (default: 3)
|
|
12
|
+
- **`--wave-size N`**: max features per wave regardless of concurrency (default: unlimited)
|
|
13
|
+
- **`--dry-run` / `--preview`**: passed through to each `/sr:implement` invocation; no git or backlog operations will run
|
|
14
|
+
|
|
15
|
+
**IMPORTANT:** Before running, ensure Read/Write/Bash/Glob/Grep permissions are set to "allow" — background agents cannot request permissions interactively.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Phase 0: Parse Input
|
|
20
|
+
|
|
21
|
+
### Step 1: Extract feature refs
|
|
22
|
+
|
|
23
|
+
Scan `$ARGUMENTS` for issue/ticket references (e.g. `#85`, `#71`). Collect into `FEATURE_REFS` list. If fewer than 2 refs are found, stop and print:
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
[batch-implement] Error: at least 2 feature refs are required. For a single feature, use /sr:implement directly.
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Step 2: Extract flags
|
|
30
|
+
|
|
31
|
+
Scan `$ARGUMENTS` for control flags:
|
|
32
|
+
|
|
33
|
+
- If `--dry-run` or `--preview` is present: set `DRY_RUN=true`. This flag is forwarded to every `/sr:implement` call.
|
|
34
|
+
- If `--deps "<spec>"` is present: capture the quoted string as `DEPS_SPEC`. Strip from arguments.
|
|
35
|
+
- If `--concurrency N` is present: set `CONCURRENCY=N` (integer ≥ 1). Default: 3.
|
|
36
|
+
- If `--wave-size N` is present: set `WAVE_SIZE=N` (integer ≥ 1). Default: unlimited (no per-wave cap).
|
|
37
|
+
|
|
38
|
+
**If `DRY_RUN=true`**, print:
|
|
39
|
+
```
|
|
40
|
+
[dry-run] Preview mode active — /sr:implement will be called with --dry-run for each wave.
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Step 3: Fetch issue titles
|
|
44
|
+
|
|
45
|
+
For each ref in `FEATURE_REFS`, fetch the issue title to use in progress output:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
{{BACKLOG_VIEW_CMD}} --json number,title
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Store as `FEATURE_TITLES` map: `{ref: title}`.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Phase 1: Wave Planning
|
|
56
|
+
|
|
57
|
+
### Step 1: Parse dependency graph
|
|
58
|
+
|
|
59
|
+
Build a directed graph `DEP_GRAPH` where an edge `A -> B` means "A must complete before B starts".
|
|
60
|
+
|
|
61
|
+
Parse `DEPS_SPEC` (if provided) by splitting on `,` and parsing each token as `<ref> -> <ref>`.
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
for each token in DEPS_SPEC.split(","):
|
|
65
|
+
left, right = token.split("->")
|
|
66
|
+
DEP_GRAPH.add_edge(left.strip(), right.strip())
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
All refs in `FEATURE_REFS` that appear in no edge are treated as independent (no dependencies).
|
|
70
|
+
|
|
71
|
+
### Step 2: Detect circular dependencies
|
|
72
|
+
|
|
73
|
+
Run cycle detection on `DEP_GRAPH`:
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
visited = {}
|
|
77
|
+
rec_stack = {}
|
|
78
|
+
|
|
79
|
+
function has_cycle(node):
|
|
80
|
+
visited[node] = true
|
|
81
|
+
rec_stack[node] = true
|
|
82
|
+
for neighbor in DEP_GRAPH.neighbors(node):
|
|
83
|
+
if not visited[neighbor] and has_cycle(neighbor):
|
|
84
|
+
return true
|
|
85
|
+
elif rec_stack[neighbor]:
|
|
86
|
+
return true
|
|
87
|
+
rec_stack[node] = false
|
|
88
|
+
return false
|
|
89
|
+
|
|
90
|
+
CYCLES = [node for node in FEATURE_REFS if not visited[node] and has_cycle(node)]
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
If `CYCLES` is non-empty: stop and print:
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
[batch-implement] Error: circular dependency detected.
|
|
97
|
+
Cycle involves: <ref-list>
|
|
98
|
+
Fix the --deps spec and re-run.
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Step 3: Compute waves via Kahn's algorithm
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
in_degree = {ref: 0 for ref in FEATURE_REFS}
|
|
105
|
+
for each edge (A -> B) in DEP_GRAPH:
|
|
106
|
+
in_degree[B] += 1
|
|
107
|
+
|
|
108
|
+
WAVES = []
|
|
109
|
+
ready = [ref for ref in FEATURE_REFS if in_degree[ref] == 0]
|
|
110
|
+
sort ready alphabetically (stable ordering)
|
|
111
|
+
|
|
112
|
+
while ready is non-empty:
|
|
113
|
+
wave = ready[:WAVE_SIZE] # cap at WAVE_SIZE if set; else take all
|
|
114
|
+
remaining = ready[WAVE_SIZE:] if WAVE_SIZE else []
|
|
115
|
+
WAVES.append(wave)
|
|
116
|
+
for ref in wave:
|
|
117
|
+
for neighbor in DEP_GRAPH.neighbors(ref):
|
|
118
|
+
in_degree[neighbor] -= 1
|
|
119
|
+
if in_degree[neighbor] == 0:
|
|
120
|
+
remaining.append(neighbor)
|
|
121
|
+
sort remaining alphabetically
|
|
122
|
+
ready = remaining
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Set `TOTAL_WAVES = len(WAVES)`.
|
|
126
|
+
|
|
127
|
+
### Step 4: Print execution plan and ask for confirmation
|
|
128
|
+
|
|
129
|
+
Print the wave execution plan:
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
## Batch Execution Plan
|
|
133
|
+
|
|
134
|
+
Total features : <N>
|
|
135
|
+
Total waves : <TOTAL_WAVES>
|
|
136
|
+
Max concurrency: <CONCURRENCY>
|
|
137
|
+
Dry-run : <yes / no>
|
|
138
|
+
|
|
139
|
+
| Wave | Features | Depends On |
|
|
140
|
+
|------|----------|------------|
|
|
141
|
+
| 1 | #85, #71 | — |
|
|
142
|
+
| 2 | #63 | #85, #71 |
|
|
143
|
+
|
|
144
|
+
Dependency graph:
|
|
145
|
+
#71 -> #63
|
|
146
|
+
#85 -> #63
|
|
147
|
+
|
|
148
|
+
Proceed? (yes / no / edit-deps)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Wait for user confirmation.
|
|
152
|
+
|
|
153
|
+
- **`yes`**: proceed to Phase 2.
|
|
154
|
+
- **`no`**: stop. Print `[batch-implement] Aborted by user.`
|
|
155
|
+
- **`edit-deps`**: ask the user to provide a corrected `--deps` spec, re-run Phase 1 from Step 1.
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Phase 2: Wave Execution Loop
|
|
160
|
+
|
|
161
|
+
Execute waves sequentially. Within each wave, invoke `/sr:implement` for all features in parallel (up to `CONCURRENCY` at a time).
|
|
162
|
+
|
|
163
|
+
### Progress Dashboard
|
|
164
|
+
|
|
165
|
+
Before starting each wave, print the current dashboard state:
|
|
166
|
+
|
|
167
|
+
```
|
|
168
|
+
## Batch Progress
|
|
169
|
+
|
|
170
|
+
| # | Feature | Title | Wave | Status | Notes |
|
|
171
|
+
|---|---------|-------|------|--------|-------|
|
|
172
|
+
| 1 | #85 | <title> | 1 | done | |
|
|
173
|
+
| 2 | #71 | <title> | 1 | done | |
|
|
174
|
+
| 3 | #63 | <title> | 2 | running| |
|
|
175
|
+
| 4 | #42 | <title> | 2 | blocked| depends on #63 |
|
|
176
|
+
| 5 | #17 | <title> | 3 | pending| |
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Status values:
|
|
180
|
+
- `pending` — not yet started
|
|
181
|
+
- `running` — `/sr:implement` invocation is active
|
|
182
|
+
- `done` — `/sr:implement` completed successfully
|
|
183
|
+
- `failed` — `/sr:implement` exited with an error
|
|
184
|
+
- `blocked` — a dependency failed; this feature will not run
|
|
185
|
+
|
|
186
|
+
### Wave invocation
|
|
187
|
+
|
|
188
|
+
For each wave `W`:
|
|
189
|
+
|
|
190
|
+
1. Print: `[wave W/TOTAL_WAVES] Starting — features: <ref-list>`
|
|
191
|
+
2. For each feature batch of size ≤ `CONCURRENCY` within the wave:
|
|
192
|
+
- Invoke `/sr:implement` with the feature refs and forwarded flags:
|
|
193
|
+
```
|
|
194
|
+
/sr:implement <ref1> <ref2> ... [--dry-run]
|
|
195
|
+
```
|
|
196
|
+
- Run invocations in the batch in parallel (`run_in_background: true`).
|
|
197
|
+
- Wait for all in the batch to complete before starting the next batch.
|
|
198
|
+
3. For each completed invocation, record outcome in `WAVE_RESULTS`:
|
|
199
|
+
- `{ref, wave, status: "done" | "failed", error_summary: "..." | null}`
|
|
200
|
+
|
|
201
|
+
### Failure isolation
|
|
202
|
+
|
|
203
|
+
After each wave completes:
|
|
204
|
+
|
|
205
|
+
```
|
|
206
|
+
FAILED_THIS_WAVE = [ref for ref in wave if WAVE_RESULTS[ref].status == "failed"]
|
|
207
|
+
|
|
208
|
+
for each ref in FAILED_THIS_WAVE:
|
|
209
|
+
BLOCKED = all refs in DEP_GRAPH.descendants(ref)
|
|
210
|
+
for each blocked_ref in BLOCKED:
|
|
211
|
+
WAVE_RESULTS[blocked_ref] = {status: "blocked", reason: "depends on failed " + ref}
|
|
212
|
+
remove blocked_ref from all future waves
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
A failed feature blocks ONLY its transitive dependents. Features in other branches of the dependency graph continue unaffected.
|
|
216
|
+
|
|
217
|
+
Print updated dashboard after each wave.
|
|
218
|
+
|
|
219
|
+
### Wave completion gate
|
|
220
|
+
|
|
221
|
+
Before starting wave W+1, confirm all features in wave W have status `done` or `blocked`. Never start a downstream wave while upstream features are still running.
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## Phase 3: Batch Report
|
|
226
|
+
|
|
227
|
+
After all waves complete (or all remaining features are blocked), print the final batch report.
|
|
228
|
+
|
|
229
|
+
```
|
|
230
|
+
## Batch Implementation Report
|
|
231
|
+
|
|
232
|
+
Run completed: <ISO 8601 timestamp>
|
|
233
|
+
Dry-run: <yes / no>
|
|
234
|
+
|
|
235
|
+
### Summary
|
|
236
|
+
|
|
237
|
+
| Metric | Count |
|
|
238
|
+
|--------|-------|
|
|
239
|
+
| Total features | N |
|
|
240
|
+
| Succeeded | N |
|
|
241
|
+
| Failed | N |
|
|
242
|
+
| Blocked (dep failure) | N |
|
|
243
|
+
|
|
244
|
+
### Per-Feature Results
|
|
245
|
+
|
|
246
|
+
| # | Feature | Title | Wave | Status | Notes |
|
|
247
|
+
|---|---------|-------|------|--------|-------|
|
|
248
|
+
| 1 | #85 | <title> | 1 | done | |
|
|
249
|
+
| 2 | #71 | <title> | 1 | failed | see /sr:implement output |
|
|
250
|
+
| 3 | #63 | <title> | 2 | blocked| depends on #71 |
|
|
251
|
+
|
|
252
|
+
### Merge Conflicts
|
|
253
|
+
|
|
254
|
+
[List any merge conflicts reported by /sr:implement across all waves. If none: "No merge conflicts detected."]
|
|
255
|
+
|
|
256
|
+
| Feature | File | Conflicting Region |
|
|
257
|
+
|---------|------|--------------------|
|
|
258
|
+
| #85 | src/utils/parser.ts | function parseQuery |
|
|
259
|
+
|
|
260
|
+
### Next Steps
|
|
261
|
+
|
|
262
|
+
[If all features succeeded:]
|
|
263
|
+
All features implemented. Review open PRs and monitor CI.
|
|
264
|
+
|
|
265
|
+
[If any features failed:]
|
|
266
|
+
Re-run failed features individually:
|
|
267
|
+
/sr:implement <failed-ref>
|
|
268
|
+
/sr:implement <failed-ref>
|
|
269
|
+
|
|
270
|
+
[If any features were blocked:]
|
|
271
|
+
Once failed features are fixed, re-run blocked features:
|
|
272
|
+
/sr:implement <blocked-ref> [--deps "..."]
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## Error Handling
|
|
278
|
+
|
|
279
|
+
- If a `/sr:implement` invocation fails: record failure, apply failure isolation, continue remaining waves
|
|
280
|
+
- If GitHub CLI is unavailable (detected during issue title fetch): proceed without titles, show refs only
|
|
281
|
+
- If `--deps` spec contains unknown refs: warn and continue — unknown refs are ignored in graph construction
|
|
282
|
+
- Never block the entire batch on a single feature failure. Always produce a final report.
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: "Compatibility Impact Analyzer"
|
|
3
|
+
description: "Snapshot the current API surface and detect breaking changes against a prior baseline. Generates a migration guide when breaking changes are found."
|
|
4
|
+
category: Workflow
|
|
5
|
+
tags: [workflow, compatibility, breaking-changes, migration]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Analyze the API surface of **{{PROJECT_NAME}}** for backwards compatibility. Extracts the current contract surface (CLI flags, template placeholders, command names, argument flags, agent names, config keys), compares against a stored baseline, classifies each change by severity, and generates a migration guide when breaking changes are found.
|
|
9
|
+
|
|
10
|
+
**Input:** `$ARGUMENTS` — optional flags:
|
|
11
|
+
- `--diff` — compare current surface to most recent snapshot (default when snapshots exist)
|
|
12
|
+
- `--snapshot` — capture current surface and save without diffing (default on first run)
|
|
13
|
+
- `--since <date>` — diff against snapshot from this date (ISO format: YYYY-MM-DD)
|
|
14
|
+
- `--propose <change-dir>` — diff proposed changes in `openspec/changes/<change-dir>/` against current surface
|
|
15
|
+
- `--dry-run` — run all phases but skip saving the snapshot
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Phase 0: Argument Parsing
|
|
20
|
+
|
|
21
|
+
Parse `$ARGUMENTS` to set runtime variables.
|
|
22
|
+
|
|
23
|
+
**Variables to set:**
|
|
24
|
+
|
|
25
|
+
- `MODE` — string, one of `"snapshot"`, `"diff"`, `"propose"`. Default: `"diff"` if `.claude/compat-snapshots/` contains any `.json` files; `"snapshot"` otherwise.
|
|
26
|
+
- `COMPARE_DATE` — string (ISO date) or empty string. Default: `""` (use most recent snapshot).
|
|
27
|
+
- `PROPOSE_DIR` — string or empty string. Default: `""`.
|
|
28
|
+
- `DRY_RUN` — boolean. Default: `false`.
|
|
29
|
+
|
|
30
|
+
**Parsing rules:**
|
|
31
|
+
|
|
32
|
+
1. Scan `$ARGUMENTS` for `--snapshot`. If found, set `MODE=snapshot`.
|
|
33
|
+
2. Scan for `--diff`. If found, set `MODE=diff`.
|
|
34
|
+
3. Scan for `--since <date>`. If found, set `COMPARE_DATE=<date>` and (if `MODE` not already set to `snapshot`) set `MODE=diff`.
|
|
35
|
+
4. Scan for `--propose <change-dir>`. If found, set `PROPOSE_DIR=<change-dir>` and `MODE=propose`.
|
|
36
|
+
- Verify `openspec/changes/<change-dir>/` exists. If not: print `Error: no change found at openspec/changes/<change-dir>/` and stop.
|
|
37
|
+
5. Scan for `--dry-run`. If found, set `DRY_RUN=true`.
|
|
38
|
+
6. Apply default-mode logic if `MODE` is not yet set: check whether `.claude/compat-snapshots/` exists and contains `.json` files. If yes: `MODE=diff`. If no: `MODE=snapshot`.
|
|
39
|
+
|
|
40
|
+
**Verify prerequisites:**
|
|
41
|
+
|
|
42
|
+
- Check whether `templates/` directory exists. If not: print `Error: templates/ not found — is this a specrails repo?` and stop.
|
|
43
|
+
- Check whether `install.sh` exists. If not: set `INSTALLER_AVAILABLE=false` (installer flags category will be skipped). Otherwise set `INSTALLER_AVAILABLE=true`.
|
|
44
|
+
|
|
45
|
+
**Print active configuration:**
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
Mode: <MODE> | Compare date: <COMPARE_DATE or "latest"> | Dry-run: <true/false>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Phase 1: Extract Current Surface
|
|
54
|
+
|
|
55
|
+
Read the codebase and build the surface snapshot. Print one progress line as each category completes.
|
|
56
|
+
|
|
57
|
+
**Surface category: installer_flags**
|
|
58
|
+
|
|
59
|
+
If `INSTALLER_AVAILABLE=false`: print ` installer_flags: skipped (install.sh not found)` and record as unavailable.
|
|
60
|
+
|
|
61
|
+
Otherwise: read `install.sh`. Find all `case` blocks that parse CLI arguments. Extract every `--<word>)` flag pattern. For each flag, record the flag string and line number.
|
|
62
|
+
|
|
63
|
+
Print: ` installer_flags: N found`
|
|
64
|
+
|
|
65
|
+
**Surface category: template_placeholders**
|
|
66
|
+
|
|
67
|
+
Read all files matching `templates/**/*.md`. For each file, extract all `{{UPPER_SNAKE_CASE}}` patterns (regex: `\{\{[A-Z][A-Z0-9_]*\}\}`). Deduplicate across files. For each unique key, record the list of source files it appears in.
|
|
68
|
+
|
|
69
|
+
Note: Skip patterns inside code fences that are used as documentation examples (i.e., patterns that appear inside triple-backtick blocks describing placeholder syntax rather than actual template usage). Use judgment to distinguish real template placeholders from documented examples.
|
|
70
|
+
|
|
71
|
+
Print: ` template_placeholders: N unique keys found`
|
|
72
|
+
|
|
73
|
+
**Surface category: command_names and command_arguments**
|
|
74
|
+
|
|
75
|
+
Read each file in `templates/commands/`. For each:
|
|
76
|
+
- Extract `name:` value from the YAML frontmatter (between the first `---` and second `---`)
|
|
77
|
+
- Extract the display name from the frontmatter `name:` field (the quoted string)
|
|
78
|
+
- Find all `--<word>` flag patterns in the `$ARGUMENTS` section or argument description prose
|
|
79
|
+
- Record command name, display name, source file, and flags list
|
|
80
|
+
|
|
81
|
+
Print: ` command_names: N commands found`
|
|
82
|
+
Print: ` command_arguments: N commands with flags documented`
|
|
83
|
+
|
|
84
|
+
**Surface category: agent_names**
|
|
85
|
+
|
|
86
|
+
Read each file in `templates/agents/`. Extract `name:` value from the YAML frontmatter.
|
|
87
|
+
|
|
88
|
+
Print: ` agent_names: N agents found`
|
|
89
|
+
|
|
90
|
+
**Surface category: config_keys**
|
|
91
|
+
|
|
92
|
+
Read `openspec/config.yaml`. Extract all top-level YAML keys (lines matching `^<key>:` at zero indentation).
|
|
93
|
+
|
|
94
|
+
Print: ` config_keys: N keys found`
|
|
95
|
+
|
|
96
|
+
**Build the surface object:**
|
|
97
|
+
|
|
98
|
+
Assemble all extracted data into a snapshot object matching the schema:
|
|
99
|
+
|
|
100
|
+
```json
|
|
101
|
+
{
|
|
102
|
+
"schema_version": "1",
|
|
103
|
+
"captured_at": "<ISO 8601 datetime>",
|
|
104
|
+
"git_sha": "<git rev-parse HEAD or 'unknown'>",
|
|
105
|
+
"git_branch": "<git rev-parse --abbrev-ref HEAD or 'unknown'>",
|
|
106
|
+
"surfaces": {
|
|
107
|
+
"installer_flags": [...],
|
|
108
|
+
"template_placeholders": [...],
|
|
109
|
+
"command_names": [...],
|
|
110
|
+
"command_arguments": [...],
|
|
111
|
+
"agent_names": [...],
|
|
112
|
+
"config_keys": [...]
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Set `CURRENT_SURFACE` to this object.
|
|
118
|
+
|
|
119
|
+
If `MODE=snapshot`: proceed directly to Phase 5 (skip Phases 2–4 diff logic, but still print a surface summary).
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## Phase 2: Load Baseline
|
|
124
|
+
|
|
125
|
+
Applies in `diff` and `propose` modes only.
|
|
126
|
+
|
|
127
|
+
**For `diff` mode:**
|
|
128
|
+
|
|
129
|
+
1. Check whether `.claude/compat-snapshots/` exists and contains `.json` files.
|
|
130
|
+
- If empty or missing: print `Advisory: no prior snapshot found. Switching to snapshot mode.` Set `MODE=snapshot`. Proceed to Phase 5.
|
|
131
|
+
2. If `COMPARE_DATE` is empty: select the most recently modified `.json` file.
|
|
132
|
+
3. If `COMPARE_DATE` is set: find the snapshot whose filename date is closest to `COMPARE_DATE` without exceeding it. If no match within 7 days: print `Warning: no snapshot found near <COMPARE_DATE>. Falling back to most recent.` Use most recent.
|
|
133
|
+
4. Load the selected file as `BASELINE_SURFACE`.
|
|
134
|
+
5. Print: `Baseline: <YYYY-MM-DD> (<sha from filename>)`
|
|
135
|
+
|
|
136
|
+
**For `propose` mode:**
|
|
137
|
+
|
|
138
|
+
1. Load the most recent snapshot from `.claude/compat-snapshots/` as `BASELINE_SURFACE` (same selection logic as `diff` mode with `COMPARE_DATE` empty).
|
|
139
|
+
2. Additionally read `openspec/changes/<PROPOSE_DIR>/design.md` to understand the projected surface changes.
|
|
140
|
+
- If `design.md` does not exist: print `Warning: no design.md found in openspec/changes/<PROPOSE_DIR>/. Proceeding with surface extraction only (no projection).`
|
|
141
|
+
- If it exists: read also `openspec/changes/<PROPOSE_DIR>/tasks.md` if present.
|
|
142
|
+
3. Use the proposed changes to project the "after" surface: identify which elements would be added, removed, or modified based on the design document.
|
|
143
|
+
4. Print: `Propose mode: analyzing openspec/changes/<PROPOSE_DIR>/`
|
|
144
|
+
|
|
145
|
+
Set `BASELINE_SURFACE` and `PROJECTED_CHANGES` (in propose mode).
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Phase 3: Diff and Classify
|
|
150
|
+
|
|
151
|
+
Applies in `diff` and `propose` modes. Skipped in `snapshot` mode.
|
|
152
|
+
|
|
153
|
+
For each surface category (`installer_flags`, `template_placeholders`, `command_names`, `command_arguments`, `agent_names`, `config_keys`):
|
|
154
|
+
|
|
155
|
+
1. Build identifier sets from baseline and current (or projected, in propose mode).
|
|
156
|
+
2. Compute:
|
|
157
|
+
- `removed = identifiers in baseline but not in current`
|
|
158
|
+
- `added = identifiers in current but not in baseline`
|
|
159
|
+
- `common = identifiers in both`
|
|
160
|
+
3. For common elements: check whether attributes changed (display name, flags list, file list). Classify attribute changes as Category 3 (Signature Change) if they affect the interface.
|
|
161
|
+
4. Classify each removal:
|
|
162
|
+
- If a similar-looking name appears in `added`: classify as **Category 2: Rename** (BREAKING — MAJOR)
|
|
163
|
+
- Otherwise: classify as **Category 1: Removal** (BREAKING — MAJOR)
|
|
164
|
+
5. Classify additions as non-breaking (new additions do not break existing callers).
|
|
165
|
+
6. Classify behavioral changes detected from the design document (in propose mode) as **Category 4: Behavioral Change** (ADVISORY).
|
|
166
|
+
|
|
167
|
+
Build two lists:
|
|
168
|
+
- `BREAKING_CHANGES` — list of `{ category, element, surface, severity, description }` objects (Categories 1, 2, 3)
|
|
169
|
+
- `ADVISORY_CHANGES` — list of `{ category, element, surface, description }` objects (Category 4)
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Phase 4: Generate Report
|
|
174
|
+
|
|
175
|
+
Print the full compatibility report.
|
|
176
|
+
|
|
177
|
+
```
|
|
178
|
+
## Compatibility Impact Report — {{PROJECT_NAME}}
|
|
179
|
+
Date: <ISO date> | Commit: <git_short_sha or "unknown">
|
|
180
|
+
|
|
181
|
+
### Surface Snapshot
|
|
182
|
+
| Category | Elements Found |
|
|
183
|
+
|----------|---------------|
|
|
184
|
+
| Installer flags | N |
|
|
185
|
+
| Template placeholders | N |
|
|
186
|
+
| Command names | N |
|
|
187
|
+
| Command argument flags | N |
|
|
188
|
+
| Agent names | N |
|
|
189
|
+
| Config keys | N |
|
|
190
|
+
|
|
191
|
+
### Breaking Changes (N found)
|
|
192
|
+
<if BREAKING_CHANGES is empty:>
|
|
193
|
+
None detected.
|
|
194
|
+
|
|
195
|
+
<if BREAKING_CHANGES is non-empty, for each:>
|
|
196
|
+
- [Category <N>: <category-name>] <surface>: `<element>` — <description>
|
|
197
|
+
|
|
198
|
+
### Advisory Changes (N found)
|
|
199
|
+
<if ADVISORY_CHANGES is empty:>
|
|
200
|
+
None detected.
|
|
201
|
+
|
|
202
|
+
<if ADVISORY_CHANGES is non-empty, for each:>
|
|
203
|
+
- [Category 4: Behavioral Change] <surface>: `<element>` — <description>
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
**Migration Guide** (only when `len(BREAKING_CHANGES) > 0`):
|
|
207
|
+
|
|
208
|
+
For each breaking change, append a Migration Guide block:
|
|
209
|
+
|
|
210
|
+
```
|
|
211
|
+
## Migration Guide
|
|
212
|
+
|
|
213
|
+
**Change type:** <Removal | Rename | Signature Change>
|
|
214
|
+
**Severity:** BREAKING
|
|
215
|
+
**Affects:** <who is affected>
|
|
216
|
+
|
|
217
|
+
### What Changed
|
|
218
|
+
<one paragraph describing before and after>
|
|
219
|
+
|
|
220
|
+
### Before
|
|
221
|
+
<concrete example of old usage>
|
|
222
|
+
|
|
223
|
+
### After
|
|
224
|
+
<concrete example of new usage>
|
|
225
|
+
|
|
226
|
+
### Remediation Options
|
|
227
|
+
|
|
228
|
+
**Option A — Backwards-compatible alias (recommended)**
|
|
229
|
+
<how to add an alias or shim>
|
|
230
|
+
|
|
231
|
+
**Option B — Clean break with changelog**
|
|
232
|
+
<what to put in CHANGELOG.md>
|
|
233
|
+
|
|
234
|
+
### Version Strategy
|
|
235
|
+
<MAJOR bump if removing/renaming; MINOR if signature-only>
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## Phase 5: Save Snapshot
|
|
241
|
+
|
|
242
|
+
**If `DRY_RUN=true`:**
|
|
243
|
+
|
|
244
|
+
Print: `Snapshot not saved — dry-run mode`
|
|
245
|
+
|
|
246
|
+
Skip the save. Still perform the housekeeping check and `.gitignore` check below.
|
|
247
|
+
|
|
248
|
+
**If `DRY_RUN=false`:**
|
|
249
|
+
|
|
250
|
+
1. Determine filename: `<YYYY-MM-DD>-<git_short_sha>.json`. If git is unavailable: `<YYYY-MM-DD>-unknown.json`.
|
|
251
|
+
2. Create `.claude/compat-snapshots/` if it does not exist.
|
|
252
|
+
3. Write `CURRENT_SURFACE` serialized as JSON to `.claude/compat-snapshots/<filename>`.
|
|
253
|
+
4. Print: `Snapshot saved: .claude/compat-snapshots/<filename>`
|
|
254
|
+
|
|
255
|
+
**Housekeeping notice:**
|
|
256
|
+
|
|
257
|
+
Count `.json` files in `.claude/compat-snapshots/`. If count > 30, print:
|
|
258
|
+
|
|
259
|
+
```
|
|
260
|
+
Note: .claude/compat-snapshots/ has N snapshots. Consider pruning old ones with:
|
|
261
|
+
ls -t .claude/compat-snapshots/ | tail -n +31 | xargs -I{} rm .claude/compat-snapshots/{}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
**.gitignore suggestion:**
|
|
265
|
+
|
|
266
|
+
Check whether `.claude/compat-snapshots/` appears in `.gitignore` (if `.gitignore` exists). If it does not appear, print:
|
|
267
|
+
|
|
268
|
+
```
|
|
269
|
+
Tip: compat snapshots are local artifacts. Add to .gitignore:
|
|
270
|
+
echo '.claude/compat-snapshots/' >> .gitignore
|
|
271
|
+
```
|