create-sdd-project 0.4.2 ā 0.5.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/README.md +138 -16
- package/bin/cli.js +105 -0
- package/lib/config.js +11 -0
- package/lib/generator.js +4 -0
- package/lib/init-generator.js +15 -1
- package/lib/upgrade-generator.js +504 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# SDD DevFlow
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/create-sdd-project)
|
|
4
|
+
[](https://www.npmjs.com/package/create-sdd-project)
|
|
4
5
|
[](LICENSE)
|
|
5
6
|
[](package.json)
|
|
6
7
|
|
|
@@ -16,7 +17,34 @@ A complete development methodology for Claude Code and Gemini that combines spec
|
|
|
16
17
|
npx create-sdd-project my-app
|
|
17
18
|
```
|
|
18
19
|
|
|
19
|
-
The interactive wizard asks about your stack, AI tools, and autonomy level
|
|
20
|
+
The interactive wizard asks about your stack, AI tools, and autonomy level:
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
š Create SDD DevFlow Project
|
|
24
|
+
|
|
25
|
+
āā Step 1: Project Basics āāāāāāāāāāāāāāāāāāāāāā
|
|
26
|
+
Project name: my-app
|
|
27
|
+
Brief project description: Task management API
|
|
28
|
+
|
|
29
|
+
āā Step 3: Project Type & Tech Stack āāāāāāāāāā
|
|
30
|
+
1) Backend + Frontend (monorepo) ā default
|
|
31
|
+
2) Backend only
|
|
32
|
+
3) Frontend only
|
|
33
|
+
|
|
34
|
+
āā Step 4: AI Tools āāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
35
|
+
1) Claude Code + Gemini ā default
|
|
36
|
+
2) Claude Code only
|
|
37
|
+
3) Gemini only
|
|
38
|
+
|
|
39
|
+
āā Step 5: Workflow Configuration āāāāāāāāāāāāāā
|
|
40
|
+
Autonomy level:
|
|
41
|
+
1) L1 Full Control ā Human approves every checkpoint
|
|
42
|
+
2) L2 Trusted ā Human reviews plans + merges only ā default
|
|
43
|
+
3) L3 Autopilot ā Human only approves merges
|
|
44
|
+
4) L4 Full Auto ā No human checkpoints, CI/CD gates only
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
For defaults (fullstack Express+Next.js, L2 autonomy):
|
|
20
48
|
|
|
21
49
|
```bash
|
|
22
50
|
npx create-sdd-project my-app --yes
|
|
@@ -29,18 +57,108 @@ cd your-existing-project
|
|
|
29
57
|
npx create-sdd-project --init
|
|
30
58
|
```
|
|
31
59
|
|
|
32
|
-
|
|
60
|
+
The scanner detects your stack and installs adapted SDD files:
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
š Scanning project...
|
|
64
|
+
|
|
65
|
+
Project: my-api
|
|
66
|
+
Language: TypeScript
|
|
67
|
+
Backend: Express + Mongoose + MongoDB
|
|
68
|
+
Frontend: Not detected
|
|
69
|
+
Architecture: Layered (controllers + handlers + managers)
|
|
70
|
+
Tests: Jest (3 test files)
|
|
71
|
+
Monorepo: No
|
|
72
|
+
|
|
73
|
+
Adding SDD DevFlow to my-api...
|
|
74
|
+
|
|
75
|
+
ā Installing Claude Code config (agents, skills, commands, hooks)
|
|
76
|
+
ā Installing Gemini config (agents, skills, commands)
|
|
77
|
+
ā Creating ai-specs/specs/ (4 standards files)
|
|
78
|
+
ā Creating docs/project_notes/ (product tracker, memory)
|
|
79
|
+
ā Creating AGENTS.md
|
|
80
|
+
ā Setting autonomy level: L2 (Trusted)
|
|
81
|
+
|
|
82
|
+
Done! Next steps:
|
|
83
|
+
git add -A && git commit -m "chore: add SDD DevFlow to existing project"
|
|
84
|
+
# Open in your AI coding tool and run: add feature "your first feature"
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Never modifies existing code or overwrites existing files.
|
|
88
|
+
|
|
89
|
+
### Upgrade
|
|
90
|
+
|
|
91
|
+
When a new version of SDD DevFlow is released, upgrade your project's template files:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
cd your-existing-project
|
|
95
|
+
npx create-sdd-project@latest --upgrade
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
š SDD DevFlow Upgrade
|
|
100
|
+
|
|
101
|
+
Current version: 0.4.2
|
|
102
|
+
New version: 0.5.0
|
|
103
|
+
AI tools: Claude Code + Gemini
|
|
104
|
+
Project type: backend
|
|
105
|
+
|
|
106
|
+
Will replace:
|
|
107
|
+
ā .claude/ (agents, skills, hooks)
|
|
108
|
+
ā .gemini/ (agents, skills, commands)
|
|
109
|
+
ā AGENTS.md, CLAUDE.md / GEMINI.md
|
|
110
|
+
ā .env.example
|
|
111
|
+
|
|
112
|
+
Will preserve:
|
|
113
|
+
ā .claude/settings.local.json (personal settings)
|
|
114
|
+
ā docs/project_notes/* (project memory)
|
|
115
|
+
ā docs/specs/* (your specs)
|
|
116
|
+
ā docs/tickets/* (your tickets)
|
|
117
|
+
ā .gitignore
|
|
118
|
+
|
|
119
|
+
Standards:
|
|
120
|
+
ā ai-specs/specs/base-standards.mdc ā unchanged, will update
|
|
121
|
+
ā ai-specs/specs/backend-standards.mdc ā modified by you, preserved
|
|
122
|
+
|
|
123
|
+
Proceed? (y/N)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**What gets upgraded:**
|
|
127
|
+
- Agent definitions, skills, hooks, and commands
|
|
128
|
+
- `AGENTS.md`, `CLAUDE.md`, `GEMINI.md`, `.env.example`
|
|
129
|
+
- Standards files (`.mdc`) ā only if you haven't customized them
|
|
130
|
+
|
|
131
|
+
**What is always preserved:**
|
|
132
|
+
- Your project documentation (`docs/project_notes/`, `docs/specs/`, `docs/tickets/`)
|
|
133
|
+
- Custom agents you added to `.claude/agents/` or `.gemini/agents/`
|
|
134
|
+
- Custom commands in `.claude/commands/`
|
|
135
|
+
- Personal settings (`.claude/settings.local.json`)
|
|
136
|
+
- Your `.gitignore`
|
|
137
|
+
- Autonomy level setting
|
|
138
|
+
|
|
139
|
+
For non-interactive upgrades (CI/scripts): `npx create-sdd-project@latest --upgrade --yes`
|
|
33
140
|
|
|
34
141
|
### After Setup
|
|
35
142
|
|
|
36
|
-
Open in
|
|
143
|
+
Open your project in Claude Code or Gemini and start building:
|
|
144
|
+
|
|
145
|
+
**Claude Code:**
|
|
146
|
+
```
|
|
147
|
+
/add-feature "user authentication with JWT"
|
|
148
|
+
/start-task F001
|
|
149
|
+
/show-progress
|
|
150
|
+
/next-task
|
|
151
|
+
```
|
|
37
152
|
|
|
153
|
+
**Gemini:**
|
|
38
154
|
```
|
|
39
|
-
add
|
|
40
|
-
start
|
|
155
|
+
/add-feature "user authentication with JWT"
|
|
156
|
+
/start-task F001
|
|
157
|
+
/show-progress
|
|
158
|
+
/next-task
|
|
41
159
|
```
|
|
42
160
|
|
|
43
|
-
The workflow skill guides you through each step with checkpoints based on your autonomy level.
|
|
161
|
+
The workflow skill guides you through each step ā from spec writing to implementation to code review ā with checkpoints based on your autonomy level.
|
|
44
162
|
|
|
45
163
|
---
|
|
46
164
|
|
|
@@ -149,7 +267,7 @@ When running `--init` on an existing project, the scanner automatically detects:
|
|
|
149
267
|
| **Component libraries** | Radix UI, Headless UI, Material UI, Chakra UI, Ant Design |
|
|
150
268
|
| **State management** | Zustand, Redux, Jotai, TanStack Query, Recoil, Pinia, MobX |
|
|
151
269
|
| **Testing** | Jest, Vitest, Mocha (unit) + Playwright, Cypress (e2e) |
|
|
152
|
-
| **Architecture** | MVC, DDD, feature-based, handler-based, flat |
|
|
270
|
+
| **Architecture** | MVC, DDD, feature-based, handler-based, layered, flat |
|
|
153
271
|
| **Project type** | Monorepo (workspaces, Lerna, Turbo, pnpm) or single-package |
|
|
154
272
|
|
|
155
273
|
Standards files are adapted to match your actual architecture ā not generic defaults.
|
|
@@ -203,7 +321,7 @@ Configurable via the wizard or `<!-- CONFIG -->` comments in template files:
|
|
|
203
321
|
|
|
204
322
|
- **Backend**: Node.js + Express + Prisma + PostgreSQL
|
|
205
323
|
- **Frontend**: Next.js (App Router) + Tailwind CSS + Radix UI + Zustand
|
|
206
|
-
- **Shared Types**:
|
|
324
|
+
- **Shared Types**: Validation schemas with TypeScript type inference
|
|
207
325
|
- **Testing**: Jest (unit) + Playwright (e2e)
|
|
208
326
|
- **Methodology**: TDD + DDD + Spec-Driven Development
|
|
209
327
|
|
|
@@ -214,7 +332,7 @@ These 6 principles apply to ALL tasks, ALL agents, ALL complexity levels:
|
|
|
214
332
|
1. **Spec First** ā No implementation without an approved specification
|
|
215
333
|
2. **Small Tasks** ā Work in baby steps, one at a time
|
|
216
334
|
3. **Test-Driven Development** ā Write tests before implementation
|
|
217
|
-
4. **Type Safety** ā Strict TypeScript, no `any`, runtime validation
|
|
335
|
+
4. **Type Safety** ā Strict TypeScript, no `any`, runtime validation at boundaries
|
|
218
336
|
5. **English Only** ā All code, comments, docs, commits, and tickets in English
|
|
219
337
|
6. **Reuse Over Recreate** ā Always check existing code before proposing new files
|
|
220
338
|
|
|
@@ -224,21 +342,25 @@ These 6 principles apply to ALL tasks, ALL agents, ALL complexity levels:
|
|
|
224
342
|
- Node.js 18+
|
|
225
343
|
- `jq` (for quick-scan hook): `brew install jq` (macOS) or `apt install jq` (Linux)
|
|
226
344
|
|
|
227
|
-
## Manual Setup
|
|
345
|
+
## Manual Setup
|
|
228
346
|
|
|
229
|
-
If you prefer manual configuration over the CLI wizard:
|
|
347
|
+
If you prefer manual configuration over the CLI wizard, copy the template directory and look for `<!-- CONFIG: ... -->` comments in the files to customize:
|
|
230
348
|
|
|
231
349
|
```bash
|
|
232
|
-
cp -r template/ /path/to/your-project/
|
|
350
|
+
cp -r node_modules/create-sdd-project/template/ /path/to/your-project/
|
|
233
351
|
```
|
|
234
352
|
|
|
235
|
-
Then look for `<!-- CONFIG: ... -->` comments in the files to customize.
|
|
236
|
-
|
|
237
353
|
## Roadmap
|
|
238
354
|
|
|
239
|
-
- **
|
|
240
|
-
- **
|
|
355
|
+
- **Monorepo improvements**: Better support for pnpm workspaces and Turbo
|
|
356
|
+
- **SDD Upgrade/Migration**: Version bumps for projects already using SDD
|
|
241
357
|
- **Retrofit Testing**: Automated test generation for existing projects with low coverage
|
|
358
|
+
- **Agent Teams**: Parallel execution of independent tasks
|
|
359
|
+
- **PM Agent + L5 Autonomous**: AI-driven feature orchestration with human review at milestone boundaries
|
|
360
|
+
|
|
361
|
+
## Contributing
|
|
362
|
+
|
|
363
|
+
Found a bug or have a suggestion? [Open an issue on GitHub](https://github.com/pbojeda/sdd-devflow/issues).
|
|
242
364
|
|
|
243
365
|
## License
|
|
244
366
|
|
package/bin/cli.js
CHANGED
|
@@ -8,8 +8,13 @@ const args = process.argv.slice(2);
|
|
|
8
8
|
const projectName = args.find((a) => !a.startsWith('-'));
|
|
9
9
|
const useDefaults = args.includes('--yes') || args.includes('-y');
|
|
10
10
|
const isInit = args.includes('--init');
|
|
11
|
+
const isUpgrade = args.includes('--upgrade');
|
|
12
|
+
const isForce = args.includes('--force');
|
|
11
13
|
|
|
12
14
|
async function main() {
|
|
15
|
+
if (isUpgrade) {
|
|
16
|
+
return runUpgrade();
|
|
17
|
+
}
|
|
13
18
|
if (isInit) {
|
|
14
19
|
return runInit();
|
|
15
20
|
}
|
|
@@ -90,6 +95,106 @@ async function runInit() {
|
|
|
90
95
|
generateInit(config);
|
|
91
96
|
}
|
|
92
97
|
|
|
98
|
+
async function runUpgrade() {
|
|
99
|
+
const { scan } = require('../lib/scanner');
|
|
100
|
+
const { buildInitDefaultConfig } = require('../lib/init-wizard');
|
|
101
|
+
const readline = require('readline');
|
|
102
|
+
const {
|
|
103
|
+
generateUpgrade,
|
|
104
|
+
readInstalledVersion,
|
|
105
|
+
getPackageVersion,
|
|
106
|
+
detectAiTools,
|
|
107
|
+
detectProjectType,
|
|
108
|
+
readAutonomyLevel,
|
|
109
|
+
collectCustomAgents,
|
|
110
|
+
collectCustomCommands,
|
|
111
|
+
buildSummary,
|
|
112
|
+
} = require('../lib/upgrade-generator');
|
|
113
|
+
|
|
114
|
+
const cwd = process.cwd();
|
|
115
|
+
|
|
116
|
+
// Validate: must be in an existing project
|
|
117
|
+
if (!fs.existsSync(path.join(cwd, 'package.json'))) {
|
|
118
|
+
console.error('Error: No package.json found in current directory.');
|
|
119
|
+
console.error('The --upgrade flag must be run from inside an existing project.');
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Validate: SDD must be installed
|
|
124
|
+
if (!fs.existsSync(path.join(cwd, 'ai-specs'))) {
|
|
125
|
+
console.error('Error: ai-specs/ directory not found.');
|
|
126
|
+
console.error('SDD DevFlow does not appear to be installed. Use --init first.');
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Validate: no project name with --upgrade
|
|
131
|
+
if (projectName) {
|
|
132
|
+
console.error('Error: Cannot specify a project name with --upgrade.');
|
|
133
|
+
console.error('Usage: create-sdd-project --upgrade');
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Read current state
|
|
138
|
+
const installedVersion = readInstalledVersion(cwd);
|
|
139
|
+
const packageVersion = getPackageVersion();
|
|
140
|
+
|
|
141
|
+
// Same version check
|
|
142
|
+
if (installedVersion === packageVersion && !isForce) {
|
|
143
|
+
console.log(`\nSDD DevFlow is already at version ${packageVersion}.`);
|
|
144
|
+
console.log('Use --force to re-install the same version.\n');
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Scan project
|
|
149
|
+
const scanResult = scan(cwd);
|
|
150
|
+
|
|
151
|
+
// Detect current config from installed files
|
|
152
|
+
const aiTools = detectAiTools(cwd);
|
|
153
|
+
const projectType = detectProjectType(cwd);
|
|
154
|
+
const autonomy = readAutonomyLevel(cwd);
|
|
155
|
+
const customAgents = collectCustomAgents(cwd);
|
|
156
|
+
const customCommands = collectCustomCommands(cwd);
|
|
157
|
+
const settingsLocal = fs.existsSync(path.join(cwd, '.claude', 'settings.local.json'));
|
|
158
|
+
|
|
159
|
+
// Build config (reuse init defaults as base)
|
|
160
|
+
const config = buildInitDefaultConfig(scanResult);
|
|
161
|
+
config.aiTools = aiTools;
|
|
162
|
+
config.projectType = projectType;
|
|
163
|
+
config.autonomyLevel = autonomy.level;
|
|
164
|
+
config.autonomyName = autonomy.name;
|
|
165
|
+
config.installedVersion = installedVersion;
|
|
166
|
+
|
|
167
|
+
// Build and show summary
|
|
168
|
+
const state = {
|
|
169
|
+
installedVersion,
|
|
170
|
+
packageVersion,
|
|
171
|
+
aiTools,
|
|
172
|
+
projectType,
|
|
173
|
+
customAgents,
|
|
174
|
+
customCommands,
|
|
175
|
+
settingsLocal,
|
|
176
|
+
standardsStatus: [], // computed during generation
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
if (!useDefaults) {
|
|
180
|
+
console.log('\n' + buildSummary(state));
|
|
181
|
+
console.log('');
|
|
182
|
+
|
|
183
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
184
|
+
const answer = await new Promise((resolve) => {
|
|
185
|
+
rl.question(' Proceed? (y/N) ', resolve);
|
|
186
|
+
});
|
|
187
|
+
rl.close();
|
|
188
|
+
|
|
189
|
+
if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
|
|
190
|
+
console.log('\nUpgrade cancelled.\n');
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
generateUpgrade(config);
|
|
196
|
+
}
|
|
197
|
+
|
|
93
198
|
main().catch((err) => {
|
|
94
199
|
console.error('\nError:', err.message);
|
|
95
200
|
process.exit(1);
|
package/lib/config.js
CHANGED
|
@@ -93,6 +93,16 @@ const BRANCHING_STRATEGIES = [
|
|
|
93
93
|
const FRONTEND_AGENTS = ['frontend-developer.md', 'frontend-planner.md'];
|
|
94
94
|
const BACKEND_AGENTS = ['backend-developer.md', 'backend-planner.md', 'database-architect.md'];
|
|
95
95
|
|
|
96
|
+
// All template-provided agent files (for upgrade: detect custom agents)
|
|
97
|
+
const TEMPLATE_AGENTS = [
|
|
98
|
+
...FRONTEND_AGENTS,
|
|
99
|
+
...BACKEND_AGENTS,
|
|
100
|
+
'spec-creator.md',
|
|
101
|
+
'code-review-specialist.md',
|
|
102
|
+
'production-code-validator.md',
|
|
103
|
+
'qa-engineer.md',
|
|
104
|
+
];
|
|
105
|
+
|
|
96
106
|
module.exports = {
|
|
97
107
|
DEFAULTS,
|
|
98
108
|
PROJECT_TYPES,
|
|
@@ -103,4 +113,5 @@ module.exports = {
|
|
|
103
113
|
BRANCHING_STRATEGIES,
|
|
104
114
|
FRONTEND_AGENTS,
|
|
105
115
|
BACKEND_AGENTS,
|
|
116
|
+
TEMPLATE_AGENTS,
|
|
106
117
|
};
|
package/lib/generator.js
CHANGED
|
@@ -77,6 +77,10 @@ function generate(config) {
|
|
|
77
77
|
safeDelete(path.join(dest, 'CLAUDE.md'));
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
// Write version marker
|
|
81
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8'));
|
|
82
|
+
fs.writeFileSync(path.join(dest, '.sdd-version'), pkg.version + '\n', 'utf8');
|
|
83
|
+
|
|
80
84
|
// Show notes
|
|
81
85
|
const notes = collectNotes(config);
|
|
82
86
|
if (notes.length > 0) {
|
package/lib/init-generator.js
CHANGED
|
@@ -230,6 +230,10 @@ function generateInit(config) {
|
|
|
230
230
|
console.log(' Referenced in docs/project_notes/key_facts.md');
|
|
231
231
|
}
|
|
232
232
|
|
|
233
|
+
// Write version marker
|
|
234
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8'));
|
|
235
|
+
fs.writeFileSync(path.join(dest, '.sdd-version'), pkg.version + '\n', 'utf8');
|
|
236
|
+
|
|
233
237
|
// Done
|
|
234
238
|
console.log(`\nDone! Next steps:`);
|
|
235
239
|
console.log(` git add -A && git commit -m "chore: add SDD DevFlow to existing project"`);
|
|
@@ -996,4 +1000,14 @@ function appendGitignore(dest, skipped) {
|
|
|
996
1000
|
}
|
|
997
1001
|
}
|
|
998
1002
|
|
|
999
|
-
module.exports = {
|
|
1003
|
+
module.exports = {
|
|
1004
|
+
generateInit,
|
|
1005
|
+
adaptBaseStandards,
|
|
1006
|
+
adaptBackendStandards,
|
|
1007
|
+
adaptFrontendStandards,
|
|
1008
|
+
adaptAgentsMd,
|
|
1009
|
+
adaptCopiedFiles,
|
|
1010
|
+
adaptEnvExample,
|
|
1011
|
+
updateAutonomy,
|
|
1012
|
+
regexReplaceInFile,
|
|
1013
|
+
};
|
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const {
|
|
6
|
+
AUTONOMY_LEVELS,
|
|
7
|
+
FRONTEND_AGENTS,
|
|
8
|
+
BACKEND_AGENTS,
|
|
9
|
+
TEMPLATE_AGENTS,
|
|
10
|
+
} = require('./config');
|
|
11
|
+
const { adaptAgentContentForProjectType } = require('./adapt-agents');
|
|
12
|
+
const {
|
|
13
|
+
adaptBaseStandards,
|
|
14
|
+
adaptBackendStandards,
|
|
15
|
+
adaptFrontendStandards,
|
|
16
|
+
adaptAgentsMd,
|
|
17
|
+
adaptCopiedFiles,
|
|
18
|
+
adaptEnvExample,
|
|
19
|
+
updateAutonomy,
|
|
20
|
+
regexReplaceInFile,
|
|
21
|
+
} = require('./init-generator');
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Read the installed SDD version from .sdd-version file.
|
|
25
|
+
*/
|
|
26
|
+
function readInstalledVersion(dest) {
|
|
27
|
+
const versionFile = path.join(dest, '.sdd-version');
|
|
28
|
+
if (fs.existsSync(versionFile)) {
|
|
29
|
+
return fs.readFileSync(versionFile, 'utf8').trim();
|
|
30
|
+
}
|
|
31
|
+
return 'unknown';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Read the current package version.
|
|
36
|
+
*/
|
|
37
|
+
function getPackageVersion() {
|
|
38
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8'));
|
|
39
|
+
return pkg.version;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Detect which AI tools are installed by checking existing directories.
|
|
44
|
+
*/
|
|
45
|
+
function detectAiTools(dest) {
|
|
46
|
+
const hasClaude = fs.existsSync(path.join(dest, '.claude'));
|
|
47
|
+
const hasGemini = fs.existsSync(path.join(dest, '.gemini'));
|
|
48
|
+
if (hasClaude && hasGemini) return 'both';
|
|
49
|
+
if (hasClaude) return 'claude';
|
|
50
|
+
if (hasGemini) return 'gemini';
|
|
51
|
+
return 'both'; // fallback: install both
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Detect project type from existing agent files.
|
|
56
|
+
*/
|
|
57
|
+
function detectProjectType(dest) {
|
|
58
|
+
const toolDir = fs.existsSync(path.join(dest, '.claude')) ? '.claude' : '.gemini';
|
|
59
|
+
const agentsDir = path.join(dest, toolDir, 'agents');
|
|
60
|
+
if (!fs.existsSync(agentsDir)) return 'fullstack';
|
|
61
|
+
|
|
62
|
+
const agents = fs.readdirSync(agentsDir);
|
|
63
|
+
const hasFrontend = agents.some((a) => FRONTEND_AGENTS.includes(a));
|
|
64
|
+
const hasBackend = agents.some((a) => BACKEND_AGENTS.includes(a));
|
|
65
|
+
|
|
66
|
+
if (hasFrontend && hasBackend) return 'fullstack';
|
|
67
|
+
if (hasBackend) return 'backend';
|
|
68
|
+
if (hasFrontend) return 'frontend';
|
|
69
|
+
return 'fullstack';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Read autonomy level from CLAUDE.md or GEMINI.md.
|
|
74
|
+
*/
|
|
75
|
+
function readAutonomyLevel(dest) {
|
|
76
|
+
for (const file of ['CLAUDE.md', 'GEMINI.md']) {
|
|
77
|
+
const filePath = path.join(dest, file);
|
|
78
|
+
if (fs.existsSync(filePath)) {
|
|
79
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
80
|
+
const match = content.match(/\*\*Autonomy Level: (\d+) \(([^)]+)\)\*\*/);
|
|
81
|
+
if (match) {
|
|
82
|
+
return { level: parseInt(match[1], 10), name: match[2] };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return { level: 2, name: 'Trusted' }; // default
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Find custom agent files (not in template list).
|
|
91
|
+
* Returns array of { name, content } objects.
|
|
92
|
+
*/
|
|
93
|
+
function collectCustomAgents(dest) {
|
|
94
|
+
const customs = [];
|
|
95
|
+
for (const toolDir of ['.claude', '.gemini']) {
|
|
96
|
+
const agentsDir = path.join(dest, toolDir, 'agents');
|
|
97
|
+
if (!fs.existsSync(agentsDir)) continue;
|
|
98
|
+
const files = fs.readdirSync(agentsDir);
|
|
99
|
+
for (const file of files) {
|
|
100
|
+
if (!TEMPLATE_AGENTS.includes(file) && file.endsWith('.md')) {
|
|
101
|
+
customs.push({
|
|
102
|
+
relativePath: path.join(toolDir, 'agents', file),
|
|
103
|
+
content: fs.readFileSync(path.join(agentsDir, file), 'utf8'),
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return customs;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Find custom command files (not .gitkeep).
|
|
113
|
+
* Returns array of { relativePath, content } objects.
|
|
114
|
+
*/
|
|
115
|
+
function collectCustomCommands(dest) {
|
|
116
|
+
const customs = [];
|
|
117
|
+
const commandsDir = path.join(dest, '.claude', 'commands');
|
|
118
|
+
if (!fs.existsSync(commandsDir)) return customs;
|
|
119
|
+
const files = fs.readdirSync(commandsDir);
|
|
120
|
+
for (const file of files) {
|
|
121
|
+
if (file === '.gitkeep') continue;
|
|
122
|
+
customs.push({
|
|
123
|
+
relativePath: path.join('.claude', 'commands', file),
|
|
124
|
+
content: fs.readFileSync(path.join(commandsDir, file), 'utf8'),
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
return customs;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Check if a standard file has been modified by the user.
|
|
132
|
+
* Compares existing file against freshly generated version.
|
|
133
|
+
*/
|
|
134
|
+
function isStandardModified(existingContent, freshContent) {
|
|
135
|
+
return existingContent.trim() !== freshContent.trim();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Build the upgrade summary for display.
|
|
140
|
+
*/
|
|
141
|
+
function buildSummary(state) {
|
|
142
|
+
const lines = [];
|
|
143
|
+
lines.push('š SDD DevFlow Upgrade\n');
|
|
144
|
+
lines.push(` Current version: ${state.installedVersion}`);
|
|
145
|
+
lines.push(` New version: ${state.packageVersion}`);
|
|
146
|
+
lines.push(` AI tools: ${state.aiTools === 'both' ? 'Claude Code + Gemini' : state.aiTools === 'claude' ? 'Claude Code' : 'Gemini'}`);
|
|
147
|
+
lines.push(` Project type: ${state.projectType}\n`);
|
|
148
|
+
|
|
149
|
+
lines.push(' Will replace:');
|
|
150
|
+
if (state.aiTools !== 'gemini') {
|
|
151
|
+
const customNote = state.customAgents.filter((a) => a.relativePath.startsWith('.claude')).length > 0
|
|
152
|
+
? ` ā custom agents preserved` : '';
|
|
153
|
+
lines.push(` ā .claude/ (agents, skills, hooks)${customNote}`);
|
|
154
|
+
}
|
|
155
|
+
if (state.aiTools !== 'claude') {
|
|
156
|
+
lines.push(` ā .gemini/ (agents, skills, commands)`);
|
|
157
|
+
}
|
|
158
|
+
lines.push(' ā AGENTS.md, CLAUDE.md / GEMINI.md');
|
|
159
|
+
lines.push(' ā .env.example\n');
|
|
160
|
+
|
|
161
|
+
lines.push(' Will preserve:');
|
|
162
|
+
if (state.settingsLocal) lines.push(' ā .claude/settings.local.json (personal settings)');
|
|
163
|
+
lines.push(' ā .claude/settings.json permissions (if any)');
|
|
164
|
+
for (const c of state.customCommands) lines.push(` ā ${c.relativePath} (custom command)`);
|
|
165
|
+
for (const a of state.customAgents) lines.push(` ā ${a.relativePath} (custom agent)`);
|
|
166
|
+
lines.push(' ā docs/project_notes/* (project memory)');
|
|
167
|
+
lines.push(' ā docs/specs/* (your specs)');
|
|
168
|
+
lines.push(' ā docs/tickets/* (your tickets)');
|
|
169
|
+
lines.push(' ā .gitignore\n');
|
|
170
|
+
|
|
171
|
+
if (state.standardsStatus.length > 0) {
|
|
172
|
+
lines.push(' Standards:');
|
|
173
|
+
for (const s of state.standardsStatus) {
|
|
174
|
+
if (s.modified) {
|
|
175
|
+
lines.push(` ā ${s.name} ā modified by you, will be preserved`);
|
|
176
|
+
} else {
|
|
177
|
+
lines.push(` ā ${s.name} ā unchanged, will update`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return lines.join('\n');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Main upgrade function.
|
|
187
|
+
*/
|
|
188
|
+
function generateUpgrade(config) {
|
|
189
|
+
const templateDir = path.join(__dirname, '..', 'template');
|
|
190
|
+
const dest = config.projectDir;
|
|
191
|
+
const scan = config.scanResult;
|
|
192
|
+
const aiTools = config.aiTools;
|
|
193
|
+
const projectType = config.projectType;
|
|
194
|
+
|
|
195
|
+
console.log(`\nUpgrading SDD DevFlow in ${config.projectName}...\n`);
|
|
196
|
+
|
|
197
|
+
// --- a) Preserve user items ---
|
|
198
|
+
const autonomy = readAutonomyLevel(dest);
|
|
199
|
+
const customAgents = collectCustomAgents(dest);
|
|
200
|
+
const customCommands = collectCustomCommands(dest);
|
|
201
|
+
|
|
202
|
+
// Preserve settings.local.json
|
|
203
|
+
let settingsLocal = null;
|
|
204
|
+
const settingsLocalPath = path.join(dest, '.claude', 'settings.local.json');
|
|
205
|
+
if (fs.existsSync(settingsLocalPath)) {
|
|
206
|
+
settingsLocal = fs.readFileSync(settingsLocalPath, 'utf8');
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
let replaced = 0;
|
|
210
|
+
let preserved = 0;
|
|
211
|
+
|
|
212
|
+
// --- b) Replace SDD-owned directories ---
|
|
213
|
+
const toolDirs = [];
|
|
214
|
+
if (aiTools !== 'gemini') toolDirs.push('.claude');
|
|
215
|
+
if (aiTools !== 'claude') toolDirs.push('.gemini');
|
|
216
|
+
|
|
217
|
+
for (const dir of toolDirs) {
|
|
218
|
+
const base = path.join(dest, dir);
|
|
219
|
+
// Delete specific SDD-owned subdirectories (NOT commands for .claude)
|
|
220
|
+
for (const sub of ['agents', 'skills', 'hooks', 'styles']) {
|
|
221
|
+
const subDir = path.join(base, sub);
|
|
222
|
+
if (fs.existsSync(subDir)) {
|
|
223
|
+
fs.rmSync(subDir, { recursive: true, force: true });
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
// For .gemini, also remove commands (SDD-owned ā Gemini TOML commands)
|
|
227
|
+
if (dir === '.gemini') {
|
|
228
|
+
const cmdDir = path.join(base, 'commands');
|
|
229
|
+
if (fs.existsSync(cmdDir)) {
|
|
230
|
+
fs.rmSync(cmdDir, { recursive: true, force: true });
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Copy fresh from template
|
|
235
|
+
const templateToolDir = path.join(templateDir, dir);
|
|
236
|
+
if (fs.existsSync(templateToolDir)) {
|
|
237
|
+
for (const sub of ['agents', 'skills', 'hooks', 'styles', 'commands']) {
|
|
238
|
+
const srcSub = path.join(templateToolDir, sub);
|
|
239
|
+
const destSub = path.join(base, sub);
|
|
240
|
+
if (fs.existsSync(srcSub)) {
|
|
241
|
+
// For .claude/commands, merge (don't overwrite user's custom commands)
|
|
242
|
+
if (dir === '.claude' && sub === 'commands') {
|
|
243
|
+
// Just ensure directory exists ā template only has .gitkeep
|
|
244
|
+
fs.mkdirSync(destSub, { recursive: true });
|
|
245
|
+
} else {
|
|
246
|
+
fs.cpSync(srcSub, destSub, { recursive: true });
|
|
247
|
+
}
|
|
248
|
+
replaced++;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Merge settings.json ā update hooks from template, preserve user's permissions/additionalDirectories
|
|
253
|
+
const settingsSrc = path.join(templateToolDir, 'settings.json');
|
|
254
|
+
const settingsDest = path.join(base, 'settings.json');
|
|
255
|
+
if (fs.existsSync(settingsSrc)) {
|
|
256
|
+
const templateSettings = JSON.parse(fs.readFileSync(settingsSrc, 'utf8'));
|
|
257
|
+
if (fs.existsSync(settingsDest)) {
|
|
258
|
+
const userSettings = JSON.parse(fs.readFileSync(settingsDest, 'utf8'));
|
|
259
|
+
// Keep user's permissions and additionalDirectories, take template's hooks
|
|
260
|
+
const merged = { ...templateSettings };
|
|
261
|
+
if (userSettings.permissions) merged.permissions = userSettings.permissions;
|
|
262
|
+
if (userSettings.additionalDirectories) merged.additionalDirectories = userSettings.additionalDirectories;
|
|
263
|
+
fs.writeFileSync(settingsDest, JSON.stringify(merged, null, 2) + '\n', 'utf8');
|
|
264
|
+
} else {
|
|
265
|
+
fs.copyFileSync(settingsSrc, settingsDest);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
step('Replaced agents, skills, hooks, and settings');
|
|
271
|
+
|
|
272
|
+
// --- c) Restore preserved items ---
|
|
273
|
+
for (const agent of customAgents) {
|
|
274
|
+
const agentPath = path.join(dest, agent.relativePath);
|
|
275
|
+
fs.mkdirSync(path.dirname(agentPath), { recursive: true });
|
|
276
|
+
fs.writeFileSync(agentPath, agent.content, 'utf8');
|
|
277
|
+
preserved++;
|
|
278
|
+
}
|
|
279
|
+
if (customAgents.length > 0) {
|
|
280
|
+
step(`Restored ${customAgents.length} custom agent(s)`);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Restore settings.local.json
|
|
284
|
+
if (settingsLocal) {
|
|
285
|
+
fs.writeFileSync(settingsLocalPath, settingsLocal, 'utf8');
|
|
286
|
+
preserved++;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// --- d) Handle standards (smart diff) ---
|
|
290
|
+
const standardsResults = [];
|
|
291
|
+
|
|
292
|
+
// base-standards.mdc
|
|
293
|
+
const baseStdPath = path.join(dest, 'ai-specs', 'specs', 'base-standards.mdc');
|
|
294
|
+
if (fs.existsSync(baseStdPath)) {
|
|
295
|
+
const existing = fs.readFileSync(baseStdPath, 'utf8');
|
|
296
|
+
const template = fs.readFileSync(path.join(templateDir, 'ai-specs', 'specs', 'base-standards.mdc'), 'utf8');
|
|
297
|
+
const fresh = adaptBaseStandards(template, scan, config);
|
|
298
|
+
if (isStandardModified(existing, fresh)) {
|
|
299
|
+
standardsResults.push({ name: 'ai-specs/specs/base-standards.mdc', modified: true });
|
|
300
|
+
preserved++;
|
|
301
|
+
} else {
|
|
302
|
+
fs.writeFileSync(baseStdPath, fresh, 'utf8');
|
|
303
|
+
standardsResults.push({ name: 'ai-specs/specs/base-standards.mdc', modified: false });
|
|
304
|
+
replaced++;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// backend-standards.mdc
|
|
309
|
+
if (projectType !== 'frontend') {
|
|
310
|
+
const backendStdPath = path.join(dest, 'ai-specs', 'specs', 'backend-standards.mdc');
|
|
311
|
+
if (fs.existsSync(backendStdPath)) {
|
|
312
|
+
const existing = fs.readFileSync(backendStdPath, 'utf8');
|
|
313
|
+
const template = fs.readFileSync(path.join(templateDir, 'ai-specs', 'specs', 'backend-standards.mdc'), 'utf8');
|
|
314
|
+
const fresh = adaptBackendStandards(template, scan);
|
|
315
|
+
if (isStandardModified(existing, fresh)) {
|
|
316
|
+
standardsResults.push({ name: 'ai-specs/specs/backend-standards.mdc', modified: true });
|
|
317
|
+
preserved++;
|
|
318
|
+
} else {
|
|
319
|
+
fs.writeFileSync(backendStdPath, fresh, 'utf8');
|
|
320
|
+
standardsResults.push({ name: 'ai-specs/specs/backend-standards.mdc', modified: false });
|
|
321
|
+
replaced++;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// frontend-standards.mdc
|
|
327
|
+
if (projectType !== 'backend') {
|
|
328
|
+
const frontendStdPath = path.join(dest, 'ai-specs', 'specs', 'frontend-standards.mdc');
|
|
329
|
+
if (fs.existsSync(frontendStdPath)) {
|
|
330
|
+
const existing = fs.readFileSync(frontendStdPath, 'utf8');
|
|
331
|
+
const template = fs.readFileSync(path.join(templateDir, 'ai-specs', 'specs', 'frontend-standards.mdc'), 'utf8');
|
|
332
|
+
const fresh = adaptFrontendStandards(template, scan);
|
|
333
|
+
if (isStandardModified(existing, fresh)) {
|
|
334
|
+
standardsResults.push({ name: 'ai-specs/specs/frontend-standards.mdc', modified: true });
|
|
335
|
+
preserved++;
|
|
336
|
+
} else {
|
|
337
|
+
fs.writeFileSync(frontendStdPath, fresh, 'utf8');
|
|
338
|
+
standardsResults.push({ name: 'ai-specs/specs/frontend-standards.mdc', modified: false });
|
|
339
|
+
replaced++;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// documentation-standards.mdc ā always replace (unlikely customized, no adaptation)
|
|
345
|
+
const docStdSrc = path.join(templateDir, 'ai-specs', 'specs', 'documentation-standards.mdc');
|
|
346
|
+
const docStdDest = path.join(dest, 'ai-specs', 'specs', 'documentation-standards.mdc');
|
|
347
|
+
if (fs.existsSync(docStdSrc)) {
|
|
348
|
+
fs.copyFileSync(docStdSrc, docStdDest);
|
|
349
|
+
replaced++;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
step('Updated standards files');
|
|
353
|
+
const modifiedStandards = standardsResults.filter((s) => s.modified);
|
|
354
|
+
if (modifiedStandards.length > 0) {
|
|
355
|
+
for (const s of modifiedStandards) {
|
|
356
|
+
console.log(` ā ${s.name} ā customized, preserved`);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// --- e) Replace top-level configs ---
|
|
361
|
+
// AGENTS.md
|
|
362
|
+
const agentsMdTemplate = fs.readFileSync(path.join(templateDir, 'AGENTS.md'), 'utf8');
|
|
363
|
+
const adaptedAgentsMd = adaptAgentsMd(agentsMdTemplate, config, scan);
|
|
364
|
+
fs.writeFileSync(path.join(dest, 'AGENTS.md'), adaptedAgentsMd, 'utf8');
|
|
365
|
+
replaced++;
|
|
366
|
+
|
|
367
|
+
// CLAUDE.md / GEMINI.md
|
|
368
|
+
if (aiTools !== 'gemini') {
|
|
369
|
+
fs.copyFileSync(path.join(templateDir, 'CLAUDE.md'), path.join(dest, 'CLAUDE.md'));
|
|
370
|
+
replaced++;
|
|
371
|
+
}
|
|
372
|
+
if (aiTools !== 'claude') {
|
|
373
|
+
fs.copyFileSync(path.join(templateDir, 'GEMINI.md'), path.join(dest, 'GEMINI.md'));
|
|
374
|
+
replaced++;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Restore autonomy level
|
|
378
|
+
const autonomyConfig = {
|
|
379
|
+
autonomyLevel: autonomy.level,
|
|
380
|
+
autonomyName: autonomy.name,
|
|
381
|
+
};
|
|
382
|
+
// Ensure autonomy name matches expected values
|
|
383
|
+
const levelInfo = AUTONOMY_LEVELS.find((l) => l.level === autonomy.level);
|
|
384
|
+
if (levelInfo) {
|
|
385
|
+
autonomyConfig.autonomyName = levelInfo.name;
|
|
386
|
+
}
|
|
387
|
+
updateAutonomy(dest, autonomyConfig);
|
|
388
|
+
step(`Restored autonomy level: L${autonomy.level} (${autonomyConfig.autonomyName})`);
|
|
389
|
+
|
|
390
|
+
// .env.example ā generate fresh, then append any custom lines from existing
|
|
391
|
+
let envContent = fs.readFileSync(path.join(templateDir, '.env.example'), 'utf8');
|
|
392
|
+
envContent = adaptEnvExample(envContent, config, scan);
|
|
393
|
+
const envPath = path.join(dest, '.env.example');
|
|
394
|
+
if (fs.existsSync(envPath)) {
|
|
395
|
+
const existingEnv = fs.readFileSync(envPath, 'utf8');
|
|
396
|
+
const freshLines = new Set(envContent.split('\n').map((l) => l.trim()));
|
|
397
|
+
const customLines = existingEnv.split('\n').filter((line) => {
|
|
398
|
+
const trimmed = line.trim();
|
|
399
|
+
// Keep lines that are not in the fresh version and are not empty
|
|
400
|
+
return trimmed.length > 0 && !freshLines.has(trimmed);
|
|
401
|
+
});
|
|
402
|
+
if (customLines.length > 0) {
|
|
403
|
+
envContent = envContent.trimEnd() + '\n\n# Project-specific variables (preserved from previous version)\n' + customLines.join('\n') + '\n';
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
fs.writeFileSync(envPath, envContent, 'utf8');
|
|
407
|
+
replaced++;
|
|
408
|
+
|
|
409
|
+
step('Replaced AGENTS.md, CLAUDE.md/GEMINI.md, .env.example');
|
|
410
|
+
|
|
411
|
+
// --- f) Adapt for project type ---
|
|
412
|
+
// Remove agents for single-stack projects
|
|
413
|
+
if (projectType === 'backend') {
|
|
414
|
+
for (const agent of FRONTEND_AGENTS) {
|
|
415
|
+
for (const dir of toolDirs) {
|
|
416
|
+
const agentPath = path.join(dest, dir, 'agents', agent);
|
|
417
|
+
try { fs.unlinkSync(agentPath); } catch { /* ignore */ }
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
// Remove frontend spec doc if it doesn't exist as user-owned
|
|
421
|
+
// (don't touch docs/specs/ ā user-owned)
|
|
422
|
+
} else if (projectType === 'frontend') {
|
|
423
|
+
for (const agent of BACKEND_AGENTS) {
|
|
424
|
+
for (const dir of toolDirs) {
|
|
425
|
+
const agentPath = path.join(dest, dir, 'agents', agent);
|
|
426
|
+
try { fs.unlinkSync(agentPath); } catch { /* ignore */ }
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Adapt agent/skill content for project type
|
|
432
|
+
if (projectType !== 'fullstack') {
|
|
433
|
+
adaptAgentContentForProjectType(dest, config, regexReplaceInFile);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Adapt copied files for detected stack (Zod, Prisma, DDD, etc.)
|
|
437
|
+
adaptCopiedFiles(dest, scan, config);
|
|
438
|
+
|
|
439
|
+
// Adapt documentation-standards for project type
|
|
440
|
+
const docStdPath2 = path.join(dest, 'ai-specs', 'specs', 'documentation-standards.mdc');
|
|
441
|
+
if (fs.existsSync(docStdPath2)) {
|
|
442
|
+
if (projectType === 'backend') {
|
|
443
|
+
regexReplaceInFile(docStdPath2, [
|
|
444
|
+
[/\| `ai-specs\/specs\/frontend-standards\.mdc` \|[^\n]*\n/, ''],
|
|
445
|
+
[/\| `docs\/specs\/ui-components\.md` \|[^\n]*\n/, ''],
|
|
446
|
+
[/ - UI component changes ā `docs\/specs\/ui-components\.md`\n/, ''],
|
|
447
|
+
]);
|
|
448
|
+
} else if (projectType === 'frontend') {
|
|
449
|
+
regexReplaceInFile(docStdPath2, [
|
|
450
|
+
[/\| `ai-specs\/specs\/backend-standards\.mdc` \|[^\n]*\n/, ''],
|
|
451
|
+
[/\| `docs\/specs\/api-spec\.yaml` \|[^\n]*\n/, ''],
|
|
452
|
+
]);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
step('Adapted files for project type and stack');
|
|
457
|
+
|
|
458
|
+
// --- g) Write version marker ---
|
|
459
|
+
const newVersion = getPackageVersion();
|
|
460
|
+
fs.writeFileSync(path.join(dest, '.sdd-version'), newVersion + '\n', 'utf8');
|
|
461
|
+
step(`Updated .sdd-version to ${newVersion}`);
|
|
462
|
+
|
|
463
|
+
// --- Show result ---
|
|
464
|
+
const updatedCount = standardsResults.filter((s) => !s.modified).length;
|
|
465
|
+
const preservedCount = modifiedStandards.length;
|
|
466
|
+
|
|
467
|
+
console.log(`\nā
Upgraded SDD DevFlow: ${config.installedVersion} ā ${newVersion}\n`);
|
|
468
|
+
console.log(` Replaced: agents, skills, hooks, configs`);
|
|
469
|
+
if (customAgents.length > 0 || customCommands.length > 0 || settingsLocal) {
|
|
470
|
+
const items = [];
|
|
471
|
+
if (customAgents.length > 0) items.push(`${customAgents.length} custom agent(s)`);
|
|
472
|
+
if (customCommands.length > 0) items.push(`${customCommands.length} custom command(s)`);
|
|
473
|
+
if (settingsLocal) items.push('personal settings');
|
|
474
|
+
console.log(` Preserved: ${items.join(', ')}`);
|
|
475
|
+
}
|
|
476
|
+
if (standardsResults.length > 0) {
|
|
477
|
+
console.log(` Standards: ${updatedCount} updated, ${preservedCount} preserved (customized)`);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
if (modifiedStandards.length > 0) {
|
|
481
|
+
console.log(`\n ā Review preserved standards for compatibility:`);
|
|
482
|
+
for (const s of modifiedStandards) {
|
|
483
|
+
console.log(` - ${s.name} (customized ā not updated)`);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
console.log(`\nNext: git add -A && git commit -m "chore: upgrade SDD DevFlow to ${newVersion}"\n`);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function step(msg) {
|
|
491
|
+
console.log(` ā ${msg}`);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
module.exports = {
|
|
495
|
+
generateUpgrade,
|
|
496
|
+
readInstalledVersion,
|
|
497
|
+
getPackageVersion,
|
|
498
|
+
detectAiTools,
|
|
499
|
+
detectProjectType,
|
|
500
|
+
readAutonomyLevel,
|
|
501
|
+
collectCustomAgents,
|
|
502
|
+
collectCustomCommands,
|
|
503
|
+
buildSummary,
|
|
504
|
+
};
|