5-phase-workflow 1.9.0 → 1.9.1

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
@@ -38,13 +38,17 @@ function compareVersions(v1, v2) {
38
38
  }
39
39
 
40
40
  // Get installed version from .5/version.json
41
+ // Reads per-runtime version when available (runtimes[activeRuntime].packageVersion),
42
+ // falling back to top-level packageVersion for backward compatibility.
41
43
  function getInstalledVersion(isGlobal) {
42
44
  const versionFile = path.join(getDataPath(isGlobal), 'version.json');
43
45
  if (!fs.existsSync(versionFile)) return null;
44
46
 
45
47
  try {
46
48
  const data = JSON.parse(fs.readFileSync(versionFile, 'utf8'));
47
- return data.packageVersion;
49
+ return (data.runtimes && data.runtimes[activeRuntime] && data.runtimes[activeRuntime].packageVersion)
50
+ || data.packageVersion
51
+ || null;
48
52
  } catch (e) {
49
53
  return null; // Corrupted file, treat as missing
50
54
  }
@@ -624,7 +628,10 @@ function cleanupOrphanedFiles(targetPath, dataDir) {
624
628
  if (fs.existsSync(versionFile)) {
625
629
  try {
626
630
  const data = JSON.parse(fs.readFileSync(versionFile, 'utf8'));
627
- oldManifest = data.manifest || null;
631
+ // Prefer per-runtime manifest to avoid cross-runtime orphan cleanup
632
+ oldManifest = (data.runtimes && data.runtimes[activeRuntime] && data.runtimes[activeRuntime].manifest)
633
+ || data.manifest
634
+ || null;
628
635
  } catch (e) {
629
636
  // Corrupted file, treat as no manifest
630
637
  }
@@ -677,27 +684,73 @@ function ensureDotFiveGitignore(dataDir) {
677
684
  }
678
685
  }
679
686
 
680
- // Initialize version.json after successful install
681
- function initializeVersionJson(isGlobal) {
682
- const dataDir = getDataPath(isGlobal);
687
+ // Write (or update) version.json, preserving per-runtime state for other runtimes.
688
+ // - Reads existing file to preserve the other runtime's runtimes[] entry.
689
+ // - Writes runtimes[activeRuntime] with current version + manifest.
690
+ // - Top-level packageVersion = minimum across all runtime versions (so hooks
691
+ // detect an update if ANY runtime is behind).
692
+ function writeVersionJson(dataDir, isGlobal, version) {
683
693
  const versionFile = path.join(dataDir, 'version.json');
694
+ const now = new Date().toISOString();
684
695
 
685
- if (!fs.existsSync(dataDir)) {
686
- fs.mkdirSync(dataDir, { recursive: true });
696
+ let existing = {};
697
+ if (fs.existsSync(versionFile)) {
698
+ try {
699
+ existing = JSON.parse(fs.readFileSync(versionFile, 'utf8'));
700
+ } catch (e) {
701
+ existing = {};
702
+ }
687
703
  }
688
704
 
689
- const version = getPackageVersion();
690
- const now = new Date().toISOString();
691
-
692
- const versionData = {
705
+ const runtimes = existing.runtimes || {};
706
+ runtimes[activeRuntime] = {
693
707
  packageVersion: version,
694
- installedAt: now,
695
708
  lastUpdated: now,
696
- installationType: isGlobal ? 'global' : 'local',
697
709
  manifest: getFileManifest()
698
710
  };
699
711
 
712
+ // Top-level packageVersion = min across all runtime versions
713
+ const runtimeVersions = Object.values(runtimes).map(r => r.packageVersion).filter(Boolean);
714
+ const minVersion = runtimeVersions.reduce((min, v) => compareVersions(v, min) < 0 ? v : min, version);
715
+
716
+ const versionData = {
717
+ packageVersion: minVersion,
718
+ installedAt: existing.installedAt || now,
719
+ lastUpdated: now,
720
+ installationType: existing.installationType || (isGlobal ? 'global' : 'local'),
721
+ manifest: runtimes[activeRuntime].manifest,
722
+ runtimes
723
+ };
724
+
725
+ if (!fs.existsSync(dataDir)) {
726
+ fs.mkdirSync(dataDir, { recursive: true });
727
+ }
700
728
  fs.writeFileSync(versionFile, JSON.stringify(versionData, null, 2));
729
+ }
730
+
731
+ // Detect which runtimes are actually installed by probing marker files.
732
+ function getInstalledRuntimes(isGlobal) {
733
+ const installed = [];
734
+ const saved = activeRuntime;
735
+ for (const rt of ['claude', 'codex']) {
736
+ activeRuntime = rt;
737
+ const tp = getTargetPath(isGlobal);
738
+ if (checkExistingInstallation(tp)) installed.push(rt);
739
+ }
740
+ activeRuntime = saved;
741
+ return installed;
742
+ }
743
+
744
+ // Initialize version.json after successful install
745
+ function initializeVersionJson(isGlobal) {
746
+ const dataDir = getDataPath(isGlobal);
747
+
748
+ if (!fs.existsSync(dataDir)) {
749
+ fs.mkdirSync(dataDir, { recursive: true });
750
+ }
751
+
752
+ const version = getPackageVersion();
753
+ writeVersionJson(dataDir, isGlobal, version);
701
754
  ensureDotFiveGitignore(dataDir);
702
755
  log.success('Initialized version tracking');
703
756
  }
@@ -868,25 +921,8 @@ function performUpdate(targetPath, sourcePath, isGlobal, versionInfo) {
868
921
  // Merge settings (deep merge preserves user customizations)
869
922
  mergeSettings(targetPath, sourcePath);
870
923
 
871
- // Update version.json
872
- const versionFile = path.join(dataDir, 'version.json');
873
- const now = new Date().toISOString();
874
-
875
- const existing = fs.existsSync(versionFile)
876
- ? JSON.parse(fs.readFileSync(versionFile, 'utf8'))
877
- : {};
878
- const versionData = {
879
- packageVersion: versionInfo.available,
880
- installedAt: existing.installedAt || now,
881
- lastUpdated: now,
882
- installationType: existing.installationType || (isGlobal ? 'global' : 'local'),
883
- manifest: getFileManifest()
884
- };
885
-
886
- if (!fs.existsSync(dataDir)) {
887
- fs.mkdirSync(dataDir, { recursive: true });
888
- }
889
- fs.writeFileSync(versionFile, JSON.stringify(versionData, null, 2));
924
+ // Update version.json (per-runtime, preserving other runtime's state)
925
+ writeVersionJson(dataDir, isGlobal, versionInfo.available);
890
926
  ensureDotFiveGitignore(dataDir);
891
927
 
892
928
  // Create features directory if it doesn't exist
@@ -1092,24 +1128,8 @@ function performCodexUpdate(targetPath, sourcePath, isGlobal, versionInfo) {
1092
1128
  const dataDir = getDataPath(isGlobal);
1093
1129
  cleanupOrphanedFiles(targetPath, dataDir);
1094
1130
 
1095
- // Update version.json
1096
- const versionFile = path.join(dataDir, 'version.json');
1097
- const now = new Date().toISOString();
1098
- const existing = fs.existsSync(versionFile)
1099
- ? JSON.parse(fs.readFileSync(versionFile, 'utf8'))
1100
- : {};
1101
- const versionData = {
1102
- packageVersion: versionInfo.available,
1103
- installedAt: existing.installedAt || now,
1104
- lastUpdated: now,
1105
- installationType: existing.installationType || (isGlobal ? 'global' : 'local'),
1106
- manifest: getFileManifest()
1107
- };
1108
-
1109
- if (!fs.existsSync(dataDir)) {
1110
- fs.mkdirSync(dataDir, { recursive: true });
1111
- }
1112
- fs.writeFileSync(versionFile, JSON.stringify(versionData, null, 2));
1131
+ // Update version.json (per-runtime, preserving other runtime's state)
1132
+ writeVersionJson(dataDir, isGlobal, versionInfo.available);
1113
1133
  ensureDotFiveGitignore(dataDir);
1114
1134
 
1115
1135
  const featuresDir = path.join(dataDir, 'features');
@@ -1216,6 +1236,7 @@ function install(isGlobal, forceUpgrade = false) {
1216
1236
  log.warn('Detected legacy installation (no version tracking)');
1217
1237
  log.info(`Upgrading from legacy install to ${versionInfo.available}`);
1218
1238
  update(targetPath, sourcePath, isGlobal, versionInfo);
1239
+ updateOtherRuntime(isGlobal);
1219
1240
  return;
1220
1241
  } else if (versionInfo.needsUpdate) {
1221
1242
  log.info(`Installed: ${versionInfo.installed}`);
@@ -1235,11 +1256,13 @@ function install(isGlobal, forceUpgrade = false) {
1235
1256
  return;
1236
1257
  }
1237
1258
  update(targetPath, sourcePath, isGlobal, versionInfo);
1259
+ updateOtherRuntime(isGlobal);
1238
1260
  });
1239
1261
  return; // Wait for user input
1240
1262
  }
1241
1263
  // Force upgrade, no prompt
1242
1264
  update(targetPath, sourcePath, isGlobal, versionInfo);
1265
+ updateOtherRuntime(isGlobal);
1243
1266
  return;
1244
1267
  } else {
1245
1268
  // Same version
@@ -1252,6 +1275,31 @@ function install(isGlobal, forceUpgrade = false) {
1252
1275
  freshInstall(targetPath, sourcePath, isGlobal);
1253
1276
  }
1254
1277
 
1278
+ // After updating the active runtime, check if the other runtime is also installed
1279
+ // and needs an update. Updates it silently if so.
1280
+ function updateOtherRuntime(isGlobal) {
1281
+ const primaryRuntime = activeRuntime;
1282
+ const installed = getInstalledRuntimes(isGlobal);
1283
+
1284
+ for (const rt of installed) {
1285
+ if (rt === primaryRuntime) continue;
1286
+
1287
+ activeRuntime = rt;
1288
+ const tp = getTargetPath(isGlobal);
1289
+ const sp = getSourcePath();
1290
+ const vi = getVersionInfo(tp, isGlobal);
1291
+
1292
+ if (vi.exists && (vi.needsUpdate || vi.legacy)) {
1293
+ const updateFn = rt === 'codex' ? performCodexUpdate : performUpdate;
1294
+ updateFn(tp, sp, isGlobal, vi);
1295
+ } else if (vi.exists) {
1296
+ log.success(`${rt}: already at version ${vi.installed}`);
1297
+ }
1298
+ }
1299
+
1300
+ activeRuntime = primaryRuntime;
1301
+ }
1302
+
1255
1303
  // Perform uninstallation
1256
1304
  function uninstall() {
1257
1305
  if (activeRuntime === 'codex') {
@@ -1369,25 +1417,48 @@ function main() {
1369
1417
  }
1370
1418
 
1371
1419
  if (options.check) {
1372
- const targetPath = getTargetPath(options.global);
1373
1420
  migrateDataDir(options.global);
1374
- const versionInfo = getVersionInfo(targetPath, options.global);
1375
1421
 
1376
- if (!versionInfo.exists) {
1377
- log.info(`Not installed (${activeRuntime})`);
1422
+ // When --codex is explicitly passed, check only Codex. Otherwise check all installed runtimes.
1423
+ const runtimesToCheck = options.runtime === 'codex'
1424
+ ? ['codex']
1425
+ : getInstalledRuntimes(options.global);
1426
+
1427
+ if (runtimesToCheck.length === 0) {
1428
+ log.info('Not installed');
1378
1429
  return;
1379
1430
  }
1380
1431
 
1381
- log.info(`Runtime: ${activeRuntime}`);
1382
- log.info(`Installed: ${versionInfo.installed || 'legacy (no version)'}`);
1383
- log.info(`Available: ${versionInfo.available}`);
1432
+ const primaryRuntime = activeRuntime;
1433
+ let anyUpdateAvailable = false;
1384
1434
 
1385
- if (versionInfo.needsUpdate) {
1386
- log.warn('Update available');
1387
- log.info(`Run: npx 5-phase-workflow${activeRuntime === 'codex' ? ' --codex' : ''} --upgrade`);
1388
- } else {
1389
- log.success('Up to date');
1435
+ for (const rt of runtimesToCheck) {
1436
+ activeRuntime = rt;
1437
+ const tp = getTargetPath(options.global);
1438
+ const vi = getVersionInfo(tp, options.global);
1439
+
1440
+ if (!vi.exists) {
1441
+ log.info(`${rt}: not installed`);
1442
+ continue;
1443
+ }
1444
+
1445
+ log.info(`Runtime: ${rt}`);
1446
+ log.info(`Installed: ${vi.installed || 'legacy (no version)'}`);
1447
+ log.info(`Available: ${vi.available}`);
1448
+
1449
+ if (vi.needsUpdate) {
1450
+ log.warn(`${rt}: update available`);
1451
+ anyUpdateAvailable = true;
1452
+ } else {
1453
+ log.success(`${rt}: up to date`);
1454
+ }
1390
1455
  }
1456
+
1457
+ if (anyUpdateAvailable) {
1458
+ log.info('Run: npx 5-phase-workflow --upgrade');
1459
+ }
1460
+
1461
+ activeRuntime = primaryRuntime;
1391
1462
  return;
1392
1463
  }
1393
1464
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "5-phase-workflow",
3
- "version": "1.9.0",
3
+ "version": "1.9.1",
4
4
  "description": "A 5-phase feature development workflow for Claude Code",
5
5
  "bin": {
6
6
  "5-phase-workflow": "bin/install.js"
@@ -117,6 +117,8 @@ Use `multiSelect: false` to get single focus area initially.
117
117
 
118
118
  Based on the user's focus area, explore the codebase if needed:
119
119
 
120
+ **Index shortcut:** Before spawning Explore agents or running Glob/Grep, check if `.5/index/` exists. If it does, read `.5/index/README.md` first for the generation timestamp — if fresh (under 1 day old), read the relevant index files (e.g., modules.md for scope changes, routes.md for API discussions, models.md for data model questions) to quickly gather context and skip broad scanning. If outdated, inform the user they can run `.5/index/rebuild-index.sh` to refresh it. If `.5/index/` does not exist, inform the user they can run `/5:reconfigure` to generate it. In both missing/outdated cases, fall back to Glob/Grep or Explore agents for exploration.
121
+
120
122
  **For technical constraint discussions:**
121
123
  - Search for similar implementations
122
124
  - Check existing patterns
@@ -207,6 +207,9 @@ Task tool call:
207
207
  {If Depends On is not "—": "This component depends on {dep-name} ({dep-file}). Read that file first to understand the exports/types you need to use."}
208
208
  {If Depends On is "—": omit this section entirely}
209
209
 
210
+ ## Codebase Context (optional)
211
+ If you need to understand how this component relates to other modules (imports, service boundaries, data flow), check `.5/index/` for quick reference — especially modules.md and libraries.md. Only if these files exist.
212
+
210
213
  ## Verify
211
214
  {Verify command(s) from plan table — executor runs these after implementation}
212
215
 
@@ -129,12 +129,13 @@ Analyze the codebase for a feature specification session.
129
129
  **Feature Description:** {paste the user's feature description}
130
130
 
131
131
  **Your Task:**
132
- 1. Explore project structure to identify modules/components
133
- 2. Find existing implementations similar to this feature
134
- 3. Identify coding patterns and conventions
135
- 4. Find reusable components or patterns
136
- 5. Identify affected files/modules
137
- 6. Run `git branch --show-current` to get the current branch name
132
+ 1. Check if `.5/index/` exists. If it does, read `.5/index/README.md` first — it includes a generation timestamp. If the index is fresh (under 1 day old), read the relevant index files (e.g., modules.md, routes.md, models.md) as your structural overview and skip broad Glob scans for information already covered. If the index is outdated (over 1 day old), note in your report that the user should run `.5/index/rebuild-index.sh` to refresh it, then use it anyway (stale is better than nothing). If `.5/index/` does not exist at all, note in your report that the user can run `/5:reconfigure` to generate it, then proceed with Glob/Grep exploration as below.
133
+ 2. Explore project structure to identify modules/components
134
+ 3. Find existing implementations similar to this feature
135
+ 4. Identify coding patterns and conventions
136
+ 5. Find reusable components or patterns
137
+ 6. Identify affected files/modules
138
+ 7. Run `git branch --show-current` to get the current branch name
138
139
 
139
140
  **Report Format:**
140
141
  - Current git branch name
@@ -160,13 +160,14 @@ Quick codebase scan for implementation planning.
160
160
  Focus scan on {projectType}-relevant directories and patterns.
161
161
 
162
162
  **Your Task:**
163
- 1. Find source directories and understand project structure
164
- 2. Identify where similar components live (models, services, controllers, tests)
165
- 3. Note naming conventions from existing files
166
- 4. Find example files that can serve as patterns for new components
167
- 5. Identify the project's test framework, test file conventions, and test directory structure (e.g., __tests__/, tests/, *.test.ts, *.spec.ts, test_*.py)
168
- 6. Detect e2e test framework and config (Cypress, Playwright, Selenium, Supertest, etc.) look for config files like playwright.config.ts, cypress.config.js, e2e/ directories
169
- 7. Detect integration test patterns (test containers, in-memory DBs, API test helpers, fixtures) — look for setup files, docker-compose.test.yml, test utilities
163
+ 1. Check if `.5/index/` exists. If it does, read `.5/index/README.md` first — it includes a generation timestamp. If the index is fresh (under 1 day old), read the relevant index files (modules.md, models.md, libraries.md, etc.) for project structure and component locations, then focus Glob/Grep on naming conventions, pattern files, and test framework details not covered by the index. If the index is outdated (over 1 day old), note in your report that the user should run `.5/index/rebuild-index.sh` to refresh it, then use it anyway. If `.5/index/` does not exist at all, note in your report that the user can run `/5:reconfigure` to generate it, then scan from scratch as below.
164
+ 2. Find source directories and understand project structure
165
+ 3. Identify where similar components live (models, services, controllers, tests)
166
+ 4. Note naming conventions from existing files
167
+ 5. Find example files that can serve as patterns for new components
168
+ 6. Identify the project's test framework, test file conventions, and test directory structure (e.g., __tests__/, tests/, *.test.ts, *.spec.ts, test_*.py)
169
+ 7. Detect e2e test framework and config (Cypress, Playwright, Selenium, Supertest, etc.) — look for config files like playwright.config.ts, cypress.config.js, e2e/ directories
170
+ 8. Detect integration test patterns (test containers, in-memory DBs, API test helpers, fixtures) — look for setup files, docker-compose.test.yml, test utilities
170
171
 
171
172
  **Report Format:**
172
173
  - Project structure (key directories)
@@ -91,7 +91,7 @@ Check if `.5/features/${feature_name}/state.json` already exists:
91
91
 
92
92
  ### Step 4: Analyze and Scope Check
93
93
 
94
- 1. **Identify affected files** using Glob and Grep
94
+ 1. **Identify affected files:** If `.5/index/` exists, read `.5/index/README.md` first for the generation timestamp — if fresh (under 1 day old), read the relevant index files (modules.md, routes.md, models.md) to quickly locate affected areas, then confirm with targeted Glob/Grep. If the index is outdated, note that the user can run `.5/index/rebuild-index.sh` to refresh it. If no index exists, note that the user can run `/5:reconfigure` to generate it. In both cases, fall back to Glob and Grep directly.
95
95
  2. **Determine skills needed** based on task type
96
96
  3. **List components** (max 5 for quick mode)
97
97
 
@@ -261,6 +261,7 @@ Task tool call:
261
261
  For each component:
262
262
 
263
263
  **If creating a new file:**
264
+ 0. If `.5/index/` exists, check modules.md or libraries.md to find where similar components live — this narrows your Glob search.
264
265
  1. Find a similar file using Glob (e.g., *Service.ts for services)
265
266
  2. Read it to understand the pattern (imports, structure, exports)
266
267
  3. Create the new file following that pattern