5-phase-workflow 1.4.2 → 1.4.4
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/bin/install.js +81 -188
- package/package.json +1 -1
- package/src/commands/5/configure.md +122 -326
- package/src/commands/5/discuss-feature.md +7 -172
- package/src/commands/5/implement-feature.md +33 -151
- package/src/commands/5/plan-feature.md +2 -8
- package/src/commands/5/plan-implementation.md +0 -10
- package/src/commands/5/quick-implement.md +34 -141
- package/src/commands/5/review-code.md +2 -12
- package/src/commands/5/update.md +17 -0
- package/src/commands/5/verify-implementation.md +32 -199
- package/src/hooks/check-updates.js +11 -13
- package/src/hooks/config-guard.js +30 -0
- package/src/hooks/plan-guard.js +52 -28
- package/src/hooks/statusline.js +29 -3
- package/src/settings.json +11 -1
- package/src/skills/build-project/SKILL.md +4 -130
- package/src/skills/configure-project/SKILL.md +26 -215
- package/src/skills/run-tests/SKILL.md +5 -208
- package/src/templates/workflow/QUICK-PLAN.md +0 -17
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;
|
|
@@ -214,7 +215,8 @@ function getWorkflowManagedFiles() {
|
|
|
214
215
|
hooks: [
|
|
215
216
|
'statusline.js',
|
|
216
217
|
'check-updates.js',
|
|
217
|
-
'plan-guard.js'
|
|
218
|
+
'plan-guard.js',
|
|
219
|
+
'config-guard.js'
|
|
218
220
|
],
|
|
219
221
|
|
|
220
222
|
// Templates: specific template files
|
|
@@ -234,7 +236,6 @@ function getWorkflowManagedFiles() {
|
|
|
234
236
|
'workflow/VERIFICATION-REPORT.md',
|
|
235
237
|
'workflow/REVIEW-FINDINGS.md',
|
|
236
238
|
'workflow/REVIEW-SUMMARY.md',
|
|
237
|
-
'workflow/QUICK-PLAN.md',
|
|
238
239
|
'workflow/FIX-PLAN.md'
|
|
239
240
|
]
|
|
240
241
|
};
|
|
@@ -255,20 +256,22 @@ function selectiveUpdate(targetPath, sourcePath) {
|
|
|
255
256
|
log.success('Updated commands/5/');
|
|
256
257
|
}
|
|
257
258
|
|
|
258
|
-
// Update specific agents
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
fs.
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
fs.
|
|
259
|
+
// Update specific agents (currently none — instructions are embedded inline in commands)
|
|
260
|
+
if (managed.agents.length > 0) {
|
|
261
|
+
const agentsSrc = path.join(sourcePath, 'agents');
|
|
262
|
+
const agentsDest = path.join(targetPath, 'agents');
|
|
263
|
+
if (!fs.existsSync(agentsDest)) {
|
|
264
|
+
fs.mkdirSync(agentsDest, { recursive: true });
|
|
265
|
+
}
|
|
266
|
+
for (const agent of managed.agents) {
|
|
267
|
+
const src = path.join(agentsSrc, agent);
|
|
268
|
+
const dest = path.join(agentsDest, agent);
|
|
269
|
+
if (fs.existsSync(src)) {
|
|
270
|
+
fs.copyFileSync(src, dest);
|
|
271
|
+
}
|
|
269
272
|
}
|
|
273
|
+
log.success('Updated agents/ (workflow files only)');
|
|
270
274
|
}
|
|
271
|
-
log.success('Updated agents/ (workflow files only)');
|
|
272
275
|
|
|
273
276
|
// Update specific skills
|
|
274
277
|
const skillsSrc = path.join(sourcePath, 'skills');
|
|
@@ -324,168 +327,6 @@ function selectiveUpdate(targetPath, sourcePath) {
|
|
|
324
327
|
log.success('Updated templates/ (workflow files only)');
|
|
325
328
|
}
|
|
326
329
|
|
|
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
330
|
// Initialize version.json after successful install
|
|
490
331
|
function initializeVersionJson(targetPath, isGlobal) {
|
|
491
332
|
const configDir = path.join(targetPath, '.5');
|
|
@@ -512,16 +353,42 @@ function initializeVersionJson(targetPath, isGlobal) {
|
|
|
512
353
|
log.success('Initialized version tracking');
|
|
513
354
|
}
|
|
514
355
|
|
|
356
|
+
// Merge hook arrays by matching on the command path.
|
|
357
|
+
// Keeps user overrides for existing hooks, adds new hooks from source.
|
|
358
|
+
function mergeHookArrays(targetArr, sourceArr) {
|
|
359
|
+
const result = [...targetArr];
|
|
360
|
+
for (const sourceEntry of sourceArr) {
|
|
361
|
+
const sourceCmd = sourceEntry.hooks?.[0]?.command || '';
|
|
362
|
+
const exists = result.some(entry => {
|
|
363
|
+
const cmd = entry.hooks?.[0]?.command || '';
|
|
364
|
+
return cmd === sourceCmd;
|
|
365
|
+
});
|
|
366
|
+
if (!exists) {
|
|
367
|
+
result.push(sourceEntry);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return result;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Hook event keys that contain arrays of hook entries
|
|
374
|
+
const HOOK_ARRAY_KEYS = new Set([
|
|
375
|
+
'PreToolUse', 'PostToolUse', 'SessionStart', 'SessionEnd',
|
|
376
|
+
'PreCompact', 'PostCompact'
|
|
377
|
+
]);
|
|
378
|
+
|
|
515
379
|
// Deep merge for settings.json
|
|
516
|
-
function deepMerge(target, source) {
|
|
380
|
+
function deepMerge(target, source, parentKey) {
|
|
517
381
|
const result = { ...target };
|
|
518
382
|
|
|
519
383
|
for (const key in source) {
|
|
520
384
|
if (source[key] !== null && typeof source[key] === 'object' && !Array.isArray(source[key])) {
|
|
521
385
|
// Recursively merge nested objects
|
|
522
|
-
result[key] = deepMerge(result[key] || {}, source[key]);
|
|
386
|
+
result[key] = deepMerge(result[key] || {}, source[key], key);
|
|
387
|
+
} else if (Array.isArray(source[key]) && Array.isArray(result[key]) && HOOK_ARRAY_KEYS.has(key)) {
|
|
388
|
+
// Hook arrays: merge by command path to add new hooks
|
|
389
|
+
result[key] = mergeHookArrays(result[key], source[key]);
|
|
523
390
|
} else {
|
|
524
|
-
// For primitives and arrays: use source if target doesn't have it
|
|
391
|
+
// For primitives and non-hook arrays: use source if target doesn't have it
|
|
525
392
|
if (!(key in result)) {
|
|
526
393
|
result[key] = source[key];
|
|
527
394
|
}
|
|
@@ -727,15 +594,41 @@ function uninstall() {
|
|
|
727
594
|
return;
|
|
728
595
|
}
|
|
729
596
|
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
597
|
+
const managed = getWorkflowManagedFiles();
|
|
598
|
+
|
|
599
|
+
// Remove commands/5/ (workflow namespace only)
|
|
600
|
+
const commands5 = path.join(targetPath, 'commands', '5');
|
|
601
|
+
if (fs.existsSync(commands5)) {
|
|
602
|
+
removeDir(commands5);
|
|
603
|
+
log.success('Removed commands/5/');
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// Remove only workflow-managed skills
|
|
607
|
+
for (const skill of managed.skills) {
|
|
608
|
+
const skillPath = path.join(targetPath, 'skills', skill);
|
|
609
|
+
if (fs.existsSync(skillPath)) {
|
|
610
|
+
removeDir(skillPath);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
log.success('Removed workflow skills (preserved user-created skills)');
|
|
614
|
+
|
|
615
|
+
// Remove only workflow-managed hooks
|
|
616
|
+
for (const hook of managed.hooks) {
|
|
617
|
+
const hookPath = path.join(targetPath, 'hooks', hook);
|
|
618
|
+
if (fs.existsSync(hookPath)) {
|
|
619
|
+
fs.unlinkSync(hookPath);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
log.success('Removed workflow hooks (preserved user-created hooks)');
|
|
623
|
+
|
|
624
|
+
// Remove only workflow-managed templates
|
|
625
|
+
for (const template of managed.templates) {
|
|
626
|
+
const templatePath = path.join(targetPath, 'templates', template);
|
|
627
|
+
if (fs.existsSync(templatePath)) {
|
|
628
|
+
fs.unlinkSync(templatePath);
|
|
737
629
|
}
|
|
738
630
|
}
|
|
631
|
+
log.success('Removed workflow templates (preserved user-created templates)');
|
|
739
632
|
|
|
740
633
|
// Remove config
|
|
741
634
|
const configDir = path.join(targetPath, '.5');
|