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 +133 -62
- package/package.json +1 -1
- package/src/commands/5/discuss-feature.md +2 -0
- package/src/commands/5/implement-feature.md +3 -0
- package/src/commands/5/plan-feature.md +7 -6
- package/src/commands/5/plan-implementation.md +8 -7
- package/src/commands/5/quick-implement.md +2 -1
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
|
-
|
|
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
|
-
//
|
|
681
|
-
|
|
682
|
-
|
|
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
|
-
|
|
686
|
-
|
|
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
|
|
690
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1377
|
-
|
|
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
|
-
|
|
1382
|
-
|
|
1383
|
-
log.info(`Available: ${versionInfo.available}`);
|
|
1432
|
+
const primaryRuntime = activeRuntime;
|
|
1433
|
+
let anyUpdateAvailable = false;
|
|
1384
1434
|
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
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
|
@@ -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.
|
|
133
|
-
2.
|
|
134
|
-
3.
|
|
135
|
-
4.
|
|
136
|
-
5.
|
|
137
|
-
6.
|
|
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.
|
|
164
|
-
2.
|
|
165
|
-
3.
|
|
166
|
-
4.
|
|
167
|
-
5.
|
|
168
|
-
6.
|
|
169
|
-
7. Detect
|
|
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
|
|
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
|