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 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(Number);
28
- const parts2 = v2.split('.').map(Number);
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
- const agentsSrc = path.join(sourcePath, 'agents');
260
- const agentsDest = path.join(targetPath, 'agents');
261
- if (!fs.existsSync(agentsDest)) {
262
- fs.mkdirSync(agentsDest, { recursive: true });
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);
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
- // Remove directories
731
- const dirs = ['commands/5', 'agents', 'skills', 'hooks', 'templates'];
732
- for (const dir of dirs) {
733
- const fullPath = path.join(targetPath, dir);
734
- if (fs.existsSync(fullPath)) {
735
- removeDir(fullPath);
736
- log.success(`Removed ${dir}`);
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');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "5-phase-workflow",
3
- "version": "1.4.2",
3
+ "version": "1.4.4",
4
4
  "description": "A 5-phase feature development workflow for Claude Code",
5
5
  "bin": {
6
6
  "5-phase-workflow": "bin/install.js"