5-phase-workflow 1.4.3 → 1.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 +18 -10
- package/bin/install.js +177 -218
- package/docs/workflow-guide.md +4 -4
- package/package.json +1 -1
- package/src/commands/5/configure.md +124 -328
- package/src/commands/5/discuss-feature.md +7 -172
- package/src/commands/5/implement-feature.md +40 -152
- package/src/commands/5/plan-feature.md +15 -8
- package/src/commands/5/plan-implementation.md +14 -10
- package/src/commands/5/quick-implement.md +41 -142
- package/src/commands/5/review-code.md +4 -14
- package/src/commands/5/unlock.md +23 -0
- package/src/commands/5/update.md +41 -4
- package/src/commands/5/verify-implementation.md +34 -201
- package/src/hooks/check-updates.js +4 -3
- package/src/hooks/config-guard.js +30 -0
- package/src/hooks/plan-guard.js +79 -32
- package/src/hooks/statusline.js +4 -3
- package/src/settings.json +11 -1
- package/src/skills/build-project/SKILL.md +6 -132
- package/src/skills/configure-project/SKILL.md +26 -215
- package/src/skills/run-tests/SKILL.md +7 -210
- package/src/templates/workflow/QUICK-PLAN.md +0 -17
package/README.md
CHANGED
|
@@ -126,10 +126,11 @@ All commands are available under the `/5:` namespace:
|
|
|
126
126
|
| `/5:verify-implementation` | 4 | Verify completeness and correctness |
|
|
127
127
|
| `/5:review-code` | 5 | AI-powered code review (CodeRabbit) |
|
|
128
128
|
| `/5:quick-implement` | Fast | Streamlined workflow for small tasks |
|
|
129
|
+
| `/5:unlock` | Utility | Remove planning guard lock |
|
|
129
130
|
|
|
130
131
|
## Configuration
|
|
131
132
|
|
|
132
|
-
The workflow is configured via `.
|
|
133
|
+
The workflow is configured via `.5/config.json`. Here's an example:
|
|
133
134
|
|
|
134
135
|
```json
|
|
135
136
|
{
|
|
@@ -181,7 +182,7 @@ Claude asks 5-10 clarifying questions to understand your requirements:
|
|
|
181
182
|
- How will we verify it works?
|
|
182
183
|
- Are there simpler alternatives?
|
|
183
184
|
|
|
184
|
-
The output is a comprehensive feature spec at `.
|
|
185
|
+
The output is a comprehensive feature spec at `.5/features/{ticket-id}/feature.md`.
|
|
185
186
|
|
|
186
187
|
### Phase 2: Implementation Planning
|
|
187
188
|
|
|
@@ -207,7 +208,7 @@ Claude executes the plan using specialized agents:
|
|
|
207
208
|
- **step-verifier**: Compiles and checks for errors after each step
|
|
208
209
|
- **integration-agent**: Wires components and registers routes
|
|
209
210
|
|
|
210
|
-
State is tracked in `.
|
|
211
|
+
State is tracked in `.5/features/{ticket-id}/state.json` for resumability.
|
|
211
212
|
|
|
212
213
|
### Phase 4: Verify Implementation
|
|
213
214
|
|
|
@@ -234,9 +235,12 @@ Automated review using CodeRabbit (if installed):
|
|
|
234
235
|
After installation, your `.claude/` directory will contain:
|
|
235
236
|
|
|
236
237
|
```
|
|
238
|
+
.5/
|
|
239
|
+
├── config.json # Project configuration
|
|
240
|
+
├── version.json # Version tracking
|
|
241
|
+
└── features/ # Feature tracking
|
|
242
|
+
|
|
237
243
|
.claude/
|
|
238
|
-
├── .5/
|
|
239
|
-
│ └── config.json # Project configuration
|
|
240
244
|
├── commands/5/ # Workflow commands
|
|
241
245
|
│ ├── plan-feature.md
|
|
242
246
|
│ ├── plan-implementation.md
|
|
@@ -245,13 +249,17 @@ After installation, your `.claude/` directory will contain:
|
|
|
245
249
|
│ ├── review-code.md
|
|
246
250
|
│ ├── discuss-feature.md
|
|
247
251
|
│ ├── quick-implement.md
|
|
248
|
-
│
|
|
252
|
+
│ ├── configure.md
|
|
253
|
+
│ └── unlock.md
|
|
249
254
|
├── skills/ # Atomic operations
|
|
250
255
|
│ ├── build-project/
|
|
251
256
|
│ ├── run-tests/
|
|
252
257
|
│ └── generate-readme/
|
|
253
258
|
├── hooks/
|
|
254
|
-
│
|
|
259
|
+
│ ├── statusline.js # Status line integration
|
|
260
|
+
│ ├── check-updates.js # Update notifications
|
|
261
|
+
│ ├── plan-guard.js # Planning phase edit guard
|
|
262
|
+
│ └── config-guard.js # Configuration guard
|
|
255
263
|
└── settings.json # Claude Code settings
|
|
256
264
|
```
|
|
257
265
|
|
|
@@ -293,7 +301,7 @@ After installation, your `.claude/` directory will contain:
|
|
|
293
301
|
|
|
294
302
|
1. Run `/5:configure` to verify configuration
|
|
295
303
|
2. Test commands manually in terminal
|
|
296
|
-
3. Update `.
|
|
304
|
+
3. Update `.5/config.json` with correct commands
|
|
297
305
|
|
|
298
306
|
### "Cannot find project type"
|
|
299
307
|
|
|
@@ -303,7 +311,7 @@ The auto-detection failed. Run `/5:configure` and manually select your project t
|
|
|
303
311
|
|
|
304
312
|
If implementation gets stuck:
|
|
305
313
|
|
|
306
|
-
1. Check `.
|
|
314
|
+
1. Check `.5/features/{ticket-id}/state.json`
|
|
307
315
|
2. Note the `currentStep` value
|
|
308
316
|
3. Run `/5:implement-feature` again - it will resume from that step
|
|
309
317
|
|
|
@@ -338,7 +346,7 @@ npx 5-phase-workflow
|
|
|
338
346
|
```
|
|
339
347
|
|
|
340
348
|
**Note:** During updates:
|
|
341
|
-
- Config files in `.
|
|
349
|
+
- Config files in `.5/` are preserved
|
|
342
350
|
- User-created commands, agents, skills, hooks, and templates are preserved
|
|
343
351
|
- Only workflow-managed files are updated
|
|
344
352
|
|
package/bin/install.js
CHANGED
|
@@ -23,9 +23,10 @@ const log = {
|
|
|
23
23
|
};
|
|
24
24
|
|
|
25
25
|
// Version comparison (semver)
|
|
26
|
+
// Uses parseInt to handle pre-release tags (e.g., "2-beta" → 2)
|
|
26
27
|
function compareVersions(v1, v2) {
|
|
27
|
-
const parts1 = v1.split('.').map(
|
|
28
|
-
const parts2 = v2.split('.').map(
|
|
28
|
+
const parts1 = v1.split('.').map(p => parseInt(p, 10) || 0);
|
|
29
|
+
const parts2 = v2.split('.').map(p => parseInt(p, 10) || 0);
|
|
29
30
|
for (let i = 0; i < 3; i++) {
|
|
30
31
|
if (parts1[i] > parts2[i]) return 1;
|
|
31
32
|
if (parts1[i] < parts2[i]) return -1;
|
|
@@ -34,8 +35,8 @@ function compareVersions(v1, v2) {
|
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
// Get installed version from .5/version.json
|
|
37
|
-
function getInstalledVersion(
|
|
38
|
-
const versionFile = path.join(
|
|
38
|
+
function getInstalledVersion(isGlobal) {
|
|
39
|
+
const versionFile = path.join(getDataPath(isGlobal), 'version.json');
|
|
39
40
|
if (!fs.existsSync(versionFile)) return null;
|
|
40
41
|
|
|
41
42
|
try {
|
|
@@ -54,13 +55,13 @@ function getPackageVersion() {
|
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
// Get full version info
|
|
57
|
-
function getVersionInfo(targetPath) {
|
|
58
|
+
function getVersionInfo(targetPath, isGlobal) {
|
|
58
59
|
const exists = checkExistingInstallation(targetPath);
|
|
59
60
|
if (!exists) {
|
|
60
61
|
return { exists: false };
|
|
61
62
|
}
|
|
62
63
|
|
|
63
|
-
const installed = getInstalledVersion(
|
|
64
|
+
const installed = getInstalledVersion(isGlobal);
|
|
64
65
|
const available = getPackageVersion();
|
|
65
66
|
|
|
66
67
|
if (!installed) {
|
|
@@ -138,7 +139,7 @@ Examples:
|
|
|
138
139
|
`);
|
|
139
140
|
}
|
|
140
141
|
|
|
141
|
-
// Get installation target path
|
|
142
|
+
// Get installation target path (.claude/ directory)
|
|
142
143
|
function getTargetPath(isGlobal) {
|
|
143
144
|
if (isGlobal) {
|
|
144
145
|
const homeDir = process.env.HOME || process.env.USERPROFILE;
|
|
@@ -147,6 +148,60 @@ function getTargetPath(isGlobal) {
|
|
|
147
148
|
return path.join(process.cwd(), '.claude');
|
|
148
149
|
}
|
|
149
150
|
|
|
151
|
+
// Get data path (.5/ directory for config, version, features)
|
|
152
|
+
// Local installs: <project>/.5 (project root)
|
|
153
|
+
// Global installs: ~/.claude/.5 (alongside global install)
|
|
154
|
+
function getDataPath(isGlobal) {
|
|
155
|
+
if (isGlobal) {
|
|
156
|
+
return path.join(getTargetPath(true), '.5');
|
|
157
|
+
}
|
|
158
|
+
return path.join(process.cwd(), '.5');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Migrate data from old .claude/.5/ to new .5/ location (local installs only)
|
|
162
|
+
// This runs during upgrades so existing users don't lose config/features/version data.
|
|
163
|
+
function migrateDataDir(isGlobal) {
|
|
164
|
+
// Global installs: old and new paths are both ~/.claude/.5 — no migration needed
|
|
165
|
+
if (isGlobal) return;
|
|
166
|
+
|
|
167
|
+
const oldDir = path.join(process.cwd(), '.claude', '.5');
|
|
168
|
+
const newDir = getDataPath(false); // <project>/.5
|
|
169
|
+
|
|
170
|
+
if (!fs.existsSync(oldDir)) return;
|
|
171
|
+
|
|
172
|
+
// If new dir doesn't exist, move everything over
|
|
173
|
+
if (!fs.existsSync(newDir)) {
|
|
174
|
+
fs.mkdirSync(newDir, { recursive: true });
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Copy all files/dirs from old to new (skip files that already exist in new)
|
|
178
|
+
copyDirMerge(oldDir, newDir);
|
|
179
|
+
|
|
180
|
+
// Remove old directory
|
|
181
|
+
removeDir(oldDir);
|
|
182
|
+
log.success('Migrated .claude/.5/ → .5/');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Copy directory recursively, skipping files that already exist at destination
|
|
186
|
+
function copyDirMerge(src, dest) {
|
|
187
|
+
if (!fs.existsSync(dest)) {
|
|
188
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
192
|
+
|
|
193
|
+
for (const entry of entries) {
|
|
194
|
+
const srcPath = path.join(src, entry.name);
|
|
195
|
+
const destPath = path.join(dest, entry.name);
|
|
196
|
+
|
|
197
|
+
if (entry.isDirectory()) {
|
|
198
|
+
copyDirMerge(srcPath, destPath);
|
|
199
|
+
} else if (!fs.existsSync(destPath)) {
|
|
200
|
+
fs.copyFileSync(srcPath, destPath);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
150
205
|
// Get source path (package installation directory)
|
|
151
206
|
function getSourcePath() {
|
|
152
207
|
// When installed via npm, __dirname is <install-location>/bin
|
|
@@ -214,7 +269,8 @@ function getWorkflowManagedFiles() {
|
|
|
214
269
|
hooks: [
|
|
215
270
|
'statusline.js',
|
|
216
271
|
'check-updates.js',
|
|
217
|
-
'plan-guard.js'
|
|
272
|
+
'plan-guard.js',
|
|
273
|
+
'config-guard.js'
|
|
218
274
|
],
|
|
219
275
|
|
|
220
276
|
// Templates: specific template files
|
|
@@ -234,7 +290,6 @@ function getWorkflowManagedFiles() {
|
|
|
234
290
|
'workflow/VERIFICATION-REPORT.md',
|
|
235
291
|
'workflow/REVIEW-FINDINGS.md',
|
|
236
292
|
'workflow/REVIEW-SUMMARY.md',
|
|
237
|
-
'workflow/QUICK-PLAN.md',
|
|
238
293
|
'workflow/FIX-PLAN.md'
|
|
239
294
|
]
|
|
240
295
|
};
|
|
@@ -255,20 +310,22 @@ function selectiveUpdate(targetPath, sourcePath) {
|
|
|
255
310
|
log.success('Updated commands/5/');
|
|
256
311
|
}
|
|
257
312
|
|
|
258
|
-
// Update specific agents
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
fs.
|
|
263
|
-
|
|
264
|
-
for (const agent of managed.agents) {
|
|
265
|
-
const src = path.join(agentsSrc, agent);
|
|
266
|
-
const dest = path.join(agentsDest, agent);
|
|
267
|
-
if (fs.existsSync(src)) {
|
|
268
|
-
fs.copyFileSync(src, dest);
|
|
313
|
+
// Update specific agents (currently none — instructions are embedded inline in commands)
|
|
314
|
+
if (managed.agents.length > 0) {
|
|
315
|
+
const agentsSrc = path.join(sourcePath, 'agents');
|
|
316
|
+
const agentsDest = path.join(targetPath, 'agents');
|
|
317
|
+
if (!fs.existsSync(agentsDest)) {
|
|
318
|
+
fs.mkdirSync(agentsDest, { recursive: true });
|
|
269
319
|
}
|
|
320
|
+
for (const agent of managed.agents) {
|
|
321
|
+
const src = path.join(agentsSrc, agent);
|
|
322
|
+
const dest = path.join(agentsDest, agent);
|
|
323
|
+
if (fs.existsSync(src)) {
|
|
324
|
+
fs.copyFileSync(src, dest);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
log.success('Updated agents/ (workflow files only)');
|
|
270
328
|
}
|
|
271
|
-
log.success('Updated agents/ (workflow files only)');
|
|
272
329
|
|
|
273
330
|
// Update specific skills
|
|
274
331
|
const skillsSrc = path.join(sourcePath, 'skills');
|
|
@@ -324,175 +381,13 @@ function selectiveUpdate(targetPath, sourcePath) {
|
|
|
324
381
|
log.success('Updated templates/ (workflow files only)');
|
|
325
382
|
}
|
|
326
383
|
|
|
327
|
-
// Detect project type by examining files in current directory
|
|
328
|
-
function detectProjectType() {
|
|
329
|
-
const cwd = process.cwd();
|
|
330
|
-
|
|
331
|
-
if (fs.existsSync(path.join(cwd, 'package.json'))) {
|
|
332
|
-
const pkg = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8'));
|
|
333
|
-
if (pkg.dependencies?.['next'] || pkg.devDependencies?.['next']) return 'nextjs';
|
|
334
|
-
if (pkg.dependencies?.['express'] || pkg.devDependencies?.['express']) return 'express';
|
|
335
|
-
if (pkg.dependencies?.['@nestjs/core']) return 'nestjs';
|
|
336
|
-
return 'javascript';
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
if (fs.existsSync(path.join(cwd, 'build.gradle')) || fs.existsSync(path.join(cwd, 'build.gradle.kts'))) {
|
|
340
|
-
return 'gradle-java';
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
if (fs.existsSync(path.join(cwd, 'pom.xml'))) {
|
|
344
|
-
return 'maven-java';
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
if (fs.existsSync(path.join(cwd, 'Cargo.toml'))) {
|
|
348
|
-
return 'rust';
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
if (fs.existsSync(path.join(cwd, 'go.mod'))) {
|
|
352
|
-
return 'go';
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
if (fs.existsSync(path.join(cwd, 'requirements.txt')) || fs.existsSync(path.join(cwd, 'pyproject.toml'))) {
|
|
356
|
-
const hasDjango = fs.existsSync(path.join(cwd, 'manage.py'));
|
|
357
|
-
const hasFlask = fs.existsSync(path.join(cwd, 'app.py')) || fs.existsSync(path.join(cwd, 'wsgi.py'));
|
|
358
|
-
if (hasDjango) return 'django';
|
|
359
|
-
if (hasFlask) return 'flask';
|
|
360
|
-
return 'python';
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
return 'unknown';
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
// Get default config based on project type
|
|
367
|
-
function getDefaultConfig(projectType) {
|
|
368
|
-
const baseConfig = {
|
|
369
|
-
ticket: {
|
|
370
|
-
pattern: '[A-Z]+-\\d+',
|
|
371
|
-
extractFromBranch: true
|
|
372
|
-
},
|
|
373
|
-
build: {
|
|
374
|
-
command: 'auto',
|
|
375
|
-
testCommand: 'auto'
|
|
376
|
-
},
|
|
377
|
-
reviewTool: 'claude',
|
|
378
|
-
git: {
|
|
379
|
-
autoCommit: false,
|
|
380
|
-
commitMessage: { pattern: '{ticket-id} {short-description}' }
|
|
381
|
-
}
|
|
382
|
-
};
|
|
383
|
-
|
|
384
|
-
// Project-specific overrides
|
|
385
|
-
const overrides = {
|
|
386
|
-
'gradle-java': {
|
|
387
|
-
build: {
|
|
388
|
-
command: './gradlew build -x test -x javadoc --offline',
|
|
389
|
-
testCommand: './gradlew test --offline'
|
|
390
|
-
}
|
|
391
|
-
},
|
|
392
|
-
'maven-java': {
|
|
393
|
-
build: {
|
|
394
|
-
command: 'mvn compile',
|
|
395
|
-
testCommand: 'mvn test'
|
|
396
|
-
}
|
|
397
|
-
},
|
|
398
|
-
'javascript': {
|
|
399
|
-
build: {
|
|
400
|
-
command: 'npm run build',
|
|
401
|
-
testCommand: 'npm test'
|
|
402
|
-
}
|
|
403
|
-
},
|
|
404
|
-
'nextjs': {
|
|
405
|
-
build: {
|
|
406
|
-
command: 'npm run build',
|
|
407
|
-
testCommand: 'npm test'
|
|
408
|
-
}
|
|
409
|
-
},
|
|
410
|
-
'express': {
|
|
411
|
-
build: {
|
|
412
|
-
command: 'npm run build || tsc',
|
|
413
|
-
testCommand: 'npm test'
|
|
414
|
-
}
|
|
415
|
-
},
|
|
416
|
-
'nestjs': {
|
|
417
|
-
build: {
|
|
418
|
-
command: 'npm run build',
|
|
419
|
-
testCommand: 'npm test'
|
|
420
|
-
}
|
|
421
|
-
},
|
|
422
|
-
'rust': {
|
|
423
|
-
build: {
|
|
424
|
-
command: 'cargo build',
|
|
425
|
-
testCommand: 'cargo test'
|
|
426
|
-
}
|
|
427
|
-
},
|
|
428
|
-
'go': {
|
|
429
|
-
build: {
|
|
430
|
-
command: 'go build ./...',
|
|
431
|
-
testCommand: 'go test ./...'
|
|
432
|
-
}
|
|
433
|
-
},
|
|
434
|
-
'python': {
|
|
435
|
-
build: {
|
|
436
|
-
command: 'python -m py_compile **/*.py',
|
|
437
|
-
testCommand: 'pytest'
|
|
438
|
-
}
|
|
439
|
-
},
|
|
440
|
-
'django': {
|
|
441
|
-
build: {
|
|
442
|
-
command: 'python manage.py check',
|
|
443
|
-
testCommand: 'python manage.py test'
|
|
444
|
-
}
|
|
445
|
-
},
|
|
446
|
-
'flask': {
|
|
447
|
-
build: {
|
|
448
|
-
command: 'python -m py_compile **/*.py',
|
|
449
|
-
testCommand: 'pytest'
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
};
|
|
453
|
-
|
|
454
|
-
return {
|
|
455
|
-
...baseConfig,
|
|
456
|
-
...(overrides[projectType] || {}),
|
|
457
|
-
projectType
|
|
458
|
-
};
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
// Initialize config file
|
|
462
|
-
function initializeConfig(targetPath) {
|
|
463
|
-
const configDir = path.join(targetPath, '.5');
|
|
464
|
-
const configFile = path.join(configDir, 'config.json');
|
|
465
|
-
|
|
466
|
-
if (fs.existsSync(configFile)) {
|
|
467
|
-
log.info('Config file already exists, skipping initialization');
|
|
468
|
-
return;
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
if (!fs.existsSync(configDir)) {
|
|
472
|
-
fs.mkdirSync(configDir, { recursive: true });
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
// Create features subdirectory
|
|
476
|
-
const featuresDir = path.join(configDir, 'features');
|
|
477
|
-
if (!fs.existsSync(featuresDir)) {
|
|
478
|
-
fs.mkdirSync(featuresDir, { recursive: true });
|
|
479
|
-
log.success('Created .5/features/ directory');
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
const projectType = detectProjectType();
|
|
483
|
-
const config = getDefaultConfig(projectType);
|
|
484
|
-
|
|
485
|
-
fs.writeFileSync(configFile, JSON.stringify(config, null, 2));
|
|
486
|
-
log.success(`Created config file with detected project type: ${projectType}`);
|
|
487
|
-
}
|
|
488
|
-
|
|
489
384
|
// Initialize version.json after successful install
|
|
490
|
-
function initializeVersionJson(
|
|
491
|
-
const
|
|
492
|
-
const versionFile = path.join(
|
|
385
|
+
function initializeVersionJson(isGlobal) {
|
|
386
|
+
const dataDir = getDataPath(isGlobal);
|
|
387
|
+
const versionFile = path.join(dataDir, 'version.json');
|
|
493
388
|
|
|
494
|
-
if (!fs.existsSync(
|
|
495
|
-
fs.mkdirSync(
|
|
389
|
+
if (!fs.existsSync(dataDir)) {
|
|
390
|
+
fs.mkdirSync(dataDir, { recursive: true });
|
|
496
391
|
}
|
|
497
392
|
|
|
498
393
|
const version = getPackageVersion();
|
|
@@ -512,16 +407,42 @@ function initializeVersionJson(targetPath, isGlobal) {
|
|
|
512
407
|
log.success('Initialized version tracking');
|
|
513
408
|
}
|
|
514
409
|
|
|
410
|
+
// Merge hook arrays by matching on the command path.
|
|
411
|
+
// Keeps user overrides for existing hooks, adds new hooks from source.
|
|
412
|
+
function mergeHookArrays(targetArr, sourceArr) {
|
|
413
|
+
const result = [...targetArr];
|
|
414
|
+
for (const sourceEntry of sourceArr) {
|
|
415
|
+
const sourceCmd = sourceEntry.hooks?.[0]?.command || '';
|
|
416
|
+
const exists = result.some(entry => {
|
|
417
|
+
const cmd = entry.hooks?.[0]?.command || '';
|
|
418
|
+
return cmd === sourceCmd;
|
|
419
|
+
});
|
|
420
|
+
if (!exists) {
|
|
421
|
+
result.push(sourceEntry);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
return result;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Hook event keys that contain arrays of hook entries
|
|
428
|
+
const HOOK_ARRAY_KEYS = new Set([
|
|
429
|
+
'PreToolUse', 'PostToolUse', 'SessionStart', 'SessionEnd',
|
|
430
|
+
'PreCompact', 'PostCompact'
|
|
431
|
+
]);
|
|
432
|
+
|
|
515
433
|
// Deep merge for settings.json
|
|
516
|
-
function deepMerge(target, source) {
|
|
434
|
+
function deepMerge(target, source, parentKey) {
|
|
517
435
|
const result = { ...target };
|
|
518
436
|
|
|
519
437
|
for (const key in source) {
|
|
520
438
|
if (source[key] !== null && typeof source[key] === 'object' && !Array.isArray(source[key])) {
|
|
521
439
|
// Recursively merge nested objects
|
|
522
|
-
result[key] = deepMerge(result[key] || {}, source[key]);
|
|
440
|
+
result[key] = deepMerge(result[key] || {}, source[key], key);
|
|
441
|
+
} else if (Array.isArray(source[key]) && Array.isArray(result[key]) && HOOK_ARRAY_KEYS.has(key)) {
|
|
442
|
+
// Hook arrays: merge by command path to add new hooks
|
|
443
|
+
result[key] = mergeHookArrays(result[key], source[key]);
|
|
523
444
|
} else {
|
|
524
|
-
// For primitives and arrays: use source if target doesn't have it
|
|
445
|
+
// For primitives and non-hook arrays: use source if target doesn't have it
|
|
525
446
|
if (!(key in result)) {
|
|
526
447
|
result[key] = source[key];
|
|
527
448
|
}
|
|
@@ -560,7 +481,7 @@ function checkExistingInstallation(targetPath) {
|
|
|
560
481
|
}
|
|
561
482
|
|
|
562
483
|
// Helper to show commands
|
|
563
|
-
function showCommandsHelp(
|
|
484
|
+
function showCommandsHelp(isGlobal) {
|
|
564
485
|
log.info('Available commands:');
|
|
565
486
|
log.info(' /5:plan-feature - Start feature planning (Phase 1)');
|
|
566
487
|
log.info(' /5:plan-implementation - Create implementation plan (Phase 2)');
|
|
@@ -568,8 +489,9 @@ function showCommandsHelp(targetPath) {
|
|
|
568
489
|
log.info(' /5:verify-implementation - Verify implementation (Phase 4)');
|
|
569
490
|
log.info(' /5:review-code - Code review (Phase 5)');
|
|
570
491
|
log.info(' /5:configure - Interactive project setup');
|
|
492
|
+
log.info(' /5:unlock - Remove planning guard lock');
|
|
571
493
|
log.info('');
|
|
572
|
-
log.info(`Config file: ${path.join(
|
|
494
|
+
log.info(`Config file: ${path.join(getDataPath(isGlobal), 'config.json')}`);
|
|
573
495
|
}
|
|
574
496
|
|
|
575
497
|
// Fresh installation
|
|
@@ -596,7 +518,7 @@ function performFreshInstall(targetPath, sourcePath, isGlobal) {
|
|
|
596
518
|
mergeSettings(targetPath, sourcePath);
|
|
597
519
|
|
|
598
520
|
// Initialize version tracking
|
|
599
|
-
initializeVersionJson(
|
|
521
|
+
initializeVersionJson(isGlobal);
|
|
600
522
|
|
|
601
523
|
log.header('Installation Complete!');
|
|
602
524
|
log.info('');
|
|
@@ -610,10 +532,10 @@ function performFreshInstall(targetPath, sourcePath, isGlobal) {
|
|
|
610
532
|
log.info(' • Create project-specific skills');
|
|
611
533
|
log.info('');
|
|
612
534
|
|
|
613
|
-
showCommandsHelp(
|
|
535
|
+
showCommandsHelp(isGlobal);
|
|
614
536
|
}
|
|
615
537
|
|
|
616
|
-
// Perform update (preserves .5/ directory
|
|
538
|
+
// Perform update (preserves user-created files, updates .5/ data directory)
|
|
617
539
|
function performUpdate(targetPath, sourcePath, isGlobal, versionInfo) {
|
|
618
540
|
log.header(`Updating from ${versionInfo.installed || 'legacy'} to ${versionInfo.available}`);
|
|
619
541
|
log.info('Preserving user-created commands, agents, skills, and hooks');
|
|
@@ -625,8 +547,8 @@ function performUpdate(targetPath, sourcePath, isGlobal, versionInfo) {
|
|
|
625
547
|
mergeSettings(targetPath, sourcePath);
|
|
626
548
|
|
|
627
549
|
// Update version.json
|
|
628
|
-
const
|
|
629
|
-
const versionFile = path.join(
|
|
550
|
+
const dataDir = getDataPath(isGlobal);
|
|
551
|
+
const versionFile = path.join(dataDir, 'version.json');
|
|
630
552
|
|
|
631
553
|
let versionData;
|
|
632
554
|
if (fs.existsSync(versionFile)) {
|
|
@@ -644,22 +566,22 @@ function performUpdate(targetPath, sourcePath, isGlobal, versionInfo) {
|
|
|
644
566
|
versionData.installedVersion = versionInfo.available;
|
|
645
567
|
versionData.lastUpdated = new Date().toISOString();
|
|
646
568
|
|
|
647
|
-
if (!fs.existsSync(
|
|
648
|
-
fs.mkdirSync(
|
|
569
|
+
if (!fs.existsSync(dataDir)) {
|
|
570
|
+
fs.mkdirSync(dataDir, { recursive: true });
|
|
649
571
|
}
|
|
650
572
|
fs.writeFileSync(versionFile, JSON.stringify(versionData, null, 2));
|
|
651
573
|
|
|
652
574
|
// Create features directory if it doesn't exist
|
|
653
|
-
const featuresDir = path.join(
|
|
575
|
+
const featuresDir = path.join(dataDir, 'features');
|
|
654
576
|
if (!fs.existsSync(featuresDir)) {
|
|
655
577
|
fs.mkdirSync(featuresDir, { recursive: true });
|
|
656
|
-
log.info('
|
|
657
|
-
log.info('
|
|
578
|
+
log.info('Feature folders nest under .5/features/');
|
|
579
|
+
log.info('See RELEASE_NOTES.md for migration if you have in-progress features');
|
|
658
580
|
}
|
|
659
581
|
|
|
660
582
|
log.header('Update Complete!');
|
|
661
583
|
log.success(`Now running version ${versionInfo.available}`);
|
|
662
|
-
showCommandsHelp(
|
|
584
|
+
showCommandsHelp(isGlobal);
|
|
663
585
|
}
|
|
664
586
|
|
|
665
587
|
// Perform installation
|
|
@@ -671,8 +593,11 @@ function install(isGlobal, forceUpgrade = false) {
|
|
|
671
593
|
log.info(`Target: ${targetPath}`);
|
|
672
594
|
log.info(`Source: ${sourcePath}`);
|
|
673
595
|
|
|
596
|
+
// Migrate data from old .claude/.5/ to new .5/ location
|
|
597
|
+
migrateDataDir(isGlobal);
|
|
598
|
+
|
|
674
599
|
// Check for existing installation and version
|
|
675
|
-
const versionInfo = getVersionInfo(targetPath);
|
|
600
|
+
const versionInfo = getVersionInfo(targetPath, isGlobal);
|
|
676
601
|
|
|
677
602
|
if (versionInfo.exists) {
|
|
678
603
|
if (versionInfo.legacy) {
|
|
@@ -727,21 +652,54 @@ function uninstall() {
|
|
|
727
652
|
return;
|
|
728
653
|
}
|
|
729
654
|
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
655
|
+
const managed = getWorkflowManagedFiles();
|
|
656
|
+
|
|
657
|
+
// Remove commands/5/ (workflow namespace only)
|
|
658
|
+
const commands5 = path.join(targetPath, 'commands', '5');
|
|
659
|
+
if (fs.existsSync(commands5)) {
|
|
660
|
+
removeDir(commands5);
|
|
661
|
+
log.success('Removed commands/5/');
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// Remove only workflow-managed skills
|
|
665
|
+
for (const skill of managed.skills) {
|
|
666
|
+
const skillPath = path.join(targetPath, 'skills', skill);
|
|
667
|
+
if (fs.existsSync(skillPath)) {
|
|
668
|
+
removeDir(skillPath);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
log.success('Removed workflow skills (preserved user-created skills)');
|
|
672
|
+
|
|
673
|
+
// Remove only workflow-managed hooks
|
|
674
|
+
for (const hook of managed.hooks) {
|
|
675
|
+
const hookPath = path.join(targetPath, 'hooks', hook);
|
|
676
|
+
if (fs.existsSync(hookPath)) {
|
|
677
|
+
fs.unlinkSync(hookPath);
|
|
737
678
|
}
|
|
738
679
|
}
|
|
680
|
+
log.success('Removed workflow hooks (preserved user-created hooks)');
|
|
681
|
+
|
|
682
|
+
// Remove only workflow-managed templates
|
|
683
|
+
for (const template of managed.templates) {
|
|
684
|
+
const templatePath = path.join(targetPath, 'templates', template);
|
|
685
|
+
if (fs.existsSync(templatePath)) {
|
|
686
|
+
fs.unlinkSync(templatePath);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
log.success('Removed workflow templates (preserved user-created templates)');
|
|
690
|
+
|
|
691
|
+
// Remove data directory (.5/)
|
|
692
|
+
const dataDir = getDataPath(false);
|
|
693
|
+
if (fs.existsSync(dataDir)) {
|
|
694
|
+
removeDir(dataDir);
|
|
695
|
+
log.success('Removed .5/ data directory');
|
|
696
|
+
}
|
|
739
697
|
|
|
740
|
-
//
|
|
741
|
-
const
|
|
742
|
-
if (fs.existsSync(
|
|
743
|
-
removeDir(
|
|
744
|
-
log.success('Removed .5/
|
|
698
|
+
// Also clean up old .claude/.5/ if it still exists
|
|
699
|
+
const oldDataDir = path.join(targetPath, '.5');
|
|
700
|
+
if (fs.existsSync(oldDataDir)) {
|
|
701
|
+
removeDir(oldDataDir);
|
|
702
|
+
log.success('Removed legacy .claude/.5/ directory');
|
|
745
703
|
}
|
|
746
704
|
|
|
747
705
|
log.header('Uninstallation Complete!');
|
|
@@ -758,7 +716,8 @@ function main() {
|
|
|
758
716
|
|
|
759
717
|
if (options.check) {
|
|
760
718
|
const targetPath = getTargetPath(options.global);
|
|
761
|
-
|
|
719
|
+
migrateDataDir(options.global);
|
|
720
|
+
const versionInfo = getVersionInfo(targetPath, options.global);
|
|
762
721
|
|
|
763
722
|
if (!versionInfo.exists) {
|
|
764
723
|
log.info('Not installed');
|
package/docs/workflow-guide.md
CHANGED
|
@@ -72,7 +72,7 @@ The installer copies workflow files to `.claude/` directory. **It does NOT creat
|
|
|
72
72
|
4. **Phase 4** (`/5:verify-implementation`): Verifies configuration
|
|
73
73
|
|
|
74
74
|
After completing these steps, you have:
|
|
75
|
-
- `.
|
|
75
|
+
- `.5/config.json` with your project settings
|
|
76
76
|
- `CLAUDE.md` with comprehensive project documentation
|
|
77
77
|
- `.5/STRUCTURE.md`, `.5/STACK.md`, etc. with modular documentation
|
|
78
78
|
- Project-specific skills in `.claude/skills/`
|
|
@@ -295,7 +295,7 @@ Map feature requirements to technical components, skills, and dependency steps.
|
|
|
295
295
|
- **Step 2 (Logic)**: Business logic, services, handlers (sequential if needed)
|
|
296
296
|
- **Step 3 (Integration)**: API routes, wiring, tests (sequential for integration)
|
|
297
297
|
|
|
298
|
-
Note: Step count and organization is configurable in `.
|
|
298
|
+
Note: Step count and organization is configurable in `.5/config.json`
|
|
299
299
|
|
|
300
300
|
6. **Implementation Plan Creation**: Claude writes an **atomic plan structure** to:
|
|
301
301
|
```
|
|
@@ -997,7 +997,7 @@ If working on multiple features:
|
|
|
997
997
|
|
|
998
998
|
## Configuring for Different Tech Stacks
|
|
999
999
|
|
|
1000
|
-
The 5-phase workflow is designed to work with any technology stack. Configuration is stored in `.
|
|
1000
|
+
The 5-phase workflow is designed to work with any technology stack. Configuration is stored in `.5/config.json` and can be customized using the `/5:configure` command.
|
|
1001
1001
|
|
|
1002
1002
|
### Quick Setup
|
|
1003
1003
|
|
|
@@ -1096,7 +1096,7 @@ You can override auto-detected values in the config file.
|
|
|
1096
1096
|
|
|
1097
1097
|
### Further Customization
|
|
1098
1098
|
|
|
1099
|
-
See the `/5:configure` command for interactive configuration, or manually edit `.
|
|
1099
|
+
See the `/5:configure` command for interactive configuration, or manually edit `.5/config.json` to customize:
|
|
1100
1100
|
- Ticket ID patterns
|
|
1101
1101
|
- Branch naming conventions
|
|
1102
1102
|
- Build and test commands
|