@howlil/ez-agents 3.4.1 → 3.5.0
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/LICENSE +21 -21
- package/README.md +84 -20
- package/agents/ez-observer-agent.md +260 -0
- package/agents/ez-release-agent.md +333 -0
- package/agents/ez-requirements-agent.md +377 -0
- package/agents/ez-scrum-master-agent.md +242 -0
- package/agents/ez-tech-lead-agent.md +267 -0
- package/bin/install.js +3221 -3230
- package/commands/ez/arch-review.md +102 -0
- package/commands/ez/execute-phase.md +11 -0
- package/commands/ez/export-session.md +79 -0
- package/commands/ez/gather-requirements.md +117 -0
- package/commands/ez/git-workflow.md +72 -0
- package/commands/ez/hotfix.md +120 -0
- package/commands/ez/import-session.md +82 -0
- package/commands/ez/join-discord.md +18 -18
- package/commands/ez/list-sessions.md +96 -0
- package/commands/ez/package-manager.md +316 -0
- package/commands/ez/plan-phase.md +9 -1
- package/commands/ez/preflight.md +79 -0
- package/commands/ez/progress.md +13 -1
- package/commands/ez/release.md +153 -0
- package/commands/ez/resume.md +107 -0
- package/commands/ez/standup.md +85 -0
- package/ez-agents/bin/ez-tools.cjs +1095 -716
- package/ez-agents/bin/lib/assistant-adapter.cjs +264 -264
- package/ez-agents/bin/lib/audit-exec.cjs +7 -2
- package/ez-agents/bin/lib/bdd-validator.cjs +622 -0
- package/ez-agents/bin/lib/circuit-breaker.cjs +118 -118
- package/ez-agents/bin/lib/config.cjs +190 -190
- package/ez-agents/bin/lib/content-scanner.cjs +238 -0
- package/ez-agents/bin/lib/context-cache.cjs +154 -0
- package/ez-agents/bin/lib/context-errors.cjs +71 -0
- package/ez-agents/bin/lib/context-manager.cjs +220 -0
- package/ez-agents/bin/lib/discussion-synthesizer.cjs +458 -0
- package/ez-agents/bin/lib/file-access.cjs +207 -0
- package/ez-agents/bin/lib/file-lock.cjs +236 -236
- package/ez-agents/bin/lib/frontmatter.cjs +299 -299
- package/ez-agents/bin/lib/fs-utils.cjs +153 -153
- package/ez-agents/bin/lib/git-errors.cjs +83 -0
- package/ez-agents/bin/lib/git-utils.cjs +118 -0
- package/ez-agents/bin/lib/git-workflow-engine.cjs +1157 -0
- package/ez-agents/bin/lib/index.cjs +157 -113
- package/ez-agents/bin/lib/init.cjs +757 -757
- package/ez-agents/bin/lib/lockfile-validator.cjs +227 -0
- package/ez-agents/bin/lib/logger.cjs +124 -124
- package/ez-agents/bin/lib/memory-compression.cjs +256 -0
- package/ez-agents/bin/lib/metrics-tracker.cjs +406 -0
- package/ez-agents/bin/lib/milestone.cjs +241 -241
- package/ez-agents/bin/lib/model-provider.cjs +241 -241
- package/ez-agents/bin/lib/package-manager-detector.cjs +203 -0
- package/ez-agents/bin/lib/package-manager-executor.cjs +385 -0
- package/ez-agents/bin/lib/package-manager-service.cjs +216 -0
- package/ez-agents/bin/lib/phase.cjs +925 -925
- package/ez-agents/bin/lib/planning-write.cjs +107 -107
- package/ez-agents/bin/lib/release-validator.cjs +614 -0
- package/ez-agents/bin/lib/retry.cjs +119 -119
- package/ez-agents/bin/lib/roadmap.cjs +306 -306
- package/ez-agents/bin/lib/safe-exec.cjs +128 -128
- package/ez-agents/bin/lib/safe-path.cjs +130 -130
- package/ez-agents/bin/lib/session-chain.cjs +304 -0
- package/ez-agents/bin/lib/session-errors.cjs +81 -0
- package/ez-agents/bin/lib/session-export.cjs +251 -0
- package/ez-agents/bin/lib/session-import.cjs +262 -0
- package/ez-agents/bin/lib/session-manager.cjs +280 -0
- package/ez-agents/bin/lib/state.cjs +736 -736
- package/ez-agents/bin/lib/temp-file.cjs +239 -239
- package/ez-agents/bin/lib/template.cjs +223 -223
- package/ez-agents/bin/lib/test-file-lock.cjs +112 -112
- package/ez-agents/bin/lib/test-graceful.cjs +93 -93
- package/ez-agents/bin/lib/test-logger.cjs +60 -60
- package/ez-agents/bin/lib/test-safe-exec.cjs +38 -38
- package/ez-agents/bin/lib/test-safe-path.cjs +33 -33
- package/ez-agents/bin/lib/test-temp-file.cjs +125 -125
- package/ez-agents/bin/lib/tier-manager.cjs +428 -0
- package/ez-agents/bin/lib/timeout-exec.cjs +63 -63
- package/ez-agents/bin/lib/url-fetch.cjs +170 -0
- package/ez-agents/bin/lib/verify.cjs +15 -1
- package/ez-agents/references/checkpoints.md +776 -776
- package/ez-agents/references/continuation-format.md +249 -249
- package/ez-agents/references/metrics-schema.md +118 -0
- package/ez-agents/references/planning-config.md +140 -0
- package/ez-agents/references/questioning.md +162 -162
- package/ez-agents/references/tdd.md +263 -263
- package/ez-agents/references/tier-strategy.md +103 -0
- package/ez-agents/templates/bdd-feature.md +173 -0
- package/ez-agents/templates/codebase/concerns.md +310 -310
- package/ez-agents/templates/codebase/conventions.md +307 -307
- package/ez-agents/templates/codebase/integrations.md +280 -280
- package/ez-agents/templates/codebase/stack.md +186 -186
- package/ez-agents/templates/codebase/testing.md +480 -480
- package/ez-agents/templates/config.json +37 -37
- package/ez-agents/templates/continue-here.md +78 -78
- package/ez-agents/templates/discussion.md +68 -0
- package/ez-agents/templates/incident-runbook.md +205 -0
- package/ez-agents/templates/milestone-archive.md +123 -123
- package/ez-agents/templates/milestone.md +115 -115
- package/ez-agents/templates/release-checklist.md +133 -0
- package/ez-agents/templates/requirements.md +231 -231
- package/ez-agents/templates/research-project/ARCHITECTURE.md +204 -204
- package/ez-agents/templates/research-project/FEATURES.md +147 -147
- package/ez-agents/templates/research-project/PITFALLS.md +200 -200
- package/ez-agents/templates/research-project/STACK.md +120 -120
- package/ez-agents/templates/research-project/SUMMARY.md +170 -170
- package/ez-agents/templates/retrospective.md +54 -54
- package/ez-agents/templates/roadmap.md +202 -202
- package/ez-agents/templates/rollback-plan.md +201 -0
- package/ez-agents/templates/summary-minimal.md +41 -41
- package/ez-agents/templates/summary-standard.md +48 -48
- package/ez-agents/templates/summary.md +248 -248
- package/ez-agents/templates/user-setup.md +311 -311
- package/ez-agents/templates/verification-report.md +322 -322
- package/ez-agents/workflows/add-phase.md +112 -112
- package/ez-agents/workflows/add-tests.md +351 -351
- package/ez-agents/workflows/add-todo.md +158 -158
- package/ez-agents/workflows/arch-review.md +54 -0
- package/ez-agents/workflows/audit-milestone.md +332 -332
- package/ez-agents/workflows/autonomous.md +131 -30
- package/ez-agents/workflows/check-todos.md +177 -177
- package/ez-agents/workflows/cleanup.md +152 -152
- package/ez-agents/workflows/complete-milestone.md +766 -766
- package/ez-agents/workflows/diagnose-issues.md +219 -219
- package/ez-agents/workflows/discovery-phase.md +289 -289
- package/ez-agents/workflows/discuss-phase.md +762 -762
- package/ez-agents/workflows/execute-phase.md +513 -468
- package/ez-agents/workflows/execute-plan.md +483 -483
- package/ez-agents/workflows/export-session.md +255 -0
- package/ez-agents/workflows/gather-requirements.md +206 -0
- package/ez-agents/workflows/health.md +159 -159
- package/ez-agents/workflows/help.md +584 -492
- package/ez-agents/workflows/hotfix.md +291 -0
- package/ez-agents/workflows/import-session.md +303 -0
- package/ez-agents/workflows/insert-phase.md +130 -130
- package/ez-agents/workflows/list-phase-assumptions.md +178 -178
- package/ez-agents/workflows/map-codebase.md +316 -316
- package/ez-agents/workflows/new-milestone.md +339 -10
- package/ez-agents/workflows/new-project.md +293 -299
- package/ez-agents/workflows/node-repair.md +92 -92
- package/ez-agents/workflows/pause-work.md +122 -122
- package/ez-agents/workflows/plan-milestone-gaps.md +274 -274
- package/ez-agents/workflows/plan-phase.md +673 -651
- package/ez-agents/workflows/progress.md +372 -382
- package/ez-agents/workflows/quick.md +610 -610
- package/ez-agents/workflows/release.md +253 -0
- package/ez-agents/workflows/remove-phase.md +155 -155
- package/ez-agents/workflows/research-phase.md +74 -74
- package/ez-agents/workflows/resume-project.md +307 -307
- package/ez-agents/workflows/resume-session.md +215 -0
- package/ez-agents/workflows/set-profile.md +81 -81
- package/ez-agents/workflows/settings.md +242 -242
- package/ez-agents/workflows/standup.md +64 -0
- package/ez-agents/workflows/stats.md +57 -57
- package/ez-agents/workflows/transition.md +544 -544
- package/ez-agents/workflows/ui-phase.md +290 -290
- package/ez-agents/workflows/ui-review.md +157 -157
- package/ez-agents/workflows/update.md +320 -320
- package/ez-agents/workflows/validate-phase.md +167 -167
- package/ez-agents/workflows/verify-phase.md +243 -243
- package/ez-agents/workflows/verify-work.md +584 -584
- package/package.json +10 -4
- package/scripts/build-hooks.js +43 -43
- package/scripts/run-tests.cjs +29 -29
|
@@ -1,125 +1,125 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Test script for EZ Temp File Handler
|
|
5
|
-
* Run: node ez-agents/bin/lib/test-temp-file.cjs
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
const os = require('os');
|
|
9
|
-
const path = require('path');
|
|
10
|
-
const fs = require('fs');
|
|
11
|
-
|
|
12
|
-
const {
|
|
13
|
-
createTempDir,
|
|
14
|
-
createTempFile,
|
|
15
|
-
writeToTemp,
|
|
16
|
-
readFromTemp,
|
|
17
|
-
cleanupTemp,
|
|
18
|
-
cleanupAll,
|
|
19
|
-
getTrackedTemps,
|
|
20
|
-
isPathSafe
|
|
21
|
-
} = require('./temp-file.cjs');
|
|
22
|
-
|
|
23
|
-
async function testCreateTempDir() {
|
|
24
|
-
console.log('\n=== Test 1: Create Temp Directory ===');
|
|
25
|
-
|
|
26
|
-
const tempDir = await createTempDir('ez-test-');
|
|
27
|
-
console.log('Created temp dir:', tempDir);
|
|
28
|
-
|
|
29
|
-
const exists = require('fs').existsSync(tempDir);
|
|
30
|
-
console.log('Directory exists:', exists);
|
|
31
|
-
|
|
32
|
-
if (!exists) throw new Error('Temp dir was not created');
|
|
33
|
-
console.log('✓ Temp directory created successfully');
|
|
34
|
-
|
|
35
|
-
return tempDir;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
async function testCreateTempFile(tempDir) {
|
|
39
|
-
console.log('\n=== Test 2: Create Temp File ===');
|
|
40
|
-
|
|
41
|
-
const tempFile = await createTempFile('test-', tempDir, 'Initial content');
|
|
42
|
-
console.log('Created temp file:', tempFile);
|
|
43
|
-
|
|
44
|
-
const content = await readFromTemp(tempFile, { validateBase: tempDir });
|
|
45
|
-
console.log('File content:', content);
|
|
46
|
-
|
|
47
|
-
if (content !== 'Initial content') {
|
|
48
|
-
throw new Error('File content mismatch');
|
|
49
|
-
}
|
|
50
|
-
console.log('✓ Temp file created and read successfully');
|
|
51
|
-
|
|
52
|
-
return tempFile;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
async function testWriteToTemp(tempFile, tempDir) {
|
|
56
|
-
console.log('\n=== Test 3: Write to Temp File ===');
|
|
57
|
-
|
|
58
|
-
await writeToTemp(tempFile, 'Updated content', { validateBase: tempDir });
|
|
59
|
-
const content = await readFromTemp(tempFile, { validateBase: tempDir });
|
|
60
|
-
|
|
61
|
-
console.log('Updated content:', content);
|
|
62
|
-
|
|
63
|
-
if (content !== 'Updated content') {
|
|
64
|
-
throw new Error('Write failed');
|
|
65
|
-
}
|
|
66
|
-
console.log('✓ Temp file write successful');
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
async function testPathSafety() {
|
|
70
|
-
console.log('\n=== Test 4: Path Safety Validation ===');
|
|
71
|
-
|
|
72
|
-
const baseDir = os.tmpdir();
|
|
73
|
-
|
|
74
|
-
// Safe paths
|
|
75
|
-
console.log('Safe path (same dir):', isPathSafe(baseDir, path.join(baseDir, 'file.txt')));
|
|
76
|
-
console.log('Safe path (subdir):', isPathSafe(baseDir, path.join(baseDir, 'sub', 'file.txt')));
|
|
77
|
-
|
|
78
|
-
// Unsafe paths (should be false)
|
|
79
|
-
const unsafePath = path.resolve(baseDir, '..', '..', 'etc', 'passwd');
|
|
80
|
-
console.log('Unsafe path (/etc/passwd):', isPathSafe(baseDir, unsafePath));
|
|
81
|
-
|
|
82
|
-
console.log('✓ Path safety validation working');
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
async function testCleanup(tempFile, tempDir) {
|
|
86
|
-
console.log('\n=== Test 5: Cleanup ===');
|
|
87
|
-
|
|
88
|
-
console.log('Tracked temps before cleanup:', getTrackedTemps().length);
|
|
89
|
-
|
|
90
|
-
await cleanupTemp(tempFile);
|
|
91
|
-
console.log('Cleaned up temp file');
|
|
92
|
-
|
|
93
|
-
const fileExists = require('fs').existsSync(tempFile);
|
|
94
|
-
console.log('Temp file exists after cleanup:', fileExists);
|
|
95
|
-
|
|
96
|
-
await cleanupAll();
|
|
97
|
-
console.log('Cleaned up all temps');
|
|
98
|
-
|
|
99
|
-
const dirExists = require('fs').existsSync(tempDir);
|
|
100
|
-
console.log('Temp dir exists after cleanup:', dirExists);
|
|
101
|
-
|
|
102
|
-
console.log('✓ Cleanup successful');
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
async function runTests() {
|
|
106
|
-
const os = require('os');
|
|
107
|
-
const path = require('path');
|
|
108
|
-
|
|
109
|
-
try {
|
|
110
|
-
const tempDir = await testCreateTempDir();
|
|
111
|
-
const tempFile = await testCreateTempFile(tempDir);
|
|
112
|
-
await testWriteToTemp(tempFile, tempDir);
|
|
113
|
-
await testPathSafety();
|
|
114
|
-
await testCleanup(tempFile, tempDir);
|
|
115
|
-
|
|
116
|
-
console.log('\n' + '='.repeat(50));
|
|
117
|
-
console.log('Temp File Handler test COMPLETE ✓');
|
|
118
|
-
console.log('='.repeat(50));
|
|
119
|
-
} catch (err) {
|
|
120
|
-
console.error('Test failed:', err);
|
|
121
|
-
process.exit(1);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
runTests();
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Test script for EZ Temp File Handler
|
|
5
|
+
* Run: node ez-agents/bin/lib/test-temp-file.cjs
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const os = require('os');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
|
|
12
|
+
const {
|
|
13
|
+
createTempDir,
|
|
14
|
+
createTempFile,
|
|
15
|
+
writeToTemp,
|
|
16
|
+
readFromTemp,
|
|
17
|
+
cleanupTemp,
|
|
18
|
+
cleanupAll,
|
|
19
|
+
getTrackedTemps,
|
|
20
|
+
isPathSafe
|
|
21
|
+
} = require('./temp-file.cjs');
|
|
22
|
+
|
|
23
|
+
async function testCreateTempDir() {
|
|
24
|
+
console.log('\n=== Test 1: Create Temp Directory ===');
|
|
25
|
+
|
|
26
|
+
const tempDir = await createTempDir('ez-test-');
|
|
27
|
+
console.log('Created temp dir:', tempDir);
|
|
28
|
+
|
|
29
|
+
const exists = require('fs').existsSync(tempDir);
|
|
30
|
+
console.log('Directory exists:', exists);
|
|
31
|
+
|
|
32
|
+
if (!exists) throw new Error('Temp dir was not created');
|
|
33
|
+
console.log('✓ Temp directory created successfully');
|
|
34
|
+
|
|
35
|
+
return tempDir;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function testCreateTempFile(tempDir) {
|
|
39
|
+
console.log('\n=== Test 2: Create Temp File ===');
|
|
40
|
+
|
|
41
|
+
const tempFile = await createTempFile('test-', tempDir, 'Initial content');
|
|
42
|
+
console.log('Created temp file:', tempFile);
|
|
43
|
+
|
|
44
|
+
const content = await readFromTemp(tempFile, { validateBase: tempDir });
|
|
45
|
+
console.log('File content:', content);
|
|
46
|
+
|
|
47
|
+
if (content !== 'Initial content') {
|
|
48
|
+
throw new Error('File content mismatch');
|
|
49
|
+
}
|
|
50
|
+
console.log('✓ Temp file created and read successfully');
|
|
51
|
+
|
|
52
|
+
return tempFile;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function testWriteToTemp(tempFile, tempDir) {
|
|
56
|
+
console.log('\n=== Test 3: Write to Temp File ===');
|
|
57
|
+
|
|
58
|
+
await writeToTemp(tempFile, 'Updated content', { validateBase: tempDir });
|
|
59
|
+
const content = await readFromTemp(tempFile, { validateBase: tempDir });
|
|
60
|
+
|
|
61
|
+
console.log('Updated content:', content);
|
|
62
|
+
|
|
63
|
+
if (content !== 'Updated content') {
|
|
64
|
+
throw new Error('Write failed');
|
|
65
|
+
}
|
|
66
|
+
console.log('✓ Temp file write successful');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function testPathSafety() {
|
|
70
|
+
console.log('\n=== Test 4: Path Safety Validation ===');
|
|
71
|
+
|
|
72
|
+
const baseDir = os.tmpdir();
|
|
73
|
+
|
|
74
|
+
// Safe paths
|
|
75
|
+
console.log('Safe path (same dir):', isPathSafe(baseDir, path.join(baseDir, 'file.txt')));
|
|
76
|
+
console.log('Safe path (subdir):', isPathSafe(baseDir, path.join(baseDir, 'sub', 'file.txt')));
|
|
77
|
+
|
|
78
|
+
// Unsafe paths (should be false)
|
|
79
|
+
const unsafePath = path.resolve(baseDir, '..', '..', 'etc', 'passwd');
|
|
80
|
+
console.log('Unsafe path (/etc/passwd):', isPathSafe(baseDir, unsafePath));
|
|
81
|
+
|
|
82
|
+
console.log('✓ Path safety validation working');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function testCleanup(tempFile, tempDir) {
|
|
86
|
+
console.log('\n=== Test 5: Cleanup ===');
|
|
87
|
+
|
|
88
|
+
console.log('Tracked temps before cleanup:', getTrackedTemps().length);
|
|
89
|
+
|
|
90
|
+
await cleanupTemp(tempFile);
|
|
91
|
+
console.log('Cleaned up temp file');
|
|
92
|
+
|
|
93
|
+
const fileExists = require('fs').existsSync(tempFile);
|
|
94
|
+
console.log('Temp file exists after cleanup:', fileExists);
|
|
95
|
+
|
|
96
|
+
await cleanupAll();
|
|
97
|
+
console.log('Cleaned up all temps');
|
|
98
|
+
|
|
99
|
+
const dirExists = require('fs').existsSync(tempDir);
|
|
100
|
+
console.log('Temp dir exists after cleanup:', dirExists);
|
|
101
|
+
|
|
102
|
+
console.log('✓ Cleanup successful');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function runTests() {
|
|
106
|
+
const os = require('os');
|
|
107
|
+
const path = require('path');
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
const tempDir = await testCreateTempDir();
|
|
111
|
+
const tempFile = await testCreateTempFile(tempDir);
|
|
112
|
+
await testWriteToTemp(tempFile, tempDir);
|
|
113
|
+
await testPathSafety();
|
|
114
|
+
await testCleanup(tempFile, tempDir);
|
|
115
|
+
|
|
116
|
+
console.log('\n' + '='.repeat(50));
|
|
117
|
+
console.log('Temp File Handler test COMPLETE ✓');
|
|
118
|
+
console.log('='.repeat(50));
|
|
119
|
+
} catch (err) {
|
|
120
|
+
console.error('Test failed:', err);
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
runTests();
|
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Tier Manager — Multi-tier release strategy management
|
|
5
|
+
*
|
|
6
|
+
* Manages MVP / Medium / Enterprise tier definitions, validation,
|
|
7
|
+
* and promotion logic for /ez:release workflows.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
|
|
15
|
+
// ─────────────────────────────────────────────
|
|
16
|
+
// Tier Definitions
|
|
17
|
+
// ─────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
const TIER_DEFINITIONS = {
|
|
20
|
+
mvp: {
|
|
21
|
+
name: 'MVP',
|
|
22
|
+
label: 'Minimum Viable Product',
|
|
23
|
+
moscow_scope: ['must'],
|
|
24
|
+
coverage_threshold: 60,
|
|
25
|
+
git_strategy: 'trunk',
|
|
26
|
+
checklist_count: 6,
|
|
27
|
+
rollback_window_minutes: 30,
|
|
28
|
+
description: 'Core @must features only. Ship in hours.'
|
|
29
|
+
},
|
|
30
|
+
medium: {
|
|
31
|
+
name: 'Medium',
|
|
32
|
+
label: 'Production Ready',
|
|
33
|
+
moscow_scope: ['must', 'should'],
|
|
34
|
+
coverage_threshold: 80,
|
|
35
|
+
git_strategy: 'github-flow',
|
|
36
|
+
checklist_count: 18,
|
|
37
|
+
rollback_window_minutes: 15,
|
|
38
|
+
description: 'Must + Should features. Real users, proper testing.'
|
|
39
|
+
},
|
|
40
|
+
enterprise: {
|
|
41
|
+
name: 'Enterprise',
|
|
42
|
+
label: 'Compliance Grade',
|
|
43
|
+
moscow_scope: ['must', 'should', 'could'],
|
|
44
|
+
coverage_threshold: 95,
|
|
45
|
+
git_strategy: 'gitflow',
|
|
46
|
+
checklist_count: 30,
|
|
47
|
+
rollback_window_minutes: 5,
|
|
48
|
+
description: 'All MoSCoW priorities. Regulated industries, enterprise customers.'
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const TIER_ORDER = ['mvp', 'medium', 'enterprise'];
|
|
53
|
+
|
|
54
|
+
// ─────────────────────────────────────────────
|
|
55
|
+
// Tier Accessors
|
|
56
|
+
// ─────────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Get tier definition
|
|
60
|
+
* @param {string} tier - 'mvp' | 'medium' | 'enterprise'
|
|
61
|
+
* @returns {object}
|
|
62
|
+
*/
|
|
63
|
+
function getTier(tier) {
|
|
64
|
+
const def = TIER_DEFINITIONS[tier.toLowerCase()];
|
|
65
|
+
if (!def) {
|
|
66
|
+
throw new Error(`Unknown tier: ${tier}. Must be one of: ${TIER_ORDER.join(', ')}`);
|
|
67
|
+
}
|
|
68
|
+
return { ...def, id: tier.toLowerCase() };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Get all tier definitions
|
|
73
|
+
* @returns {object[]}
|
|
74
|
+
*/
|
|
75
|
+
function getAllTiers() {
|
|
76
|
+
return TIER_ORDER.map(t => ({ id: t, ...TIER_DEFINITIONS[t] }));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Check if a tier is valid
|
|
81
|
+
* @param {string} tier
|
|
82
|
+
* @returns {boolean}
|
|
83
|
+
*/
|
|
84
|
+
function isValidTier(tier) {
|
|
85
|
+
return TIER_ORDER.includes(tier.toLowerCase());
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Get the tier index (0=mvp, 1=medium, 2=enterprise)
|
|
90
|
+
* @param {string} tier
|
|
91
|
+
* @returns {number}
|
|
92
|
+
*/
|
|
93
|
+
function getTierIndex(tier) {
|
|
94
|
+
return TIER_ORDER.indexOf(tier.toLowerCase());
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Check if target tier is a promotion from current tier
|
|
99
|
+
* @param {string} current
|
|
100
|
+
* @param {string} target
|
|
101
|
+
* @returns {boolean}
|
|
102
|
+
*/
|
|
103
|
+
function isPromotion(current, target) {
|
|
104
|
+
return getTierIndex(target) > getTierIndex(current);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Get the tier below (for prerequisite checking)
|
|
109
|
+
* @param {string} tier
|
|
110
|
+
* @returns {string|null}
|
|
111
|
+
*/
|
|
112
|
+
function getPreviousTier(tier) {
|
|
113
|
+
const idx = getTierIndex(tier);
|
|
114
|
+
return idx > 0 ? TIER_ORDER[idx - 1] : null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ─────────────────────────────────────────────
|
|
118
|
+
// Git Strategy
|
|
119
|
+
// ─────────────────────────────────────────────
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Get git strategy for a tier
|
|
123
|
+
* @param {string} tier
|
|
124
|
+
* @returns {{ strategy: string, releaseBranchPrefix: string, targetBranch: string, syncBranch: string|null }}
|
|
125
|
+
*/
|
|
126
|
+
function getGitStrategy(tier) {
|
|
127
|
+
const def = getTier(tier);
|
|
128
|
+
|
|
129
|
+
const strategies = {
|
|
130
|
+
trunk: {
|
|
131
|
+
strategy: 'trunk',
|
|
132
|
+
releaseBranchPrefix: null,
|
|
133
|
+
targetBranch: 'main',
|
|
134
|
+
syncBranch: null,
|
|
135
|
+
description: 'Tag directly on main. No release branch.'
|
|
136
|
+
},
|
|
137
|
+
'github-flow': {
|
|
138
|
+
strategy: 'github-flow',
|
|
139
|
+
releaseBranchPrefix: 'release',
|
|
140
|
+
targetBranch: 'main',
|
|
141
|
+
syncBranch: null,
|
|
142
|
+
description: 'release/vX.Y.Z branch → PR → main'
|
|
143
|
+
},
|
|
144
|
+
gitflow: {
|
|
145
|
+
strategy: 'gitflow',
|
|
146
|
+
releaseBranchPrefix: 'release',
|
|
147
|
+
targetBranch: 'main',
|
|
148
|
+
syncBranch: 'develop',
|
|
149
|
+
description: 'release/vX.Y.Z from develop → main → tag → sync develop'
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
return strategies[def.git_strategy];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Generate release branch name for version
|
|
158
|
+
* @param {string} tier
|
|
159
|
+
* @param {string} version - semver without 'v' prefix
|
|
160
|
+
* @returns {string|null}
|
|
161
|
+
*/
|
|
162
|
+
function getReleaseBranchName(tier, version) {
|
|
163
|
+
const strategy = getGitStrategy(tier);
|
|
164
|
+
if (!strategy.releaseBranchPrefix) return null; // trunk-based
|
|
165
|
+
return `${strategy.releaseBranchPrefix}/v${version}`;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Generate hotfix branch name
|
|
170
|
+
* @param {string} name - slug for the fix
|
|
171
|
+
* @returns {string}
|
|
172
|
+
*/
|
|
173
|
+
function getHotfixBranchName(name) {
|
|
174
|
+
const slug = name.replace(/[^a-zA-Z0-9-_]/g, '-').replace(/-+/g, '-').toLowerCase();
|
|
175
|
+
return `hotfix/${slug}`;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// ─────────────────────────────────────────────
|
|
179
|
+
// Coverage Validation
|
|
180
|
+
// ─────────────────────────────────────────────
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Check if coverage meets tier threshold
|
|
184
|
+
* @param {string} tier
|
|
185
|
+
* @param {number} coveragePct
|
|
186
|
+
* @returns {{ passes: boolean, threshold: number, actual: number, gap: number }}
|
|
187
|
+
*/
|
|
188
|
+
function checkCoverage(tier, coveragePct) {
|
|
189
|
+
const def = getTier(tier);
|
|
190
|
+
const passes = coveragePct >= def.coverage_threshold;
|
|
191
|
+
return {
|
|
192
|
+
passes,
|
|
193
|
+
threshold: def.coverage_threshold,
|
|
194
|
+
actual: coveragePct,
|
|
195
|
+
gap: passes ? 0 : def.coverage_threshold - coveragePct
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// ─────────────────────────────────────────────
|
|
200
|
+
// Feature Flag Helpers
|
|
201
|
+
// ─────────────────────────────────────────────
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Get feature flags that should be enabled for a tier
|
|
205
|
+
* MVP: only must features (all other flags = false)
|
|
206
|
+
* Medium: must + should
|
|
207
|
+
* Enterprise: all
|
|
208
|
+
*
|
|
209
|
+
* @param {string} tier
|
|
210
|
+
* @returns {{ enabled_moscow: string[], disabled_moscow: string[] }}
|
|
211
|
+
*/
|
|
212
|
+
function getFeatureFlagConfig(tier) {
|
|
213
|
+
const def = getTier(tier);
|
|
214
|
+
const all = ['must', 'should', 'could'];
|
|
215
|
+
const enabled = def.moscow_scope;
|
|
216
|
+
const disabled = all.filter(m => !enabled.includes(m));
|
|
217
|
+
|
|
218
|
+
return {
|
|
219
|
+
enabled_moscow: enabled,
|
|
220
|
+
disabled_moscow: disabled,
|
|
221
|
+
flag_config: {
|
|
222
|
+
ENABLE_SHOULD_FEATURES: enabled.includes('should'),
|
|
223
|
+
ENABLE_COULD_FEATURES: enabled.includes('could')
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// ─────────────────────────────────────────────
|
|
229
|
+
// Config Integration
|
|
230
|
+
// ─────────────────────────────────────────────
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Build release config section for .planning/config.json
|
|
234
|
+
* @param {string} currentTier
|
|
235
|
+
* @returns {object}
|
|
236
|
+
*/
|
|
237
|
+
function buildReleaseConfig(currentTier = 'mvp') {
|
|
238
|
+
return {
|
|
239
|
+
tier: currentTier,
|
|
240
|
+
tiers: {
|
|
241
|
+
mvp: {
|
|
242
|
+
moscow_scope: TIER_DEFINITIONS.mvp.moscow_scope,
|
|
243
|
+
coverage: TIER_DEFINITIONS.mvp.coverage_threshold,
|
|
244
|
+
git: TIER_DEFINITIONS.mvp.git_strategy,
|
|
245
|
+
checklist_items: TIER_DEFINITIONS.mvp.checklist_count
|
|
246
|
+
},
|
|
247
|
+
medium: {
|
|
248
|
+
moscow_scope: TIER_DEFINITIONS.medium.moscow_scope,
|
|
249
|
+
coverage: TIER_DEFINITIONS.medium.coverage_threshold,
|
|
250
|
+
git: TIER_DEFINITIONS.medium.git_strategy,
|
|
251
|
+
checklist_items: TIER_DEFINITIONS.medium.checklist_count
|
|
252
|
+
},
|
|
253
|
+
enterprise: {
|
|
254
|
+
moscow_scope: TIER_DEFINITIONS.enterprise.moscow_scope,
|
|
255
|
+
coverage: TIER_DEFINITIONS.enterprise.coverage_threshold,
|
|
256
|
+
git: TIER_DEFINITIONS.enterprise.git_strategy,
|
|
257
|
+
checklist_items: TIER_DEFINITIONS.enterprise.checklist_count
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Load current tier from config.json
|
|
265
|
+
* @param {string} configPath - Path to .planning/config.json
|
|
266
|
+
* @returns {string}
|
|
267
|
+
*/
|
|
268
|
+
function loadCurrentTier(configPath) {
|
|
269
|
+
try {
|
|
270
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
271
|
+
return (config.release && config.release.tier) || 'mvp';
|
|
272
|
+
} catch {
|
|
273
|
+
return 'mvp';
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Save current tier to config.json
|
|
279
|
+
* @param {string} configPath
|
|
280
|
+
* @param {string} tier
|
|
281
|
+
*/
|
|
282
|
+
function saveCurrentTier(configPath, tier) {
|
|
283
|
+
if (!isValidTier(tier)) throw new Error(`Invalid tier: ${tier}`);
|
|
284
|
+
|
|
285
|
+
let config = {};
|
|
286
|
+
try {
|
|
287
|
+
config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
288
|
+
} catch {
|
|
289
|
+
// file doesn't exist yet
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (!config.release) config.release = {};
|
|
293
|
+
config.release.tier = tier.toLowerCase();
|
|
294
|
+
if (!config.release.tiers) {
|
|
295
|
+
config.release = { ...config.release, ...buildReleaseConfig(tier) };
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf8');
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// ─────────────────────────────────────────────
|
|
302
|
+
// Validation Summary
|
|
303
|
+
// ─────────────────────────────────────────────
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Generate a tier validation summary
|
|
307
|
+
* @param {string} tier
|
|
308
|
+
* @param {object} checks - { coverage: number, secretsFound: number, auditPassed: boolean }
|
|
309
|
+
* @returns {{ valid: boolean, tier: string, blockers: string[], warnings: string[], summary: string }}
|
|
310
|
+
*/
|
|
311
|
+
function validateRelease(tier, checks = {}) {
|
|
312
|
+
const def = getTier(tier);
|
|
313
|
+
const blockers = [];
|
|
314
|
+
const warnings = [];
|
|
315
|
+
|
|
316
|
+
// Coverage check
|
|
317
|
+
if (checks.coverage !== undefined) {
|
|
318
|
+
const cov = checkCoverage(tier, checks.coverage);
|
|
319
|
+
if (!cov.passes) {
|
|
320
|
+
warnings.push(`Coverage ${checks.coverage}% is below ${tier} threshold (${def.coverage_threshold}%)`);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Security checks
|
|
325
|
+
if (checks.secretsFound > 0) {
|
|
326
|
+
blockers.push(`${checks.secretsFound} potential secret(s) found in committed files`);
|
|
327
|
+
}
|
|
328
|
+
if (checks.auditPassed === false) {
|
|
329
|
+
blockers.push('npm audit found critical vulnerabilities');
|
|
330
|
+
}
|
|
331
|
+
if (checks.hasProdTodos) {
|
|
332
|
+
warnings.push('Production TODO/FIXME comments found in src/');
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const valid = blockers.length === 0;
|
|
336
|
+
|
|
337
|
+
return {
|
|
338
|
+
valid,
|
|
339
|
+
tier,
|
|
340
|
+
tierDef: def,
|
|
341
|
+
blockers,
|
|
342
|
+
warnings,
|
|
343
|
+
summary: valid
|
|
344
|
+
? `${def.name} release validated (${warnings.length} warnings)`
|
|
345
|
+
: `${def.name} release BLOCKED (${blockers.length} blockers)`
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// ─────────────────────────────────────────────
|
|
350
|
+
// CLI Interface
|
|
351
|
+
// ─────────────────────────────────────────────
|
|
352
|
+
|
|
353
|
+
if (require.main === module) {
|
|
354
|
+
const args = process.argv.slice(2);
|
|
355
|
+
const cmd = args[0];
|
|
356
|
+
|
|
357
|
+
try {
|
|
358
|
+
if (cmd === 'get') {
|
|
359
|
+
const tier = args[1];
|
|
360
|
+
if (!tier) { console.error('Usage: tier-manager.cjs get <tier>'); process.exit(1); }
|
|
361
|
+
console.log(JSON.stringify(getTier(tier), null, 2));
|
|
362
|
+
} else if (cmd === 'all') {
|
|
363
|
+
console.log(JSON.stringify(getAllTiers(), null, 2));
|
|
364
|
+
} else if (cmd === 'git-strategy') {
|
|
365
|
+
const tier = args[1];
|
|
366
|
+
if (!tier) { console.error('Usage: tier-manager.cjs git-strategy <tier>'); process.exit(1); }
|
|
367
|
+
console.log(JSON.stringify(getGitStrategy(tier), null, 2));
|
|
368
|
+
} else if (cmd === 'release-branch') {
|
|
369
|
+
const tier = args[1];
|
|
370
|
+
const version = args[2];
|
|
371
|
+
if (!tier || !version) { console.error('Usage: tier-manager.cjs release-branch <tier> <version>'); process.exit(1); }
|
|
372
|
+
const branch = getReleaseBranchName(tier, version);
|
|
373
|
+
console.log(JSON.stringify({ branch }));
|
|
374
|
+
} else if (cmd === 'check-coverage') {
|
|
375
|
+
const tier = args[1];
|
|
376
|
+
const coverage = parseFloat(args[2]);
|
|
377
|
+
if (!tier || isNaN(coverage)) { console.error('Usage: tier-manager.cjs check-coverage <tier> <pct>'); process.exit(1); }
|
|
378
|
+
console.log(JSON.stringify(checkCoverage(tier, coverage), null, 2));
|
|
379
|
+
} else if (cmd === 'build-config') {
|
|
380
|
+
const tier = args[1] || 'mvp';
|
|
381
|
+
console.log(JSON.stringify(buildReleaseConfig(tier), null, 2));
|
|
382
|
+
} else if (cmd === 'load-tier') {
|
|
383
|
+
const configPath = args[1] || '.planning/config.json';
|
|
384
|
+
console.log(JSON.stringify({ tier: loadCurrentTier(configPath) }));
|
|
385
|
+
} else if (cmd === 'save-tier') {
|
|
386
|
+
const tier = args[1];
|
|
387
|
+
const configPath = args[2] || '.planning/config.json';
|
|
388
|
+
if (!tier) { console.error('Usage: tier-manager.cjs save-tier <tier> [config-path]'); process.exit(1); }
|
|
389
|
+
saveCurrentTier(configPath, tier);
|
|
390
|
+
console.log(JSON.stringify({ saved: true, tier }));
|
|
391
|
+
} else if (cmd === 'validate') {
|
|
392
|
+
const tier = args[1];
|
|
393
|
+
if (!tier) { console.error('Usage: tier-manager.cjs validate <tier> [--coverage N]'); process.exit(1); }
|
|
394
|
+
const coverageIdx = args.indexOf('--coverage');
|
|
395
|
+
const checks = {
|
|
396
|
+
coverage: coverageIdx !== -1 ? parseFloat(args[coverageIdx + 1]) : undefined
|
|
397
|
+
};
|
|
398
|
+
console.log(JSON.stringify(validateRelease(tier, checks), null, 2));
|
|
399
|
+
} else {
|
|
400
|
+
console.error(`Unknown command: ${cmd}`);
|
|
401
|
+
console.error('Commands: get, all, git-strategy, release-branch, check-coverage, build-config, load-tier, save-tier, validate');
|
|
402
|
+
process.exit(1);
|
|
403
|
+
}
|
|
404
|
+
} catch (err) {
|
|
405
|
+
console.error(`Error: ${err.message}`);
|
|
406
|
+
process.exit(1);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
module.exports = {
|
|
411
|
+
getTier,
|
|
412
|
+
getAllTiers,
|
|
413
|
+
isValidTier,
|
|
414
|
+
getTierIndex,
|
|
415
|
+
isPromotion,
|
|
416
|
+
getPreviousTier,
|
|
417
|
+
getGitStrategy,
|
|
418
|
+
getReleaseBranchName,
|
|
419
|
+
getHotfixBranchName,
|
|
420
|
+
checkCoverage,
|
|
421
|
+
getFeatureFlagConfig,
|
|
422
|
+
buildReleaseConfig,
|
|
423
|
+
loadCurrentTier,
|
|
424
|
+
saveCurrentTier,
|
|
425
|
+
validateRelease,
|
|
426
|
+
TIER_DEFINITIONS,
|
|
427
|
+
TIER_ORDER
|
|
428
|
+
};
|