5-phase-workflow 1.8.1 → 1.8.2

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.
Files changed (2) hide show
  1. package/bin/install.js +114 -3
  2. package/package.json +1 -1
package/bin/install.js CHANGED
@@ -248,6 +248,19 @@ function removeDir(dir) {
248
248
  }
249
249
  }
250
250
 
251
+ // Files removed from the package before manifest tracking existed.
252
+ // This list is frozen — future removals are handled by manifest diffing.
253
+ const LEGACY_REMOVED_FILES = [
254
+ 'agents/feature-planner.md',
255
+ 'agents/implementation-planner.md',
256
+ 'agents/review-processor.md',
257
+ 'agents/step-executor.md',
258
+ 'agents/integration-agent.md',
259
+ 'agents/step-fixer.md',
260
+ 'agents/step-verifier.md',
261
+ 'agents/verification-agent.md'
262
+ ];
263
+
251
264
  // Get list of workflow-owned files/directories (not user-created)
252
265
  function getWorkflowManagedFiles() {
253
266
  return {
@@ -303,6 +316,46 @@ function getWorkflowManagedFiles() {
303
316
  };
304
317
  }
305
318
 
319
+ // Flatten getWorkflowManagedFiles() into a list of relative paths (relative to .claude/)
320
+ function getFileManifest() {
321
+ const managed = getWorkflowManagedFiles();
322
+ const manifest = [];
323
+
324
+ // Commands are directories
325
+ for (const cmd of managed.commands) {
326
+ manifest.push(`commands/${cmd}`);
327
+ }
328
+
329
+ // Agents are files
330
+ for (const agent of managed.agents) {
331
+ manifest.push(`agents/${agent}`);
332
+ }
333
+
334
+ // Skills are directories
335
+ for (const skill of managed.skills) {
336
+ manifest.push(`skills/${skill}`);
337
+ }
338
+
339
+ // Hooks are files
340
+ for (const hook of managed.hooks) {
341
+ manifest.push(`hooks/${hook}`);
342
+ }
343
+
344
+ // Templates are files (may include nested paths like workflow/FEATURE-SPEC.md)
345
+ for (const template of managed.templates) {
346
+ manifest.push(`templates/${template}`);
347
+ }
348
+
349
+ // References are files
350
+ if (managed.references) {
351
+ for (const ref of managed.references) {
352
+ manifest.push(`references/${ref}`);
353
+ }
354
+ }
355
+
356
+ return manifest;
357
+ }
358
+
306
359
  // Selectively update only workflow-managed files, preserve user content
307
360
  function selectiveUpdate(targetPath, sourcePath) {
308
361
  const managed = getWorkflowManagedFiles();
@@ -406,6 +459,50 @@ function selectiveUpdate(targetPath, sourcePath) {
406
459
  }
407
460
  }
408
461
 
462
+ // Clean up files that were previously installed but are no longer managed
463
+ function cleanupOrphanedFiles(targetPath, dataDir) {
464
+ const versionFile = path.join(dataDir, 'version.json');
465
+ const currentManifest = getFileManifest();
466
+ const currentSet = new Set(currentManifest);
467
+
468
+ let oldManifest = null;
469
+ if (fs.existsSync(versionFile)) {
470
+ try {
471
+ const data = JSON.parse(fs.readFileSync(versionFile, 'utf8'));
472
+ oldManifest = data.manifest || null;
473
+ } catch (e) {
474
+ // Corrupted file, treat as no manifest
475
+ }
476
+ }
477
+
478
+ if (oldManifest) {
479
+ // Manifest exists: diff old vs current, delete orphans
480
+ for (const entry of oldManifest) {
481
+ if (!currentSet.has(entry)) {
482
+ const fullPath = path.join(targetPath, entry);
483
+ if (fs.existsSync(fullPath)) {
484
+ const stat = fs.statSync(fullPath);
485
+ if (stat.isDirectory()) {
486
+ removeDir(fullPath);
487
+ } else {
488
+ fs.unlinkSync(fullPath);
489
+ }
490
+ log.info(`Removed orphaned: ${entry}`);
491
+ }
492
+ }
493
+ }
494
+ } else {
495
+ // No manifest (legacy upgrade): use static legacy removals list
496
+ for (const entry of LEGACY_REMOVED_FILES) {
497
+ const fullPath = path.join(targetPath, entry);
498
+ if (fs.existsSync(fullPath)) {
499
+ fs.unlinkSync(fullPath);
500
+ log.info(`Removed legacy orphan: ${entry}`);
501
+ }
502
+ }
503
+ }
504
+ }
505
+
409
506
  // Ensure .5/.gitignore exists and contains .update-cache.json
410
507
  function ensureDotFiveGitignore(dataDir) {
411
508
  const gitignorePath = path.join(dataDir, '.gitignore');
@@ -436,7 +533,8 @@ function initializeVersionJson(isGlobal) {
436
533
  packageVersion: version,
437
534
  installedAt: now,
438
535
  lastUpdated: now,
439
- installationType: isGlobal ? 'global' : 'local'
536
+ installationType: isGlobal ? 'global' : 'local',
537
+ manifest: getFileManifest()
440
538
  };
441
539
 
442
540
  fs.writeFileSync(versionFile, JSON.stringify(versionData, null, 2));
@@ -583,11 +681,14 @@ function performUpdate(targetPath, sourcePath, isGlobal, versionInfo) {
583
681
  // Selectively update only workflow-managed files (preserves user content)
584
682
  selectiveUpdate(targetPath, sourcePath);
585
683
 
684
+ // Clean up orphaned files from previous versions
685
+ const dataDir = getDataPath(isGlobal);
686
+ cleanupOrphanedFiles(targetPath, dataDir);
687
+
586
688
  // Merge settings (deep merge preserves user customizations)
587
689
  mergeSettings(targetPath, sourcePath);
588
690
 
589
691
  // Update version.json
590
- const dataDir = getDataPath(isGlobal);
591
692
  const versionFile = path.join(dataDir, 'version.json');
592
693
  const now = new Date().toISOString();
593
694
 
@@ -598,7 +699,8 @@ function performUpdate(targetPath, sourcePath, isGlobal, versionInfo) {
598
699
  packageVersion: versionInfo.available,
599
700
  installedAt: existing.installedAt || now,
600
701
  lastUpdated: now,
601
- installationType: existing.installationType || (isGlobal ? 'global' : 'local')
702
+ installationType: existing.installationType || (isGlobal ? 'global' : 'local'),
703
+ manifest: getFileManifest()
602
704
  };
603
705
 
604
706
  if (!fs.existsSync(dataDir)) {
@@ -690,6 +792,15 @@ function uninstall() {
690
792
 
691
793
  const managed = getWorkflowManagedFiles();
692
794
 
795
+ // Remove legacy orphaned files that may still exist from older versions
796
+ for (const entry of LEGACY_REMOVED_FILES) {
797
+ const fullPath = path.join(targetPath, entry);
798
+ if (fs.existsSync(fullPath)) {
799
+ fs.unlinkSync(fullPath);
800
+ log.info(`Removed legacy orphan: ${entry}`);
801
+ }
802
+ }
803
+
693
804
  // Remove commands/5/ (workflow namespace only)
694
805
  const commands5 = path.join(targetPath, 'commands', '5');
695
806
  if (fs.existsSync(commands5)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "5-phase-workflow",
3
- "version": "1.8.1",
3
+ "version": "1.8.2",
4
4
  "description": "A 5-phase feature development workflow for Claude Code",
5
5
  "bin": {
6
6
  "5-phase-workflow": "bin/install.js"