5-phase-workflow 1.7.0 → 1.7.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.
- package/bin/install.js +27 -16
- package/package.json +1 -1
- package/src/agents/feature-planner.md +1 -0
- package/src/agents/implementation-planner.md +1 -0
- package/src/hooks/check-updates.js +10 -10
- package/src/hooks/plan-guard.js +16 -2
- package/src/hooks/statusline.js +10 -3
- package/src/settings.json +1 -1
package/bin/install.js
CHANGED
|
@@ -41,7 +41,7 @@ function getInstalledVersion(isGlobal) {
|
|
|
41
41
|
|
|
42
42
|
try {
|
|
43
43
|
const data = JSON.parse(fs.readFileSync(versionFile, 'utf8'));
|
|
44
|
-
return data.
|
|
44
|
+
return data.packageVersion;
|
|
45
45
|
} catch (e) {
|
|
46
46
|
return null; // Corrupted file, treat as missing
|
|
47
47
|
}
|
|
@@ -386,6 +386,20 @@ function selectiveUpdate(targetPath, sourcePath) {
|
|
|
386
386
|
log.success('Updated templates/ (workflow files only)');
|
|
387
387
|
}
|
|
388
388
|
|
|
389
|
+
// Ensure .5/.gitignore exists and contains .update-cache.json
|
|
390
|
+
function ensureDotFiveGitignore(dataDir) {
|
|
391
|
+
const gitignorePath = path.join(dataDir, '.gitignore');
|
|
392
|
+
const entry = '.update-cache.json';
|
|
393
|
+
if (fs.existsSync(gitignorePath)) {
|
|
394
|
+
const content = fs.readFileSync(gitignorePath, 'utf8');
|
|
395
|
+
if (!content.includes(entry)) {
|
|
396
|
+
fs.appendFileSync(gitignorePath, '\n' + entry + '\n');
|
|
397
|
+
}
|
|
398
|
+
} else {
|
|
399
|
+
fs.writeFileSync(gitignorePath, entry + '\n');
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
389
403
|
// Initialize version.json after successful install
|
|
390
404
|
function initializeVersionJson(isGlobal) {
|
|
391
405
|
const dataDir = getDataPath(isGlobal);
|
|
@@ -400,13 +414,13 @@ function initializeVersionJson(isGlobal) {
|
|
|
400
414
|
|
|
401
415
|
const versionData = {
|
|
402
416
|
packageVersion: version,
|
|
403
|
-
installedVersion: version,
|
|
404
417
|
installedAt: now,
|
|
405
418
|
lastUpdated: now,
|
|
406
419
|
installationType: isGlobal ? 'global' : 'local'
|
|
407
420
|
};
|
|
408
421
|
|
|
409
422
|
fs.writeFileSync(versionFile, JSON.stringify(versionData, null, 2));
|
|
423
|
+
ensureDotFiveGitignore(dataDir);
|
|
410
424
|
log.success('Initialized version tracking');
|
|
411
425
|
}
|
|
412
426
|
|
|
@@ -554,26 +568,23 @@ function performUpdate(targetPath, sourcePath, isGlobal, versionInfo) {
|
|
|
554
568
|
// Update version.json
|
|
555
569
|
const dataDir = getDataPath(isGlobal);
|
|
556
570
|
const versionFile = path.join(dataDir, 'version.json');
|
|
571
|
+
const now = new Date().toISOString();
|
|
557
572
|
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
versionData.packageVersion = versionInfo.available;
|
|
570
|
-
versionData.installedVersion = versionInfo.available;
|
|
571
|
-
versionData.lastUpdated = new Date().toISOString();
|
|
573
|
+
const existing = fs.existsSync(versionFile)
|
|
574
|
+
? JSON.parse(fs.readFileSync(versionFile, 'utf8'))
|
|
575
|
+
: {};
|
|
576
|
+
const versionData = {
|
|
577
|
+
packageVersion: versionInfo.available,
|
|
578
|
+
installedAt: existing.installedAt || now,
|
|
579
|
+
lastUpdated: now,
|
|
580
|
+
installationType: existing.installationType || (isGlobal ? 'global' : 'local')
|
|
581
|
+
};
|
|
572
582
|
|
|
573
583
|
if (!fs.existsSync(dataDir)) {
|
|
574
584
|
fs.mkdirSync(dataDir, { recursive: true });
|
|
575
585
|
}
|
|
576
586
|
fs.writeFileSync(versionFile, JSON.stringify(versionData, null, 2));
|
|
587
|
+
ensureDotFiveGitignore(dataDir);
|
|
577
588
|
|
|
578
589
|
// Create features directory if it doesn't exist
|
|
579
590
|
const featuresDir = path.join(dataDir, 'features');
|
package/package.json
CHANGED
|
@@ -18,6 +18,7 @@ HARD CONSTRAINTS — violations waste tokens and get blocked by plan-guard:
|
|
|
18
18
|
- NEVER describe HOW something will be implemented (file contents, signatures, class structures)
|
|
19
19
|
- NEVER spawn Task agents with subagent_type other than Explore
|
|
20
20
|
- NEVER write to any file except .5/features/{name}/feature.md and .5/.planning-active
|
|
21
|
+
- NEVER call EnterPlanMode — the workflow has its own planning process
|
|
21
22
|
- The feature spec describes WHAT and WHY, never HOW
|
|
22
23
|
- If you feel the urge to implement, STOP and ask a clarifying question instead
|
|
23
24
|
- Your output is a SPECIFICATION, not a design document. No code. No file layouts. No API shapes.
|
|
@@ -16,6 +16,7 @@ After creating the plan, you are DONE.
|
|
|
16
16
|
HARD CONSTRAINTS — violations waste tokens and get blocked by plan-guard:
|
|
17
17
|
- NEVER write code, pseudo-code, or implementation snippets
|
|
18
18
|
- NEVER create source files — you create ONE file: plan.md
|
|
19
|
+
- NEVER call EnterPlanMode — the workflow has its own planning process
|
|
19
20
|
- NEVER spawn Task agents with subagent_type other than Explore
|
|
20
21
|
- The plan describes WHAT to build and WHERE. Agents figure out HOW by reading existing code.
|
|
21
22
|
- Each component in the table gets: name, action, file path, one-sentence description, complexity
|
|
@@ -40,7 +40,7 @@ async function checkForUpdates(workspaceDir) {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
// Compare versions
|
|
43
|
-
const installed = versionData.
|
|
43
|
+
const installed = versionData.packageVersion;
|
|
44
44
|
const latestVersion = await getLatestVersion();
|
|
45
45
|
|
|
46
46
|
let newLatest = null;
|
|
@@ -48,16 +48,16 @@ async function checkForUpdates(workspaceDir) {
|
|
|
48
48
|
newLatest = latestVersion;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
//
|
|
52
|
-
const
|
|
51
|
+
// Read/write latestAvailableVersion from .update-cache.json (gitignored)
|
|
52
|
+
const cacheFile = path.join(path.dirname(versionFile), '.update-cache.json');
|
|
53
|
+
let cacheData = {};
|
|
54
|
+
if (fs.existsSync(cacheFile)) {
|
|
55
|
+
try { cacheData = JSON.parse(fs.readFileSync(cacheFile, 'utf8')); } catch(e) {}
|
|
56
|
+
}
|
|
57
|
+
const oldLatest = cacheData.latestAvailableVersion || null;
|
|
53
58
|
if (newLatest !== oldLatest) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
// Clean up legacy throttling fields
|
|
57
|
-
delete versionData.updateCheckLastRun;
|
|
58
|
-
delete versionData.updateCheckFrequency;
|
|
59
|
-
|
|
60
|
-
fs.writeFileSync(versionFile, JSON.stringify(versionData, null, 2));
|
|
59
|
+
cacheData.latestAvailableVersion = newLatest;
|
|
60
|
+
fs.writeFileSync(cacheFile, JSON.stringify(cacheData, null, 2));
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
process.exit(0);
|
package/src/hooks/plan-guard.js
CHANGED
|
@@ -20,8 +20,8 @@ process.stdin.on('end', () => {
|
|
|
20
20
|
const data = JSON.parse(input);
|
|
21
21
|
const toolName = data.tool_name || '';
|
|
22
22
|
|
|
23
|
-
// Short-circuit: only check Task, Write, and
|
|
24
|
-
if (toolName !== 'Task' && toolName !== 'Write' && toolName !== 'Edit') {
|
|
23
|
+
// Short-circuit: only check Task, Write, Edit, and EnterPlanMode tools
|
|
24
|
+
if (toolName !== 'Task' && toolName !== 'Write' && toolName !== 'Edit' && toolName !== 'EnterPlanMode') {
|
|
25
25
|
process.exit(0);
|
|
26
26
|
}
|
|
27
27
|
|
|
@@ -41,6 +41,20 @@ process.stdin.on('end', () => {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
// Planning mode enforcement
|
|
44
|
+
if (toolName === 'EnterPlanMode') {
|
|
45
|
+
const blockCount = incrementBlockCount(workspaceDir);
|
|
46
|
+
const escalation = blockCount >= 3
|
|
47
|
+
? ` WARNING: Block #${blockCount}. Repeated violations. Complete your planning artifact and STOP.`
|
|
48
|
+
: '';
|
|
49
|
+
process.stderr.write(
|
|
50
|
+
`BLOCKED: EnterPlanMode is not allowed during workflow planning phases. ` +
|
|
51
|
+
`The 5-phase workflow has its own planning process. ` +
|
|
52
|
+
`REDIRECT: Continue with your current planning task. ` +
|
|
53
|
+
`Write your output to .5/features/{name}/ and output the completion message when done.${escalation}`
|
|
54
|
+
);
|
|
55
|
+
process.exit(2);
|
|
56
|
+
}
|
|
57
|
+
|
|
44
58
|
if (toolName === 'Task') {
|
|
45
59
|
const agentType = toolInput.subagent_type || '';
|
|
46
60
|
if (agentType && agentType !== 'Explore') {
|
package/src/hooks/statusline.js
CHANGED
|
@@ -50,9 +50,16 @@ process.stdin.on('end', () => {
|
|
|
50
50
|
const versionFile = path.join(dir, '.5', 'version.json');
|
|
51
51
|
const versionData = JSON.parse(fs.readFileSync(versionFile, 'utf8'));
|
|
52
52
|
|
|
53
|
-
// Update check
|
|
54
|
-
const
|
|
55
|
-
|
|
53
|
+
// Update check — read latestAvailableVersion from cache file (gitignored)
|
|
54
|
+
const cacheFile = path.join(dir, '.5', '.update-cache.json');
|
|
55
|
+
let latest = null;
|
|
56
|
+
if (fs.existsSync(cacheFile)) {
|
|
57
|
+
try {
|
|
58
|
+
const cache = JSON.parse(fs.readFileSync(cacheFile, 'utf8'));
|
|
59
|
+
latest = cache.latestAvailableVersion || null;
|
|
60
|
+
} catch(e) {}
|
|
61
|
+
}
|
|
62
|
+
const installed = versionData.packageVersion;
|
|
56
63
|
if (latest && installed && compareVersions(installed, latest) < 0) {
|
|
57
64
|
updateIndicator = ` | \x1b[33m↑${latest} → /5:update\x1b[0m`;
|
|
58
65
|
}
|