@applica-software-guru/sdd-core 1.0.0 → 1.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/agent-defaults.d.ts +3 -0
- package/dist/agent/agent-defaults.d.ts.map +1 -0
- package/dist/agent/agent-defaults.js +13 -0
- package/dist/agent/agent-defaults.js.map +1 -0
- package/dist/agent/agent-runner.d.ts +9 -0
- package/dist/agent/agent-runner.d.ts.map +1 -0
- package/dist/agent/agent-runner.js +43 -0
- package/dist/agent/agent-runner.js.map +1 -0
- package/dist/index.d.ts +10 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -1
- package/dist/index.js.map +1 -1
- package/dist/prompt/apply-prompt-generator.d.ts +3 -0
- package/dist/prompt/apply-prompt-generator.d.ts.map +1 -0
- package/dist/prompt/apply-prompt-generator.js +48 -0
- package/dist/prompt/apply-prompt-generator.js.map +1 -0
- package/dist/prompt/prompt-generator.d.ts.map +1 -1
- package/dist/prompt/prompt-generator.js +7 -11
- package/dist/prompt/prompt-generator.js.map +1 -1
- package/dist/scaffold/init.d.ts +1 -1
- package/dist/scaffold/init.d.ts.map +1 -1
- package/dist/scaffold/init.js +10 -29
- package/dist/scaffold/init.js.map +1 -1
- package/dist/scaffold/skill-adapters.d.ts +39 -0
- package/dist/scaffold/skill-adapters.d.ts.map +1 -0
- package/dist/scaffold/skill-adapters.js +224 -0
- package/dist/scaffold/skill-adapters.js.map +1 -0
- package/dist/scaffold/templates.d.ts +5 -1
- package/dist/scaffold/templates.d.ts.map +1 -1
- package/dist/scaffold/templates.js +203 -55
- package/dist/scaffold/templates.js.map +1 -1
- package/dist/sdd.d.ts +7 -3
- package/dist/sdd.d.ts.map +1 -1
- package/dist/sdd.js +36 -18
- package/dist/sdd.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/agent/agent-defaults.ts +12 -0
- package/src/agent/agent-runner.ts +54 -0
- package/src/index.ts +17 -5
- package/src/prompt/apply-prompt-generator.ts +58 -0
- package/src/prompt/prompt-generator.ts +8 -18
- package/src/scaffold/init.ts +17 -38
- package/src/scaffold/skill-adapters.ts +322 -0
- package/src/scaffold/templates.ts +207 -54
- package/src/sdd.ts +57 -31
- package/src/types.ts +2 -0
- package/tests/apply.test.ts +119 -0
- package/tests/integration.test.ts +94 -51
- package/dist/delta/hasher.d.ts +0 -2
- package/dist/delta/hasher.d.ts.map +0 -1
- package/dist/delta/hasher.js +0 -8
- package/dist/delta/hasher.js.map +0 -1
- package/dist/lock/lock-manager.d.ts +0 -6
- package/dist/lock/lock-manager.d.ts.map +0 -1
- package/dist/lock/lock-manager.js +0 -39
- package/dist/lock/lock-manager.js.map +0 -1
|
@@ -17,13 +17,30 @@ export interface ProjectInfo {
|
|
|
17
17
|
description: string;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
export const
|
|
20
|
+
export const SKILL_MD_TEMPLATE = `---
|
|
21
|
+
name: sdd
|
|
22
|
+
description: >
|
|
23
|
+
Story Driven Development workflow. Use when working in a project
|
|
24
|
+
with .sdd/config.yaml, or when the user mentions SDD, sdd sync,
|
|
25
|
+
story driven development, or spec-driven development.
|
|
26
|
+
license: MIT
|
|
27
|
+
compatibility: Requires sdd CLI (npm i -g @applica-software-guru/sdd)
|
|
28
|
+
allowed-tools: Bash(sdd:*) Read Glob Grep
|
|
29
|
+
metadata:
|
|
30
|
+
author: applica-software-guru
|
|
31
|
+
version: "1.0"
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
# SDD — Story Driven Development
|
|
21
35
|
|
|
22
|
-
|
|
23
|
-
|
|
36
|
+
## Detection
|
|
37
|
+
|
|
38
|
+
This project uses SDD if \`.sdd/config.yaml\` exists in the project root.
|
|
24
39
|
|
|
25
40
|
## Workflow
|
|
26
41
|
|
|
42
|
+
Follow this loop every time you work on an SDD project:
|
|
43
|
+
|
|
27
44
|
1. Run \`sdd bug open\` — check if there are open bugs to fix first
|
|
28
45
|
2. If there are open bugs, fix the code/docs, then run \`sdd mark-bug-resolved\`
|
|
29
46
|
3. Run \`sdd cr pending\` — check if there are change requests to process
|
|
@@ -80,7 +97,142 @@ Delete the related code in \`code/\`, then run \`sdd mark-synced <file>\` (the d
|
|
|
80
97
|
6. Respect all constraints in \`## Agent Notes\` sections (if present)
|
|
81
98
|
7. Do not edit files inside \`.sdd/\` manually
|
|
82
99
|
|
|
83
|
-
##
|
|
100
|
+
## Project structure
|
|
101
|
+
|
|
102
|
+
- \`product/\` — What to build (vision, users, features)
|
|
103
|
+
- \`system/\` — How to build it (entities, architecture, tech stack, interfaces)
|
|
104
|
+
- \`code/\` — All generated source code goes here
|
|
105
|
+
- \`change-requests/\` — Change requests to the documentation
|
|
106
|
+
- \`bugs/\` — Bug reports
|
|
107
|
+
- \`.sdd/\` — Project config and sync state (do not edit)
|
|
108
|
+
|
|
109
|
+
## References
|
|
110
|
+
|
|
111
|
+
For detailed information on specific topics, see:
|
|
112
|
+
|
|
113
|
+
- [File format and status lifecycle](references/file-format.md)
|
|
114
|
+
- [Change Requests workflow](references/change-requests.md)
|
|
115
|
+
- [Bug workflow](references/bugs.md)
|
|
116
|
+
`;
|
|
117
|
+
|
|
118
|
+
export const SKILL_UI_MD_TEMPLATE = `---
|
|
119
|
+
name: sdd-ui
|
|
120
|
+
description: >
|
|
121
|
+
UI Component Editor workflow. Use when the user wants to implement a React component
|
|
122
|
+
from a screenshot in an SDD project, iterating visually with live preview.
|
|
123
|
+
license: MIT
|
|
124
|
+
compatibility: >
|
|
125
|
+
Requires sdd CLI (npm i -g @applica-software-guru/sdd).
|
|
126
|
+
Requires Playwright MCP configured in Claude Code
|
|
127
|
+
(e.g. @playwright/mcp or @executeautomation/playwright-mcp).
|
|
128
|
+
allowed-tools: Bash(sdd:*) Read Glob Grep Edit Write mcp__playwright__screenshot mcp__playwright__navigate mcp__playwright__click
|
|
129
|
+
metadata:
|
|
130
|
+
author: applica-software-guru
|
|
131
|
+
version: "1.1"
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
# SDD UI — Visual Component Editor
|
|
135
|
+
|
|
136
|
+
## Purpose
|
|
137
|
+
|
|
138
|
+
Use this workflow when implementing a React component from a screenshot reference in an SDD project.
|
|
139
|
+
The split-panel editor shows the spec screenshot on the left and the live component on the right,
|
|
140
|
+
so you can iterate visually until they match.
|
|
141
|
+
|
|
142
|
+
## Prerequisites
|
|
143
|
+
|
|
144
|
+
- \`sdd\` CLI installed globally
|
|
145
|
+
- Playwright MCP configured in Claude Code settings
|
|
146
|
+
- e.g. \`@playwright/mcp\` or \`@executeautomation/playwright-mcp\`
|
|
147
|
+
- If not configured, inform the user and stop — visual feedback won't work without it
|
|
148
|
+
|
|
149
|
+
## Workflow
|
|
150
|
+
|
|
151
|
+
### Step 1 — Read the spec
|
|
152
|
+
|
|
153
|
+
Read the SDD feature file to understand what the component should look like and do.
|
|
154
|
+
Look for any screenshot paths referenced in the feature doc.
|
|
155
|
+
|
|
156
|
+
### Step 2 — Launch the editor
|
|
157
|
+
|
|
158
|
+
\`\`\`bash
|
|
159
|
+
# Single screenshot — detached (recommended when run by an agent)
|
|
160
|
+
sdd ui launch-editor LoginForm \\
|
|
161
|
+
--screenshot product/features/auth/login.png \\
|
|
162
|
+
--detach
|
|
163
|
+
|
|
164
|
+
# Multiple screenshots (e.g. desktop + mobile)
|
|
165
|
+
sdd ui launch-editor LoginForm \\
|
|
166
|
+
--screenshot product/features/auth/login-desktop.png \\
|
|
167
|
+
--screenshot product/features/auth/login-mobile.png \\
|
|
168
|
+
--detach
|
|
169
|
+
\`\`\`
|
|
170
|
+
|
|
171
|
+
The command will:
|
|
172
|
+
- Scaffold \`code/components/LoginForm.tsx\` if it doesn't exist
|
|
173
|
+
- Print the exact component file path to edit
|
|
174
|
+
- Start the editor at \`http://localhost:5174\`
|
|
175
|
+
|
|
176
|
+
With \`--detach\` the process runs in background and the terminal is immediately free.
|
|
177
|
+
Without \`--detach\` it runs in foreground (use Ctrl+C to stop).
|
|
178
|
+
|
|
179
|
+
With multiple screenshots, the left panel shows a tab per screenshot.
|
|
180
|
+
With a single screenshot, no tab bar is shown.
|
|
181
|
+
|
|
182
|
+
### Step 3 — Implement the component
|
|
183
|
+
|
|
184
|
+
Edit the file printed by \`sdd ui launch-editor\` (e.g. \`code/components/LoginForm.tsx\`).
|
|
185
|
+
|
|
186
|
+
Write a React component that matches the screenshot. Use standard HTML/CSS or inline styles —
|
|
187
|
+
no external UI library unless the project already uses one.
|
|
188
|
+
|
|
189
|
+
Vite HMR will update the right panel automatically on every save.
|
|
190
|
+
|
|
191
|
+
### Step 4 — Visual check with Playwright
|
|
192
|
+
|
|
193
|
+
After each save, screenshot the live preview and compare it with the spec:
|
|
194
|
+
|
|
195
|
+
\`\`\`
|
|
196
|
+
mcp__playwright__navigate http://localhost:5174
|
|
197
|
+
mcp__playwright__screenshot
|
|
198
|
+
\`\`\`
|
|
199
|
+
|
|
200
|
+
The left panel already shows the spec screenshot for direct comparison.
|
|
201
|
+
Note differences in layout, spacing, typography, colors, and component structure.
|
|
202
|
+
|
|
203
|
+
### Step 5 — Iterate
|
|
204
|
+
|
|
205
|
+
Edit component → Playwright screenshot → compare → repeat until the preview matches the spec.
|
|
206
|
+
|
|
207
|
+
### Step 6 — Finalize
|
|
208
|
+
|
|
209
|
+
\`\`\`bash
|
|
210
|
+
sdd ui stop
|
|
211
|
+
sdd mark-synced product/features/auth/login.md
|
|
212
|
+
git add -A && git commit -m "sdd sync: implement LoginForm component"
|
|
213
|
+
\`\`\`
|
|
214
|
+
|
|
215
|
+
## Notes
|
|
216
|
+
|
|
217
|
+
- The component file is permanent — it lives in \`code/components/\` and is part of your project
|
|
218
|
+
- Port \`5174\` by default (not \`5173\`) to avoid conflicts with the user's app dev server
|
|
219
|
+
- If the component needs props, scaffold it with hardcoded sample data for the preview
|
|
220
|
+
|
|
221
|
+
## Troubleshooting
|
|
222
|
+
|
|
223
|
+
**Playwright MCP not configured:**
|
|
224
|
+
Stop and ask the user to add it to their Claude Code MCP settings before continuing.
|
|
225
|
+
|
|
226
|
+
**Component import fails in preview:**
|
|
227
|
+
Check that the component file has a valid default export and no TypeScript errors.
|
|
228
|
+
|
|
229
|
+
**Port already in use:**
|
|
230
|
+
\`sdd ui launch-editor LoginForm --screenshot login.png --port 5175\`
|
|
231
|
+
`;
|
|
232
|
+
|
|
233
|
+
export const FILE_FORMAT_REFERENCE = `# File Format and Status Lifecycle
|
|
234
|
+
|
|
235
|
+
## YAML Frontmatter
|
|
84
236
|
|
|
85
237
|
Every \`.md\` file in \`product/\` and \`system/\` must start with this YAML frontmatter:
|
|
86
238
|
|
|
@@ -94,13 +246,20 @@ version: "1.0"
|
|
|
94
246
|
---
|
|
95
247
|
\`\`\`
|
|
96
248
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
-
|
|
103
|
-
|
|
249
|
+
## Status values
|
|
250
|
+
|
|
251
|
+
- **\`new\`** — new file, needs to be implemented
|
|
252
|
+
- **\`changed\`** — modified since last sync, code needs updating
|
|
253
|
+
- **\`deleted\`** — feature to be removed, agent should delete related code
|
|
254
|
+
- **\`synced\`** — already implemented, up to date
|
|
255
|
+
|
|
256
|
+
## Version
|
|
257
|
+
|
|
258
|
+
Patch-bump on each edit: 1.0 → 1.1 → 1.2
|
|
259
|
+
|
|
260
|
+
## Last-modified
|
|
261
|
+
|
|
262
|
+
ISO 8601 datetime, updated on each edit.
|
|
104
263
|
|
|
105
264
|
## How sync works
|
|
106
265
|
|
|
@@ -112,11 +271,37 @@ version: "1.0"
|
|
|
112
271
|
|
|
113
272
|
This is why **committing after every mark-synced is mandatory** — the git history is what SDD uses to detect changes.
|
|
114
273
|
|
|
115
|
-
##
|
|
274
|
+
## UX and screenshots
|
|
275
|
+
|
|
276
|
+
When a feature has UX mockups or screenshots, place them next to the feature doc:
|
|
277
|
+
|
|
278
|
+
- **Simple feature** (no screenshots): \`product/features/auth.md\`
|
|
279
|
+
- **Feature with screenshots**: use a folder with \`index.md\`:
|
|
280
|
+
|
|
281
|
+
\`\`\`
|
|
282
|
+
product/features/auth/
|
|
283
|
+
index.md ← feature doc
|
|
284
|
+
login.png ← screenshot
|
|
285
|
+
register.png ← screenshot
|
|
286
|
+
\`\`\`
|
|
287
|
+
|
|
288
|
+
Reference images in the markdown with relative paths:
|
|
289
|
+
|
|
290
|
+
\`\`\`markdown
|
|
291
|
+
## UX
|
|
292
|
+
|
|
293
|
+

|
|
294
|
+

|
|
295
|
+
\`\`\`
|
|
296
|
+
|
|
297
|
+
Both formats work — use a folder only when you have screenshots or multiple files for a feature.
|
|
298
|
+
`;
|
|
299
|
+
|
|
300
|
+
export const CHANGE_REQUESTS_REFERENCE = `# Change Requests
|
|
116
301
|
|
|
117
302
|
Change Requests (CRs) are markdown files in \`change-requests/\` that describe modifications to the documentation.
|
|
118
303
|
|
|
119
|
-
|
|
304
|
+
## CR format
|
|
120
305
|
|
|
121
306
|
\`\`\`yaml
|
|
122
307
|
---
|
|
@@ -129,24 +314,25 @@ created-at: "2025-01-01T00:00:00.000Z"
|
|
|
129
314
|
|
|
130
315
|
- **status**: \`draft\` (pending) or \`applied\` (already processed)
|
|
131
316
|
|
|
132
|
-
|
|
317
|
+
## CR workflow
|
|
133
318
|
|
|
134
319
|
1. Check for pending CRs: \`sdd cr pending\`
|
|
135
320
|
2. Read each pending CR and apply the described changes to the documentation files (marking them as \`new\`, \`changed\`, or \`deleted\`)
|
|
136
321
|
3. After applying a CR to the docs, mark it: \`sdd mark-cr-applied change-requests/CR-001.md\`
|
|
137
322
|
4. Then run \`sdd sync\` to implement the code changes
|
|
138
323
|
|
|
139
|
-
|
|
324
|
+
## CR commands
|
|
140
325
|
|
|
141
326
|
- \`sdd cr list\` — See all change requests and their status
|
|
142
327
|
- \`sdd cr pending\` — Show only draft CRs to process
|
|
143
328
|
- \`sdd mark-cr-applied [files...]\` — Mark CRs as applied after updating the docs
|
|
329
|
+
`;
|
|
144
330
|
|
|
145
|
-
|
|
331
|
+
export const BUGS_REFERENCE = `# Bugs
|
|
146
332
|
|
|
147
333
|
Bugs are markdown files in \`bugs/\` that describe problems found in the codebase.
|
|
148
334
|
|
|
149
|
-
|
|
335
|
+
## Bug format
|
|
150
336
|
|
|
151
337
|
\`\`\`yaml
|
|
152
338
|
---
|
|
@@ -159,54 +345,21 @@ created-at: "2025-01-01T00:00:00.000Z"
|
|
|
159
345
|
|
|
160
346
|
- **status**: \`open\` (needs fixing) or \`resolved\` (already fixed)
|
|
161
347
|
|
|
162
|
-
|
|
348
|
+
## Bug workflow
|
|
163
349
|
|
|
164
350
|
1. Check for open bugs: \`sdd bug open\`
|
|
165
351
|
2. Read each open bug and fix the code and/or documentation
|
|
166
352
|
3. After fixing a bug, mark it: \`sdd mark-bug-resolved bugs/BUG-001.md\`
|
|
167
353
|
4. Commit the fix
|
|
168
354
|
|
|
169
|
-
|
|
355
|
+
## Bug commands
|
|
170
356
|
|
|
171
357
|
- \`sdd bug list\` — See all bugs and their status
|
|
172
358
|
- \`sdd bug open\` — Show only open bugs to fix
|
|
173
359
|
- \`sdd mark-bug-resolved [files...]\` — Mark bugs as resolved after fixing
|
|
174
|
-
|
|
175
|
-
## UX and screenshots
|
|
176
|
-
|
|
177
|
-
When a feature has UX mockups or screenshots, place them next to the feature doc:
|
|
178
|
-
|
|
179
|
-
- **Simple feature** (no screenshots): \`product/features/auth.md\`
|
|
180
|
-
- **Feature with screenshots**: use a folder with \`index.md\`:
|
|
181
|
-
|
|
182
|
-
\`\`\`
|
|
183
|
-
product/features/auth/
|
|
184
|
-
index.md ← feature doc
|
|
185
|
-
login.png ← screenshot
|
|
186
|
-
register.png ← screenshot
|
|
187
|
-
\`\`\`
|
|
188
|
-
|
|
189
|
-
Reference images in the markdown with relative paths:
|
|
190
|
-
|
|
191
|
-
\`\`\`markdown
|
|
192
|
-
## UX
|
|
193
|
-
|
|
194
|
-

|
|
195
|
-

|
|
196
|
-
\`\`\`
|
|
197
|
-
|
|
198
|
-
Both formats work — use a folder only when you have screenshots or multiple files for a feature.
|
|
199
|
-
|
|
200
|
-
## Project structure
|
|
201
|
-
|
|
202
|
-
- \`product/\` — What to build (vision, users, features)
|
|
203
|
-
- \`system/\` — How to build it (entities, architecture, tech stack, interfaces)
|
|
204
|
-
- \`code/\` — All generated source code goes here
|
|
205
|
-
- \`change-requests/\` — Change requests to the documentation
|
|
206
|
-
- \`bugs/\` — Bug reports
|
|
207
|
-
- \`.sdd/\` — Project config and sync state (do not edit)
|
|
208
360
|
`;
|
|
209
361
|
|
|
210
|
-
export const EMPTY_LOCK_TEMPLATE =
|
|
362
|
+
export const EMPTY_LOCK_TEMPLATE =
|
|
363
|
+
() => `synced-at: "${new Date().toISOString()}"
|
|
211
364
|
files: {}
|
|
212
365
|
`;
|
package/src/sdd.ts
CHANGED
|
@@ -1,15 +1,22 @@
|
|
|
1
|
-
import { readFile, writeFile } from
|
|
2
|
-
import { resolve } from
|
|
3
|
-
import type { StoryStatus, ValidationResult, SDDConfig, ChangeRequest, Bug } from
|
|
4
|
-
import { ProjectNotInitializedError } from
|
|
5
|
-
import { parseAllStoryFiles } from
|
|
6
|
-
import { generatePrompt } from
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import
|
|
1
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import type { StoryStatus, ValidationResult, SDDConfig, ChangeRequest, Bug } from "./types.js";
|
|
4
|
+
import { ProjectNotInitializedError } from "./errors.js";
|
|
5
|
+
import { parseAllStoryFiles } from "./parser/story-parser.js";
|
|
6
|
+
import { generatePrompt } from "./prompt/prompt-generator.js";
|
|
7
|
+
import { generateApplyPrompt } from "./prompt/apply-prompt-generator.js";
|
|
8
|
+
import { validate } from "./validate/validator.js";
|
|
9
|
+
import { initProject } from "./scaffold/init.js";
|
|
10
|
+
import { isSDDProject, readConfig, writeConfig } from "./config/config-manager.js";
|
|
11
|
+
import { parseAllCRFiles } from "./parser/cr-parser.js";
|
|
12
|
+
import { parseAllBugFiles } from "./parser/bug-parser.js";
|
|
13
|
+
import type { ProjectInfo } from "./scaffold/templates.js";
|
|
14
|
+
import {
|
|
15
|
+
listSupportedAdapters,
|
|
16
|
+
syncSkillAdapters,
|
|
17
|
+
type SyncAdaptersOptions,
|
|
18
|
+
type SyncAdaptersResult,
|
|
19
|
+
} from "./scaffold/skill-adapters.js";
|
|
13
20
|
|
|
14
21
|
export class SDD {
|
|
15
22
|
private root: string;
|
|
@@ -22,6 +29,15 @@ export class SDD {
|
|
|
22
29
|
return initProject(this.root, info);
|
|
23
30
|
}
|
|
24
31
|
|
|
32
|
+
async syncAdapters(options?: SyncAdaptersOptions): Promise<SyncAdaptersResult> {
|
|
33
|
+
this.ensureInitialized();
|
|
34
|
+
return syncSkillAdapters(this.root, options);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
supportedAdapters(): string[] {
|
|
38
|
+
return listSupportedAdapters();
|
|
39
|
+
}
|
|
40
|
+
|
|
25
41
|
async config(): Promise<SDDConfig> {
|
|
26
42
|
this.ensureInitialized();
|
|
27
43
|
return readConfig(this.root);
|
|
@@ -36,15 +52,15 @@ export class SDD {
|
|
|
36
52
|
relativePath: f.relativePath,
|
|
37
53
|
status: f.frontmatter.status,
|
|
38
54
|
version: f.frontmatter.version,
|
|
39
|
-
lastModified: f.frontmatter[
|
|
55
|
+
lastModified: f.frontmatter["last-modified"],
|
|
40
56
|
})),
|
|
41
57
|
};
|
|
42
58
|
}
|
|
43
59
|
|
|
44
|
-
async pending(): Promise<import(
|
|
60
|
+
async pending(): Promise<import("./types.js").StoryFile[]> {
|
|
45
61
|
this.ensureInitialized();
|
|
46
62
|
const files = await parseAllStoryFiles(this.root);
|
|
47
|
-
return files.filter((f) => f.frontmatter.status !==
|
|
63
|
+
return files.filter((f) => f.frontmatter.status !== "synced");
|
|
48
64
|
}
|
|
49
65
|
|
|
50
66
|
async sync(): Promise<string> {
|
|
@@ -52,6 +68,16 @@ export class SDD {
|
|
|
52
68
|
return generatePrompt(pending, this.root);
|
|
53
69
|
}
|
|
54
70
|
|
|
71
|
+
async applyPrompt(): Promise<string | null> {
|
|
72
|
+
this.ensureInitialized();
|
|
73
|
+
const [bugs, changeRequests, pendingFiles] = await Promise.all([
|
|
74
|
+
this.openBugs(),
|
|
75
|
+
this.pendingChangeRequests(),
|
|
76
|
+
this.pending(),
|
|
77
|
+
]);
|
|
78
|
+
return generateApplyPrompt(bugs, changeRequests, pendingFiles, this.root);
|
|
79
|
+
}
|
|
80
|
+
|
|
55
81
|
async validate(): Promise<ValidationResult> {
|
|
56
82
|
this.ensureInitialized();
|
|
57
83
|
const files = await parseAllStoryFiles(this.root);
|
|
@@ -65,21 +91,21 @@ export class SDD {
|
|
|
65
91
|
|
|
66
92
|
for (const file of files) {
|
|
67
93
|
const { status } = file.frontmatter;
|
|
68
|
-
if (status ===
|
|
94
|
+
if (status === "synced") continue;
|
|
69
95
|
if (paths && paths.length > 0 && !paths.includes(file.relativePath)) continue;
|
|
70
96
|
|
|
71
97
|
const absPath = resolve(this.root, file.relativePath);
|
|
72
98
|
|
|
73
|
-
if (status ===
|
|
99
|
+
if (status === "deleted") {
|
|
74
100
|
// File marked for deletion — remove it
|
|
75
|
-
const { unlink } = await import(
|
|
101
|
+
const { unlink } = await import("node:fs/promises");
|
|
76
102
|
await unlink(absPath);
|
|
77
103
|
marked.push(`${file.relativePath} (removed)`);
|
|
78
104
|
} else {
|
|
79
105
|
// new or changed → synced
|
|
80
|
-
const content = await readFile(absPath,
|
|
81
|
-
const updated = content.replace(/^status:\s*(new|changed)/m,
|
|
82
|
-
await writeFile(absPath, updated,
|
|
106
|
+
const content = await readFile(absPath, "utf-8");
|
|
107
|
+
const updated = content.replace(/^status:\s*(new|changed)/m, "status: synced");
|
|
108
|
+
await writeFile(absPath, updated, "utf-8");
|
|
83
109
|
marked.push(file.relativePath);
|
|
84
110
|
}
|
|
85
111
|
}
|
|
@@ -94,7 +120,7 @@ export class SDD {
|
|
|
94
120
|
|
|
95
121
|
async pendingChangeRequests(): Promise<ChangeRequest[]> {
|
|
96
122
|
const all = await this.changeRequests();
|
|
97
|
-
return all.filter((cr) => cr.frontmatter.status ===
|
|
123
|
+
return all.filter((cr) => cr.frontmatter.status === "draft");
|
|
98
124
|
}
|
|
99
125
|
|
|
100
126
|
async markCRApplied(paths?: string[]): Promise<string[]> {
|
|
@@ -103,13 +129,13 @@ export class SDD {
|
|
|
103
129
|
const marked: string[] = [];
|
|
104
130
|
|
|
105
131
|
for (const cr of all) {
|
|
106
|
-
if (cr.frontmatter.status ===
|
|
132
|
+
if (cr.frontmatter.status === "applied") continue;
|
|
107
133
|
if (paths && paths.length > 0 && !paths.includes(cr.relativePath)) continue;
|
|
108
134
|
|
|
109
135
|
const absPath = resolve(this.root, cr.relativePath);
|
|
110
|
-
const content = await readFile(absPath,
|
|
111
|
-
const updated = content.replace(/^status:\s*draft/m,
|
|
112
|
-
await writeFile(absPath, updated,
|
|
136
|
+
const content = await readFile(absPath, "utf-8");
|
|
137
|
+
const updated = content.replace(/^status:\s*draft/m, "status: applied");
|
|
138
|
+
await writeFile(absPath, updated, "utf-8");
|
|
113
139
|
marked.push(cr.relativePath);
|
|
114
140
|
}
|
|
115
141
|
|
|
@@ -123,7 +149,7 @@ export class SDD {
|
|
|
123
149
|
|
|
124
150
|
async openBugs(): Promise<Bug[]> {
|
|
125
151
|
const all = await this.bugs();
|
|
126
|
-
return all.filter((b) => b.frontmatter.status ===
|
|
152
|
+
return all.filter((b) => b.frontmatter.status === "open");
|
|
127
153
|
}
|
|
128
154
|
|
|
129
155
|
async markBugResolved(paths?: string[]): Promise<string[]> {
|
|
@@ -132,13 +158,13 @@ export class SDD {
|
|
|
132
158
|
const marked: string[] = [];
|
|
133
159
|
|
|
134
160
|
for (const bug of all) {
|
|
135
|
-
if (bug.frontmatter.status ===
|
|
161
|
+
if (bug.frontmatter.status === "resolved") continue;
|
|
136
162
|
if (paths && paths.length > 0 && !paths.includes(bug.relativePath)) continue;
|
|
137
163
|
|
|
138
164
|
const absPath = resolve(this.root, bug.relativePath);
|
|
139
|
-
const content = await readFile(absPath,
|
|
140
|
-
const updated = content.replace(/^status:\s*open/m,
|
|
141
|
-
await writeFile(absPath, updated,
|
|
165
|
+
const content = await readFile(absPath, "utf-8");
|
|
166
|
+
const updated = content.replace(/^status:\s*open/m, "status: resolved");
|
|
167
|
+
await writeFile(absPath, updated, "utf-8");
|
|
142
168
|
marked.push(bug.relativePath);
|
|
143
169
|
}
|
|
144
170
|
|
package/src/types.ts
CHANGED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { generateApplyPrompt } from '../src/prompt/apply-prompt-generator.js';
|
|
3
|
+
import { resolveAgentCommand, DEFAULT_AGENTS } from '../src/agent/agent-defaults.js';
|
|
4
|
+
import type { Bug, ChangeRequest, StoryFile } from '../src/types.js';
|
|
5
|
+
|
|
6
|
+
function makeBug(overrides: Partial<Bug> = {}): Bug {
|
|
7
|
+
return {
|
|
8
|
+
relativePath: 'bugs/BUG-001.md',
|
|
9
|
+
frontmatter: {
|
|
10
|
+
title: 'Login button broken',
|
|
11
|
+
status: 'open',
|
|
12
|
+
author: 'test',
|
|
13
|
+
'created-at': '2024-01-01T00:00:00.000Z',
|
|
14
|
+
},
|
|
15
|
+
body: '## Description\n\nThe login button does not work.',
|
|
16
|
+
...overrides,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function makeCR(overrides: Partial<ChangeRequest> = {}): ChangeRequest {
|
|
21
|
+
return {
|
|
22
|
+
relativePath: 'change-requests/CR-001.md',
|
|
23
|
+
frontmatter: {
|
|
24
|
+
title: 'Add dark mode',
|
|
25
|
+
status: 'draft',
|
|
26
|
+
author: 'test',
|
|
27
|
+
'created-at': '2024-01-01T00:00:00.000Z',
|
|
28
|
+
},
|
|
29
|
+
body: '## Changes\n\nAdd dark mode support.',
|
|
30
|
+
...overrides,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function makeFile(overrides: Partial<StoryFile> = {}): StoryFile {
|
|
35
|
+
return {
|
|
36
|
+
relativePath: 'product/features/auth.md',
|
|
37
|
+
frontmatter: {
|
|
38
|
+
title: 'Auth',
|
|
39
|
+
status: 'new',
|
|
40
|
+
author: 'test',
|
|
41
|
+
'last-modified': '2024-01-01T00:00:00.000Z',
|
|
42
|
+
version: '1.0',
|
|
43
|
+
},
|
|
44
|
+
body: '# Auth Feature',
|
|
45
|
+
pendingItems: [],
|
|
46
|
+
agentNotes: null,
|
|
47
|
+
crossRefs: [],
|
|
48
|
+
hash: 'abc',
|
|
49
|
+
...overrides,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
describe('generateApplyPrompt', () => {
|
|
54
|
+
it('returns null when nothing to do', () => {
|
|
55
|
+
const result = generateApplyPrompt([], [], [], '/tmp');
|
|
56
|
+
expect(result).toBeNull();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('generates prompt with only bugs', () => {
|
|
60
|
+
const prompt = generateApplyPrompt([makeBug()], [], [], '/tmp');
|
|
61
|
+
expect(prompt).not.toBeNull();
|
|
62
|
+
expect(prompt).toContain('# SDD Apply');
|
|
63
|
+
expect(prompt).toContain('Open Bugs (1)');
|
|
64
|
+
expect(prompt).toContain('Login button broken');
|
|
65
|
+
expect(prompt).toContain('bugs/BUG-001.md');
|
|
66
|
+
expect(prompt).not.toContain('Pending Change Requests');
|
|
67
|
+
expect(prompt).not.toContain('Pending Files');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('generates prompt with only CRs', () => {
|
|
71
|
+
const prompt = generateApplyPrompt([], [makeCR()], [], '/tmp');
|
|
72
|
+
expect(prompt).not.toBeNull();
|
|
73
|
+
expect(prompt).toContain('Pending Change Requests (1)');
|
|
74
|
+
expect(prompt).toContain('Add dark mode');
|
|
75
|
+
expect(prompt).not.toContain('Open Bugs');
|
|
76
|
+
expect(prompt).not.toContain('Pending Files');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('generates prompt with only pending files', () => {
|
|
80
|
+
const prompt = generateApplyPrompt([], [], [makeFile()], '/tmp');
|
|
81
|
+
expect(prompt).not.toBeNull();
|
|
82
|
+
expect(prompt).toContain('Pending Files (1)');
|
|
83
|
+
expect(prompt).toContain('product/features/auth.md');
|
|
84
|
+
expect(prompt).toContain('**new**');
|
|
85
|
+
expect(prompt).not.toContain('Open Bugs');
|
|
86
|
+
expect(prompt).not.toContain('Pending Change Requests');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('generates prompt with all three', () => {
|
|
90
|
+
const prompt = generateApplyPrompt([makeBug()], [makeCR()], [makeFile()], '/tmp');
|
|
91
|
+
expect(prompt).not.toBeNull();
|
|
92
|
+
expect(prompt).toContain('Open Bugs (1)');
|
|
93
|
+
expect(prompt).toContain('Pending Change Requests (1)');
|
|
94
|
+
expect(prompt).toContain('Pending Files (1)');
|
|
95
|
+
expect(prompt).toContain('## Instructions');
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe('resolveAgentCommand', () => {
|
|
100
|
+
it('resolves built-in agent', () => {
|
|
101
|
+
const cmd = resolveAgentCommand('claude');
|
|
102
|
+
expect(cmd).toBe(DEFAULT_AGENTS.claude);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('resolves config agent over built-in', () => {
|
|
106
|
+
const cmd = resolveAgentCommand('claude', { claude: 'my-claude $PROMPT' });
|
|
107
|
+
expect(cmd).toBe('my-claude $PROMPT');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('resolves custom agent from config', () => {
|
|
111
|
+
const cmd = resolveAgentCommand('my-agent', { 'my-agent': 'my-agent-cmd $PROMPT' });
|
|
112
|
+
expect(cmd).toBe('my-agent-cmd $PROMPT');
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('returns undefined for unknown agent', () => {
|
|
116
|
+
const cmd = resolveAgentCommand('unknown');
|
|
117
|
+
expect(cmd).toBeUndefined();
|
|
118
|
+
});
|
|
119
|
+
});
|