superspec 0.1.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/LICENSE +21 -0
- package/README.md +328 -0
- package/dist/cli/commands/archive.d.ts +7 -0
- package/dist/cli/commands/archive.d.ts.map +1 -0
- package/dist/cli/commands/archive.js +322 -0
- package/dist/cli/commands/archive.js.map +1 -0
- package/dist/cli/commands/init.d.ts +6 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +306 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/list.d.ts +9 -0
- package/dist/cli/commands/list.d.ts.map +1 -0
- package/dist/cli/commands/list.js +268 -0
- package/dist/cli/commands/list.js.map +1 -0
- package/dist/cli/commands/show.d.ts +6 -0
- package/dist/cli/commands/show.d.ts.map +1 -0
- package/dist/cli/commands/show.js +273 -0
- package/dist/cli/commands/show.js.map +1 -0
- package/dist/cli/commands/validate.d.ts +8 -0
- package/dist/cli/commands/validate.d.ts.map +1 -0
- package/dist/cli/commands/validate.js +209 -0
- package/dist/cli/commands/validate.js.map +1 -0
- package/dist/cli/commands/verify.d.ts +7 -0
- package/dist/cli/commands/verify.d.ts.map +1 -0
- package/dist/cli/commands/verify.js +328 -0
- package/dist/cli/commands/verify.js.map +1 -0
- package/dist/cli/commands/view.d.ts +6 -0
- package/dist/cli/commands/view.d.ts.map +1 -0
- package/dist/cli/commands/view.js +290 -0
- package/dist/cli/commands/view.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +87 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/ui/display.d.ts +189 -0
- package/dist/cli/ui/display.d.ts.map +1 -0
- package/dist/cli/ui/display.js +449 -0
- package/dist/cli/ui/display.js.map +1 -0
- package/dist/cli/ui/index.d.ts +12 -0
- package/dist/cli/ui/index.d.ts.map +1 -0
- package/dist/cli/ui/index.js +71 -0
- package/dist/cli/ui/index.js.map +1 -0
- package/dist/cli/ui/interactive.d.ts +21 -0
- package/dist/cli/ui/interactive.d.ts.map +1 -0
- package/dist/cli/ui/interactive.js +60 -0
- package/dist/cli/ui/interactive.js.map +1 -0
- package/dist/cli/ui/palette.d.ts +301 -0
- package/dist/cli/ui/palette.d.ts.map +1 -0
- package/dist/cli/ui/palette.js +673 -0
- package/dist/cli/ui/palette.js.map +1 -0
- package/dist/cli/ui/prompts.d.ts +62 -0
- package/dist/cli/ui/prompts.d.ts.map +1 -0
- package/dist/cli/ui/prompts.js +119 -0
- package/dist/cli/ui/prompts.js.map +1 -0
- package/dist/cli/ui/spinner.d.ts +38 -0
- package/dist/cli/ui/spinner.d.ts.map +1 -0
- package/dist/cli/ui/spinner.js +72 -0
- package/dist/cli/ui/spinner.js.map +1 -0
- package/dist/core/config/project-config.d.ts +100 -0
- package/dist/core/config/project-config.d.ts.map +1 -0
- package/dist/core/config/project-config.js +87 -0
- package/dist/core/config/project-config.js.map +1 -0
- package/dist/core/parsers/spec-parser.d.ts +48 -0
- package/dist/core/parsers/spec-parser.d.ts.map +1 -0
- package/dist/core/parsers/spec-parser.js +322 -0
- package/dist/core/parsers/spec-parser.js.map +1 -0
- package/dist/core/validation/spec-validator.d.ts +32 -0
- package/dist/core/validation/spec-validator.d.ts.map +1 -0
- package/dist/core/validation/spec-validator.js +242 -0
- package/dist/core/validation/spec-validator.js.map +1 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +44 -0
- package/dist/index.js.map +1 -0
- package/package.json +66 -0
- package/schemas/superspec-workflow/schema.yaml +166 -0
- package/templates/design.md +71 -0
- package/templates/plan.md +78 -0
- package/templates/proposal.md +36 -0
- package/templates/spec.md +57 -0
- package/templates/tasks.md +29 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Hank Liu
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
# SuperSpec
|
|
2
|
+
|
|
3
|
+
> **Spec-Driven Development Framework**
|
|
4
|
+
|
|
5
|
+
SuperSpec is a unified development framework that combines spec-driven documentation with TDD discipline.
|
|
6
|
+
|
|
7
|
+
## Core Philosophy
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
Every Scenario becomes a test. Every test traces back to a Scenario.
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
| Principle | Description |
|
|
14
|
+
|-----------|-------------|
|
|
15
|
+
| **Specs First** | All development has Specs as the single source of truth |
|
|
16
|
+
| **TDD Enforced** | Write tests first, watch them fail, then implement |
|
|
17
|
+
| **Two-Phase Review** | Spec Compliance Review → Code Quality Review |
|
|
18
|
+
| **Evidence First** | Verification over claims |
|
|
19
|
+
| **Delta Tracking** | Structured change history |
|
|
20
|
+
| **Archive Preservation** | Complete development documentation |
|
|
21
|
+
|
|
22
|
+
## Unified Workflow
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
┌─────────────────────────────────────────────────────────────────────────┐
|
|
26
|
+
│ FULL WORKFLOW (large features, team review) │
|
|
27
|
+
├─────────────────────────────────────────────────────────────────────────┤
|
|
28
|
+
│ /superspec:brainstorm → Progressive design (Explore → Propose → Spec)│
|
|
29
|
+
│ ↓ │
|
|
30
|
+
│ superspec validate → Validate specs (CLI) + team review │
|
|
31
|
+
│ ↓ │
|
|
32
|
+
│ /superspec:plan → Create TDD implementation plan │
|
|
33
|
+
└─────────────────────────────────────────────────────────────────────────┘
|
|
34
|
+
|
|
35
|
+
┌─────────────────────────────────────────────────────────────────────────┐
|
|
36
|
+
│ FAST TRACK (small-medium features) │
|
|
37
|
+
├─────────────────────────────────────────────────────────────────────────┤
|
|
38
|
+
│ /superspec:kickoff → All-in-one: brainstorm + validate + plan │
|
|
39
|
+
└─────────────────────────────────────────────────────────────────────────┘
|
|
40
|
+
|
|
41
|
+
↓ (both paths continue)
|
|
42
|
+
|
|
43
|
+
/superspec:execute Subagent-driven TDD implementation
|
|
44
|
+
↓
|
|
45
|
+
/superspec:verify Verify implementation matches specs
|
|
46
|
+
↓
|
|
47
|
+
/superspec:finish-branch Complete branch (merge/PR)
|
|
48
|
+
↓
|
|
49
|
+
/superspec:archive Archive changes, apply Delta
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Four Phases of Brainstorming
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
+----------------------------------------------------------+
|
|
56
|
+
| /superspec:brainstorm |
|
|
57
|
+
+----------------------------------------------------------+
|
|
58
|
+
| |
|
|
59
|
+
| Phase 1: EXPLORE |
|
|
60
|
+
| - Free exploration, understand the problem |
|
|
61
|
+
| - Ask clarifying questions, visualize ideas |
|
|
62
|
+
| |
|
|
63
|
+
| | Problem is clear |
|
|
64
|
+
| v |
|
|
65
|
+
| |
|
|
66
|
+
| Phase 2: PROPOSE -> proposal.md |
|
|
67
|
+
| - Define Why (problem/opportunity) |
|
|
68
|
+
| - Define What Changes (change list) |
|
|
69
|
+
| - Define Capabilities (new/modified features) |
|
|
70
|
+
| - Define Impact (affected areas) |
|
|
71
|
+
| |
|
|
72
|
+
| | Scope is defined |
|
|
73
|
+
| v |
|
|
74
|
+
| |
|
|
75
|
+
| Phase 3: DESIGN -> design.md |
|
|
76
|
+
| - Compare 2-3 technical approaches |
|
|
77
|
+
| - Select recommended approach with rationale |
|
|
78
|
+
| - Document trade-offs |
|
|
79
|
+
| |
|
|
80
|
+
| | Approach is decided |
|
|
81
|
+
| v |
|
|
82
|
+
| |
|
|
83
|
+
| Phase 4: SPEC -> specs/*.md |
|
|
84
|
+
| - Define Requirements (System SHALL...) |
|
|
85
|
+
| - Define Scenarios (WHEN/THEN -> each becomes a test) |
|
|
86
|
+
| |
|
|
87
|
+
+----------------------------------------------------------+
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Quick Start
|
|
91
|
+
|
|
92
|
+
### Installation
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
# Install CLI
|
|
96
|
+
npm install -g superspec
|
|
97
|
+
|
|
98
|
+
# Or install from source
|
|
99
|
+
git clone https://github.com/your-org/superspec
|
|
100
|
+
cd superspec
|
|
101
|
+
npm install
|
|
102
|
+
npm run build
|
|
103
|
+
npm link
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Claude Code Integration
|
|
107
|
+
|
|
108
|
+
Skills are automatically installed to `~/.claude/skills/` when you run `superspec init`.
|
|
109
|
+
|
|
110
|
+
### Initialize Project
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
superspec init
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
This will create:
|
|
117
|
+
```
|
|
118
|
+
your-project/
|
|
119
|
+
└── superspec/
|
|
120
|
+
├── project.yaml # Project configuration
|
|
121
|
+
├── specs/ # Specifications (source of truth)
|
|
122
|
+
└── changes/ # Change proposals
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Basic Usage
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
# FAST TRACK: Idea to plan in one session
|
|
129
|
+
/superspec:kickoff
|
|
130
|
+
|
|
131
|
+
# OR FULL WORKFLOW: Separate phases with review points
|
|
132
|
+
# 1. Start a new change (four-phase brainstorming)
|
|
133
|
+
/superspec:brainstorm
|
|
134
|
+
|
|
135
|
+
# 2. Validate specifications (CLI command)
|
|
136
|
+
superspec validate add-feature --strict
|
|
137
|
+
|
|
138
|
+
# 3. Create implementation plan
|
|
139
|
+
/superspec:plan
|
|
140
|
+
|
|
141
|
+
# THEN CONTINUE (both paths):
|
|
142
|
+
# 4. Execute plan (subagent-driven TDD)
|
|
143
|
+
/superspec:execute
|
|
144
|
+
|
|
145
|
+
# 5. Verify implementation (CLI command)
|
|
146
|
+
superspec verify add-feature
|
|
147
|
+
|
|
148
|
+
# 6. Complete branch (merge/PR/keep/discard)
|
|
149
|
+
/superspec:finish-branch
|
|
150
|
+
|
|
151
|
+
# 7. Archive changes (CLI command)
|
|
152
|
+
superspec archive add-feature --yes
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Directory Structure
|
|
156
|
+
|
|
157
|
+
### Project Structure
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
your-project/
|
|
161
|
+
├── superspec/
|
|
162
|
+
│ ├── specs/ # Specifications (source of truth)
|
|
163
|
+
│ │ └── [capability]/
|
|
164
|
+
│ │ └── spec.md
|
|
165
|
+
│ │
|
|
166
|
+
│ └── changes/ # Change proposals
|
|
167
|
+
│ ├── [change-id]/ # In progress
|
|
168
|
+
│ │ ├── proposal.md # Why + What
|
|
169
|
+
│ │ ├── design.md # How (technical approach)
|
|
170
|
+
│ │ ├── specs/ # Delta Specs
|
|
171
|
+
│ │ ├── plan.md # TDD implementation plan
|
|
172
|
+
│ │ └── tasks.md # Task list
|
|
173
|
+
│ │
|
|
174
|
+
│ └── archive/ # Completed
|
|
175
|
+
│ └── YYYY-MM-DD-[id]/
|
|
176
|
+
│
|
|
177
|
+
└── src/ # Actual code
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Spec Format
|
|
181
|
+
|
|
182
|
+
```markdown
|
|
183
|
+
# [Capability] Specification
|
|
184
|
+
|
|
185
|
+
## Purpose
|
|
186
|
+
[Feature purpose]
|
|
187
|
+
|
|
188
|
+
## Requirements
|
|
189
|
+
|
|
190
|
+
### Requirement: [Requirement Name]
|
|
191
|
+
The system SHALL [behavior description]
|
|
192
|
+
|
|
193
|
+
#### Scenario: [Scenario Name]
|
|
194
|
+
- **WHEN** [trigger condition]
|
|
195
|
+
- **THEN** [expected result]
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Spec-Test Mapping
|
|
199
|
+
|
|
200
|
+
```
|
|
201
|
+
Spec Scenario TDD Test
|
|
202
|
+
===============================================================
|
|
203
|
+
#### Scenario: Valid login -> test('Valid login', () => {
|
|
204
|
+
- WHEN valid credentials const result = login(valid);
|
|
205
|
+
- THEN grants access expect(result.granted).toBe(true);
|
|
206
|
+
});
|
|
207
|
+
===============================================================
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## CLI Commands
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
# Initialize
|
|
214
|
+
superspec init [path] # Initialize project
|
|
215
|
+
|
|
216
|
+
# Query
|
|
217
|
+
superspec list # List changes
|
|
218
|
+
superspec list --specs # List specifications
|
|
219
|
+
superspec show [item] # Show details
|
|
220
|
+
|
|
221
|
+
# Validate
|
|
222
|
+
superspec validate [id] # Validate specification
|
|
223
|
+
superspec validate [id] --strict # Strict mode
|
|
224
|
+
|
|
225
|
+
# Verify
|
|
226
|
+
superspec verify [id] # Verify implementation matches specs
|
|
227
|
+
superspec verify [id] --strict # Fail on extra code/tests
|
|
228
|
+
superspec verify [id] --verbose # Show detailed matching
|
|
229
|
+
|
|
230
|
+
# Archive
|
|
231
|
+
superspec archive [id] # Archive change
|
|
232
|
+
superspec archive [id] --yes # Non-interactive mode
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## Slash Commands (AI Assistant)
|
|
236
|
+
|
|
237
|
+
| Command | Description |
|
|
238
|
+
|---------|-------------|
|
|
239
|
+
| `/superspec:kickoff` | **Fast track**: brainstorm + validate + plan in one session |
|
|
240
|
+
| `/superspec:brainstorm` | **Full workflow**: Progressive design (Explore → Propose → Spec) |
|
|
241
|
+
| `/superspec:plan` | Create TDD implementation plan (after brainstorm) |
|
|
242
|
+
| `/superspec:execute` | Subagent-driven execution |
|
|
243
|
+
| `/superspec:verify` | Verify implementation matches specs |
|
|
244
|
+
| `/superspec:finish-branch` | Complete branch (merge/PR/keep/discard) |
|
|
245
|
+
| `/superspec:archive` | Archive changes |
|
|
246
|
+
|
|
247
|
+
## Skill System
|
|
248
|
+
|
|
249
|
+
### Design Phase
|
|
250
|
+
- `superspec:kickoff` - **Fast track**: idea to plan in one session
|
|
251
|
+
- `superspec:brainstorm` - **Full workflow**: progressive design only
|
|
252
|
+
|
|
253
|
+
### Planning Phase
|
|
254
|
+
- `superspec:plan` - Create TDD plan (after brainstorm)
|
|
255
|
+
- `git-worktree` - Git Worktree management
|
|
256
|
+
|
|
257
|
+
### Implementation Phase
|
|
258
|
+
- `tdd` - TDD cycle (with `testing-anti-patterns.md` reference)
|
|
259
|
+
- `superspec:execute` - Subagent-driven development (default)
|
|
260
|
+
- `executing-plans` - Batch execution with checkpoints (alternative)
|
|
261
|
+
- `dispatching-parallel-agents` - For 2+ independent parallel tasks
|
|
262
|
+
- `systematic-debugging` - Systematic debugging (with support files)
|
|
263
|
+
|
|
264
|
+
### Quality & Discipline Phase
|
|
265
|
+
- `verification-before-completion` - Evidence before completion claims
|
|
266
|
+
- `receiving-code-review` - Handle code review feedback professionally
|
|
267
|
+
|
|
268
|
+
### Review Phase
|
|
269
|
+
- `spec-validation` - Specification validation
|
|
270
|
+
- `code-review` - Code review (with `code-reviewer.md` template)
|
|
271
|
+
|
|
272
|
+
### Completion Phase
|
|
273
|
+
- `superspec:verify` - Verify implementation
|
|
274
|
+
- `superspec:finish-branch` - Complete branch (merge/PR/keep/discard)
|
|
275
|
+
- `superspec:archive` - Archive changes
|
|
276
|
+
|
|
277
|
+
## Two-Phase Review
|
|
278
|
+
|
|
279
|
+
```
|
|
280
|
+
+------------------------------------------+
|
|
281
|
+
| Stage 1: Spec Compliance Review |
|
|
282
|
+
| - Read Spec files |
|
|
283
|
+
| - Check each Requirement is implemented |
|
|
284
|
+
| - Check each Scenario has a test |
|
|
285
|
+
| - Check no extra/missing features |
|
|
286
|
+
+------------------------------------------+
|
|
287
|
+
|
|
|
288
|
+
[After passing]
|
|
289
|
+
v
|
|
290
|
+
+------------------------------------------+
|
|
291
|
+
| Stage 2: Code Quality Review |
|
|
292
|
+
| - Error handling |
|
|
293
|
+
| - Type safety |
|
|
294
|
+
| - SOLID principles |
|
|
295
|
+
| - Test quality |
|
|
296
|
+
+------------------------------------------+
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
## Iron Rules
|
|
300
|
+
|
|
301
|
+
### TDD Rule
|
|
302
|
+
```
|
|
303
|
+
No production code without a failing test first
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Spec Rule
|
|
307
|
+
```
|
|
308
|
+
Specs are the source of truth. Changes are proposals.
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### SuperSpec Rule
|
|
312
|
+
```
|
|
313
|
+
Every Scenario becomes a test. Every test traces back to a Scenario.
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### Verification Rule
|
|
317
|
+
```
|
|
318
|
+
No completion claims without fresh verification evidence
|
|
319
|
+
```
|
|
320
|
+
"It should work" is not evidence. Run the test. Show the output. Then claim completion.
|
|
321
|
+
|
|
322
|
+
## License
|
|
323
|
+
|
|
324
|
+
MIT License
|
|
325
|
+
|
|
326
|
+
---
|
|
327
|
+
|
|
328
|
+
**SuperSpec** - Spec-Driven + TDD Discipline = Traceable High-Quality Development
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"archive.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/archive.ts"],"names":[],"mappings":"AAgBA,UAAU,cAAc;IACtB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAWD,wBAAsB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAuHnG"}
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, copyFileSync, rmSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { PALETTE, ICONS, commandHeader, sectionHeader, startSpinner, spinnerSuccess, spinnerFail, isInteractive, selectChange, confirmDangerous, displayCommand, } from '../ui/index.js';
|
|
4
|
+
export async function archiveCommand(id, options) {
|
|
5
|
+
const superspecPath = join(process.cwd(), 'superspec');
|
|
6
|
+
if (!existsSync(superspecPath)) {
|
|
7
|
+
console.log(` ${ICONS.error} ${PALETTE.error('SuperSpec not initialized.')}`);
|
|
8
|
+
console.log(` ${PALETTE.dim('Run:')} ${PALETTE.accent('superspec init')}`);
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
// Interactive mode if no ID provided
|
|
12
|
+
if (!id) {
|
|
13
|
+
if (!isInteractive()) {
|
|
14
|
+
console.log(PALETTE.error('Please specify a change ID to archive.'));
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
const changesPath = join(superspecPath, 'changes');
|
|
18
|
+
const changes = [];
|
|
19
|
+
if (existsSync(changesPath)) {
|
|
20
|
+
const entries = readdirSync(changesPath, { withFileTypes: true });
|
|
21
|
+
for (const entry of entries) {
|
|
22
|
+
if (entry.isDirectory() && entry.name !== 'archive') {
|
|
23
|
+
changes.push(entry.name);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (changes.length === 0) {
|
|
28
|
+
console.log(` ${ICONS.warning} ${PALETTE.warning('No active changes found.')}`);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
id = await selectChange(changes, 'Select a change to archive:');
|
|
32
|
+
}
|
|
33
|
+
const changePath = join(superspecPath, 'changes', id);
|
|
34
|
+
if (!existsSync(changePath)) {
|
|
35
|
+
console.log(` ${ICONS.error} ${PALETTE.error(`Change not found: ${id}`)}`);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
console.log(commandHeader(`archive ${id}`, 'Archive completed change'));
|
|
39
|
+
// Check prerequisites
|
|
40
|
+
const checkSpinner = startSpinner('Checking prerequisites...');
|
|
41
|
+
const checks = checkPrerequisites(changePath);
|
|
42
|
+
if (!checks.valid) {
|
|
43
|
+
spinnerFail(checkSpinner, 'Prerequisites not met');
|
|
44
|
+
console.log(sectionHeader('Issues Found', '❌'));
|
|
45
|
+
console.log();
|
|
46
|
+
for (const issue of checks.issues) {
|
|
47
|
+
console.log(` ${ICONS.error} ${PALETTE.muted(issue)}`);
|
|
48
|
+
}
|
|
49
|
+
console.log();
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
spinnerSuccess(checkSpinner, 'All prerequisites met');
|
|
53
|
+
// Show what will happen
|
|
54
|
+
console.log(sectionHeader('Archive Actions', '📦'));
|
|
55
|
+
console.log();
|
|
56
|
+
const actions = [];
|
|
57
|
+
if (!options.skipSpecs) {
|
|
58
|
+
actions.push('Apply delta specs to main specifications');
|
|
59
|
+
}
|
|
60
|
+
actions.push(`Move ${id} to archive/`);
|
|
61
|
+
for (let i = 0; i < actions.length; i++) {
|
|
62
|
+
console.log(` ${PALETTE.accent(`${i + 1}.`)} ${actions[i]}`);
|
|
63
|
+
}
|
|
64
|
+
console.log();
|
|
65
|
+
// Confirm
|
|
66
|
+
if (!options.yes) {
|
|
67
|
+
console.log(` ${ICONS.warning} ${PALETTE.warning('This action will modify your main specifications.')}`);
|
|
68
|
+
console.log();
|
|
69
|
+
const confirmed = await confirmDangerous({
|
|
70
|
+
message: 'Proceed with archive?',
|
|
71
|
+
});
|
|
72
|
+
if (!confirmed) {
|
|
73
|
+
console.log();
|
|
74
|
+
console.log(` ${PALETTE.dim('Archive cancelled.')}`);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
console.log();
|
|
79
|
+
try {
|
|
80
|
+
// Apply deltas
|
|
81
|
+
if (!options.skipSpecs) {
|
|
82
|
+
await applyDeltas(superspecPath, changePath);
|
|
83
|
+
}
|
|
84
|
+
// Move to archive
|
|
85
|
+
await moveToArchive(superspecPath, changePath, id);
|
|
86
|
+
console.log();
|
|
87
|
+
console.log(` ${PALETTE.subtle('─'.repeat(50))}`);
|
|
88
|
+
console.log();
|
|
89
|
+
console.log(` ${ICONS.success} ${PALETTE.bold(PALETTE.success('Archive complete!'))}`);
|
|
90
|
+
console.log(sectionHeader('Next Steps', '🚀'));
|
|
91
|
+
console.log();
|
|
92
|
+
displayCommand('superspec validate --all', 'Run validation');
|
|
93
|
+
console.log(` ${PALETTE.dim('Verify main specs are updated correctly')}`);
|
|
94
|
+
console.log();
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
console.log();
|
|
98
|
+
console.log(` ${ICONS.error} ${PALETTE.bold(PALETTE.error('Archive failed'))}`);
|
|
99
|
+
console.log(` ${PALETTE.muted(err instanceof Error ? err.message : String(err))}`);
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function checkPrerequisites(changePath) {
|
|
104
|
+
const issues = [];
|
|
105
|
+
// Check required files
|
|
106
|
+
if (!existsSync(join(changePath, 'proposal.md'))) {
|
|
107
|
+
issues.push('Missing proposal.md');
|
|
108
|
+
}
|
|
109
|
+
if (!existsSync(join(changePath, 'design.md'))) {
|
|
110
|
+
issues.push('Missing design.md');
|
|
111
|
+
}
|
|
112
|
+
if (!existsSync(join(changePath, 'specs'))) {
|
|
113
|
+
issues.push('Missing specs/ directory');
|
|
114
|
+
}
|
|
115
|
+
// Check tasks completion (if tasks.md exists)
|
|
116
|
+
const tasksPath = join(changePath, 'tasks.md');
|
|
117
|
+
if (existsSync(tasksPath)) {
|
|
118
|
+
const content = readFileSync(tasksPath, 'utf-8');
|
|
119
|
+
const incomplete = (content.match(/^- \[ \]/gm) ?? []).length;
|
|
120
|
+
if (incomplete > 0) {
|
|
121
|
+
issues.push(`${incomplete} incomplete tasks in tasks.md`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return { valid: issues.length === 0, issues };
|
|
125
|
+
}
|
|
126
|
+
async function applyDeltas(superspecPath, changePath) {
|
|
127
|
+
const specsPath = join(changePath, 'specs');
|
|
128
|
+
const mainSpecsPath = join(superspecPath, 'specs');
|
|
129
|
+
if (!existsSync(specsPath)) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const spinner = startSpinner('Applying delta specs...');
|
|
133
|
+
const entries = readdirSync(specsPath, { withFileTypes: true });
|
|
134
|
+
let appliedCount = 0;
|
|
135
|
+
for (const entry of entries) {
|
|
136
|
+
if (entry.isDirectory()) {
|
|
137
|
+
const deltaSpecPath = join(specsPath, entry.name, 'spec.md');
|
|
138
|
+
if (existsSync(deltaSpecPath)) {
|
|
139
|
+
const content = readFileSync(deltaSpecPath, 'utf-8');
|
|
140
|
+
const operations = parseDeltaOperations(content);
|
|
141
|
+
if (operations.length > 0) {
|
|
142
|
+
await applyDeltaToMainSpec(mainSpecsPath, entry.name, operations, content);
|
|
143
|
+
appliedCount++;
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
// No delta markers - this is a new spec, copy as-is
|
|
147
|
+
const targetDir = join(mainSpecsPath, entry.name);
|
|
148
|
+
const targetPath = join(targetDir, 'spec.md');
|
|
149
|
+
if (!existsSync(targetDir)) {
|
|
150
|
+
mkdirSync(targetDir, { recursive: true });
|
|
151
|
+
}
|
|
152
|
+
copyFileSync(deltaSpecPath, targetPath);
|
|
153
|
+
appliedCount++;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
spinnerSuccess(spinner, `Applied ${appliedCount} specifications`);
|
|
159
|
+
}
|
|
160
|
+
function parseDeltaOperations(content) {
|
|
161
|
+
const operations = [];
|
|
162
|
+
const lines = content.split('\n');
|
|
163
|
+
let currentSection = null;
|
|
164
|
+
let currentReq = '';
|
|
165
|
+
let currentContent = [];
|
|
166
|
+
for (const line of lines) {
|
|
167
|
+
// Detect section headers
|
|
168
|
+
if (line.match(/^## ADDED Requirements/i)) {
|
|
169
|
+
currentSection = 'added';
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
if (line.match(/^## MODIFIED Requirements/i)) {
|
|
173
|
+
currentSection = 'modified';
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (line.match(/^## REMOVED Requirements/i)) {
|
|
177
|
+
currentSection = 'removed';
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if (line.match(/^## RENAMED Requirements/i)) {
|
|
181
|
+
currentSection = 'renamed';
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
// Detect new requirement
|
|
185
|
+
const reqMatch = line.match(/^### Requirement: (.+)$/);
|
|
186
|
+
if (reqMatch?.[1] && currentSection) {
|
|
187
|
+
// Save previous if exists
|
|
188
|
+
if (currentReq) {
|
|
189
|
+
operations.push({
|
|
190
|
+
type: currentSection,
|
|
191
|
+
requirement: currentReq,
|
|
192
|
+
content: currentContent.join('\n'),
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
currentReq = reqMatch[1];
|
|
196
|
+
currentContent = [line];
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
// Detect renamed format
|
|
200
|
+
if (currentSection === 'renamed') {
|
|
201
|
+
const fromMatch = line.match(/^- FROM:.*`### Requirement: (.+)`/);
|
|
202
|
+
const toMatch = line.match(/^- TO:.*`### Requirement: (.+)`/);
|
|
203
|
+
if (fromMatch?.[1]) {
|
|
204
|
+
currentReq = fromMatch[1];
|
|
205
|
+
}
|
|
206
|
+
if (toMatch?.[1] && currentReq) {
|
|
207
|
+
operations.push({
|
|
208
|
+
type: 'renamed',
|
|
209
|
+
requirement: currentReq,
|
|
210
|
+
fromName: currentReq,
|
|
211
|
+
toName: toMatch[1],
|
|
212
|
+
});
|
|
213
|
+
currentReq = '';
|
|
214
|
+
}
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
// Collect content
|
|
218
|
+
if (currentReq && currentSection) {
|
|
219
|
+
currentContent.push(line);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
// Don't forget last one
|
|
223
|
+
if (currentReq && currentSection) {
|
|
224
|
+
operations.push({
|
|
225
|
+
type: currentSection,
|
|
226
|
+
requirement: currentReq,
|
|
227
|
+
content: currentContent.join('\n'),
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
return operations;
|
|
231
|
+
}
|
|
232
|
+
async function applyDeltaToMainSpec(mainSpecsPath, capability, operations, fullContent) {
|
|
233
|
+
const mainSpecDir = join(mainSpecsPath, capability);
|
|
234
|
+
const mainSpecPath = join(mainSpecDir, 'spec.md');
|
|
235
|
+
// Create directory if needed
|
|
236
|
+
if (!existsSync(mainSpecDir)) {
|
|
237
|
+
mkdirSync(mainSpecDir, { recursive: true });
|
|
238
|
+
}
|
|
239
|
+
let mainContent = '';
|
|
240
|
+
if (existsSync(mainSpecPath)) {
|
|
241
|
+
mainContent = readFileSync(mainSpecPath, 'utf-8');
|
|
242
|
+
}
|
|
243
|
+
// Sort operations: RENAMED → REMOVED → MODIFIED → ADDED
|
|
244
|
+
const sorted = [...operations].sort((a, b) => {
|
|
245
|
+
const order = { renamed: 0, removed: 1, modified: 2, added: 3 };
|
|
246
|
+
return order[a.type] - order[b.type];
|
|
247
|
+
});
|
|
248
|
+
for (const op of sorted) {
|
|
249
|
+
switch (op.type) {
|
|
250
|
+
case 'renamed':
|
|
251
|
+
if (op.fromName && op.toName) {
|
|
252
|
+
mainContent = mainContent.replace(`### Requirement: ${op.fromName}`, `### Requirement: ${op.toName}`);
|
|
253
|
+
console.log(` ${PALETTE.cyan('→')} Renamed: ${op.fromName} → ${op.toName}`);
|
|
254
|
+
}
|
|
255
|
+
break;
|
|
256
|
+
case 'removed':
|
|
257
|
+
// Remove the requirement section
|
|
258
|
+
const removePattern = new RegExp(`### Requirement: ${escapeRegex(op.requirement)}[\\s\\S]*?(?=### Requirement:|$)`, 'g');
|
|
259
|
+
mainContent = mainContent.replace(removePattern, '');
|
|
260
|
+
console.log(` ${PALETTE.error('−')} Removed: ${op.requirement}`);
|
|
261
|
+
break;
|
|
262
|
+
case 'modified':
|
|
263
|
+
// Replace existing requirement
|
|
264
|
+
if (op.content) {
|
|
265
|
+
const modifyPattern = new RegExp(`### Requirement: ${escapeRegex(op.requirement)}[\\s\\S]*?(?=### Requirement:|$)`, 'g');
|
|
266
|
+
mainContent = mainContent.replace(modifyPattern, op.content.trim() + '\n\n');
|
|
267
|
+
console.log(` ${PALETTE.warning('~')} Modified: ${op.requirement}`);
|
|
268
|
+
}
|
|
269
|
+
break;
|
|
270
|
+
case 'added':
|
|
271
|
+
// Append to end of requirements section
|
|
272
|
+
if (op.content) {
|
|
273
|
+
mainContent = mainContent.trimEnd() + '\n\n' + op.content.trim() + '\n';
|
|
274
|
+
console.log(` ${PALETTE.success('+')} Added: ${op.requirement}`);
|
|
275
|
+
}
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
// Write updated spec
|
|
280
|
+
writeFileSync(mainSpecPath, mainContent.trim() + '\n');
|
|
281
|
+
}
|
|
282
|
+
function escapeRegex(str) {
|
|
283
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
284
|
+
}
|
|
285
|
+
async function moveToArchive(superspecPath, changePath, id) {
|
|
286
|
+
const date = new Date().toISOString().split('T')[0];
|
|
287
|
+
const archiveId = `${date}-${id}`;
|
|
288
|
+
const archivePath = join(superspecPath, 'changes', 'archive', archiveId);
|
|
289
|
+
const spinner = startSpinner(`Moving to archive/${archiveId}...`);
|
|
290
|
+
// Create archive directory
|
|
291
|
+
mkdirSync(archivePath, { recursive: true });
|
|
292
|
+
// Move all files
|
|
293
|
+
const entries = readdirSync(changePath, { withFileTypes: true });
|
|
294
|
+
for (const entry of entries) {
|
|
295
|
+
const srcPath = join(changePath, entry.name);
|
|
296
|
+
const destPath = join(archivePath, entry.name);
|
|
297
|
+
if (entry.isDirectory()) {
|
|
298
|
+
copyDirSync(srcPath, destPath);
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
copyFileSync(srcPath, destPath);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
// Remove original
|
|
305
|
+
rmSync(changePath, { recursive: true });
|
|
306
|
+
spinnerSuccess(spinner, `Archived to ${archiveId}`);
|
|
307
|
+
}
|
|
308
|
+
function copyDirSync(src, dest) {
|
|
309
|
+
mkdirSync(dest, { recursive: true });
|
|
310
|
+
const entries = readdirSync(src, { withFileTypes: true });
|
|
311
|
+
for (const entry of entries) {
|
|
312
|
+
const srcPath = join(src, entry.name);
|
|
313
|
+
const destPath = join(dest, entry.name);
|
|
314
|
+
if (entry.isDirectory()) {
|
|
315
|
+
copyDirSync(srcPath, destPath);
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
copyFileSync(srcPath, destPath);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
//# sourceMappingURL=archive.js.map
|