gsd-opencode 1.20.3 → 1.22.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/agents/gsd-codebase-mapper.md +9 -1
- package/agents/gsd-debugger.md +66 -10
- package/agents/gsd-executor.md +36 -16
- package/agents/gsd-integration-checker.md +2 -0
- package/agents/gsd-nyquist-auditor.md +178 -0
- package/agents/gsd-phase-researcher.md +28 -34
- package/agents/gsd-plan-checker.md +42 -78
- package/agents/gsd-planner.md +139 -24
- package/agents/gsd-project-researcher.md +11 -1
- package/agents/gsd-research-synthesizer.md +13 -3
- package/agents/gsd-roadmapper.md +25 -15
- package/agents/gsd-verifier.md +29 -6
- package/bin/dm/lib/constants.js +6 -1
- package/bin/dm/src/services/file-ops.js +14 -1
- package/commands/gsd/gsd-add-phase.md +6 -6
- package/commands/gsd/gsd-add-tests.md +41 -0
- package/commands/gsd/gsd-add-todo.md +7 -7
- package/commands/gsd/gsd-audit-milestone.md +9 -9
- package/commands/gsd/gsd-check-profile.md +3 -3
- package/commands/gsd/gsd-check-todos.md +7 -7
- package/commands/gsd/gsd-cleanup.md +2 -2
- package/commands/gsd/gsd-complete-milestone.md +6 -6
- package/commands/gsd/gsd-debug.md +11 -7
- package/commands/gsd/gsd-discuss-phase.md +26 -19
- package/commands/gsd/gsd-execute-phase.md +13 -13
- package/commands/gsd/gsd-health.md +7 -7
- package/commands/gsd/gsd-help.md +2 -2
- package/commands/gsd/gsd-insert-phase.md +6 -6
- package/commands/gsd/gsd-join-discord.md +1 -1
- package/commands/gsd/gsd-list-phase-assumptions.md +6 -6
- package/commands/gsd/gsd-map-codebase.md +8 -8
- package/commands/gsd/gsd-new-milestone.md +12 -12
- package/commands/gsd/gsd-new-project.md +12 -12
- package/commands/gsd/gsd-pause-work.md +6 -6
- package/commands/gsd/gsd-plan-milestone-gaps.md +9 -9
- package/commands/gsd/gsd-plan-phase.md +14 -13
- package/commands/gsd/gsd-progress.md +8 -8
- package/commands/gsd/gsd-quick.md +17 -13
- package/commands/gsd/gsd-reapply-patches.md +19 -11
- package/commands/gsd/gsd-remove-phase.md +7 -7
- package/commands/gsd/gsd-research-phase.md +12 -11
- package/commands/gsd/gsd-resume-work.md +8 -8
- package/commands/gsd/gsd-set-profile.md +6 -6
- package/commands/gsd/gsd-settings.md +7 -7
- package/commands/gsd/gsd-update.md +5 -5
- package/commands/gsd/gsd-validate-phase.md +35 -0
- package/commands/gsd/gsd-verify-work.md +11 -11
- package/get-shit-done/bin/gsd-oc-commands/allow-read-config.cjs +235 -0
- package/get-shit-done/bin/gsd-oc-tools.cjs +11 -5
- package/get-shit-done/bin/gsd-tools.cjs +45 -6
- package/get-shit-done/bin/lib/commands.cjs +11 -19
- package/get-shit-done/bin/lib/config.cjs +8 -1
- package/get-shit-done/bin/lib/core.cjs +131 -16
- package/get-shit-done/bin/lib/init.cjs +28 -12
- package/get-shit-done/bin/lib/milestone.cjs +34 -8
- package/get-shit-done/bin/lib/phase.cjs +74 -50
- package/get-shit-done/bin/lib/roadmap.cjs +7 -7
- package/get-shit-done/bin/lib/state.cjs +294 -63
- package/get-shit-done/bin/lib/template.cjs +3 -3
- package/get-shit-done/bin/lib/verify.cjs +56 -8
- package/get-shit-done/bin/test/allow-read-config.test.cjs +262 -0
- package/get-shit-done/references/checkpoints.md +1 -1
- package/get-shit-done/references/decimal-phase-calculation.md +6 -6
- package/get-shit-done/references/git-integration.md +3 -3
- package/get-shit-done/references/git-planning-commit.md +2 -2
- package/get-shit-done/references/model-profile-resolution.md +1 -1
- package/get-shit-done/references/model-profiles.md +1 -0
- package/get-shit-done/references/phase-argument-parsing.md +4 -4
- package/get-shit-done/references/planning-config.md +10 -6
- package/get-shit-done/references/questioning.md +17 -0
- package/get-shit-done/references/verification-patterns.md +1 -1
- package/get-shit-done/templates/DEBUG.md +7 -2
- package/get-shit-done/templates/VALIDATION.md +18 -46
- package/get-shit-done/templates/codebase/structure.md +3 -3
- package/get-shit-done/templates/config.json +2 -2
- package/get-shit-done/templates/context.md +14 -0
- package/get-shit-done/templates/phase-prompt.md +10 -10
- package/get-shit-done/templates/retrospective.md +54 -0
- package/get-shit-done/templates/roadmap.md +1 -1
- package/get-shit-done/workflows/add-phase.md +3 -2
- package/get-shit-done/workflows/add-tests.md +351 -0
- package/get-shit-done/workflows/add-todo.md +4 -3
- package/get-shit-done/workflows/audit-milestone.md +40 -5
- package/get-shit-done/workflows/check-todos.md +3 -2
- package/get-shit-done/workflows/cleanup.md +1 -1
- package/get-shit-done/workflows/complete-milestone.md +69 -5
- package/get-shit-done/workflows/diagnose-issues.md +2 -2
- package/get-shit-done/workflows/discovery-phase.md +6 -6
- package/get-shit-done/workflows/discuss-phase.md +194 -58
- package/get-shit-done/workflows/execute-phase.md +29 -23
- package/get-shit-done/workflows/execute-plan.md +22 -18
- package/get-shit-done/workflows/health.md +5 -2
- package/get-shit-done/workflows/help.md +4 -1
- package/get-shit-done/workflows/insert-phase.md +3 -2
- package/get-shit-done/workflows/map-codebase.md +3 -2
- package/get-shit-done/workflows/new-milestone.md +12 -10
- package/get-shit-done/workflows/new-project.md +44 -49
- package/get-shit-done/workflows/oc-set-profile.md +24 -0
- package/get-shit-done/workflows/pause-work.md +2 -2
- package/get-shit-done/workflows/plan-milestone-gaps.md +3 -3
- package/get-shit-done/workflows/plan-phase.md +155 -73
- package/get-shit-done/workflows/progress.md +8 -7
- package/get-shit-done/workflows/quick.md +158 -10
- package/get-shit-done/workflows/remove-phase.md +5 -4
- package/get-shit-done/workflows/research-phase.md +5 -4
- package/get-shit-done/workflows/resume-project.md +3 -2
- package/get-shit-done/workflows/set-profile.md +3 -2
- package/get-shit-done/workflows/settings.md +6 -6
- package/get-shit-done/workflows/transition.md +5 -5
- package/get-shit-done/workflows/update.md +45 -19
- package/get-shit-done/workflows/validate-phase.md +167 -0
- package/get-shit-done/workflows/verify-phase.md +10 -9
- package/get-shit-done/workflows/verify-work.md +18 -4
- package/package.json +1 -1
|
@@ -6,6 +6,7 @@ const fs = require('fs');
|
|
|
6
6
|
const path = require('path');
|
|
7
7
|
const { safeReadFile, normalizePhaseName, execGit, findPhaseInternal, getMilestoneInfo, output, error } = require('./core.cjs');
|
|
8
8
|
const { extractFrontmatter, parseMustHavesBlock } = require('./frontmatter.cjs');
|
|
9
|
+
const { writeStateMd } = require('./state.cjs');
|
|
9
10
|
|
|
10
11
|
function cmdVerifySummary(cwd, summaryPath, checkFileCount, raw) {
|
|
11
12
|
if (!summaryPath) {
|
|
@@ -410,7 +411,7 @@ function cmdValidateConsistency(cwd, raw) {
|
|
|
410
411
|
|
|
411
412
|
// Extract phases from ROADMAP
|
|
412
413
|
const roadmapPhases = new Set();
|
|
413
|
-
const phasePattern = /#{2,4}\s*Phase\s+(\d+(?:\.\d+)
|
|
414
|
+
const phasePattern = /#{2,4}\s*Phase\s+(\d+[A-Z]?(?:\.\d+)*)\s*:/gi;
|
|
414
415
|
let m;
|
|
415
416
|
while ((m = phasePattern.exec(roadmapContent)) !== null) {
|
|
416
417
|
roadmapPhases.add(m[1]);
|
|
@@ -422,7 +423,7 @@ function cmdValidateConsistency(cwd, raw) {
|
|
|
422
423
|
const entries = fs.readdirSync(phasesDir, { withFileTypes: true });
|
|
423
424
|
const dirs = entries.filter(e => e.isDirectory()).map(e => e.name);
|
|
424
425
|
for (const dir of dirs) {
|
|
425
|
-
const dm = dir.match(/^(\d+(?:\.\d+)
|
|
426
|
+
const dm = dir.match(/^(\d+[A-Z]?(?:\.\d+)*)/i);
|
|
426
427
|
if (dm) diskPhases.add(dm[1]);
|
|
427
428
|
}
|
|
428
429
|
} catch {}
|
|
@@ -572,14 +573,14 @@ function cmdValidateHealth(cwd, options, raw) {
|
|
|
572
573
|
} else {
|
|
573
574
|
const stateContent = fs.readFileSync(statePath, 'utf-8');
|
|
574
575
|
// Extract phase references from STATE.md
|
|
575
|
-
const phaseRefs = [...stateContent.matchAll(/[Pp]hase\s+(\d+(?:\.\d+)
|
|
576
|
+
const phaseRefs = [...stateContent.matchAll(/[Pp]hase\s+(\d+(?:\.\d+)*)/g)].map(m => m[1]);
|
|
576
577
|
// Get disk phases
|
|
577
578
|
const diskPhases = new Set();
|
|
578
579
|
try {
|
|
579
580
|
const entries = fs.readdirSync(phasesDir, { withFileTypes: true });
|
|
580
581
|
for (const e of entries) {
|
|
581
582
|
if (e.isDirectory()) {
|
|
582
|
-
const m = e.name.match(/^(\d+(?:\.\d+)
|
|
583
|
+
const m = e.name.match(/^(\d+(?:\.\d+)*)/);
|
|
583
584
|
if (m) diskPhases.add(m[1]);
|
|
584
585
|
}
|
|
585
586
|
}
|
|
@@ -616,11 +617,23 @@ function cmdValidateHealth(cwd, options, raw) {
|
|
|
616
617
|
}
|
|
617
618
|
}
|
|
618
619
|
|
|
620
|
+
// ─── Check 5b: Nyquist validation key presence ──────────────────────────
|
|
621
|
+
if (fs.existsSync(configPath)) {
|
|
622
|
+
try {
|
|
623
|
+
const configRaw = fs.readFileSync(configPath, 'utf-8');
|
|
624
|
+
const configParsed = JSON.parse(configRaw);
|
|
625
|
+
if (configParsed.workflow && configParsed.workflow.nyquist_validation === undefined) {
|
|
626
|
+
addIssue('warning', 'W008', 'config.json: workflow.nyquist_validation absent (defaults to enabled but agents may skip)', 'Run /gsd-health --repair to add key', true);
|
|
627
|
+
if (!repairs.includes('addNyquistKey')) repairs.push('addNyquistKey');
|
|
628
|
+
}
|
|
629
|
+
} catch {}
|
|
630
|
+
}
|
|
631
|
+
|
|
619
632
|
// ─── Check 6: Phase directory naming (NN-name format) ─────────────────────
|
|
620
633
|
try {
|
|
621
634
|
const entries = fs.readdirSync(phasesDir, { withFileTypes: true });
|
|
622
635
|
for (const e of entries) {
|
|
623
|
-
if (e.isDirectory() && !e.name.match(/^\d{2}(?:\.\d+)
|
|
636
|
+
if (e.isDirectory() && !e.name.match(/^\d{2}(?:\.\d+)*-[\w-]+$/)) {
|
|
624
637
|
addIssue('warning', 'W005', `Phase directory "${e.name}" doesn't follow NN-name format`, 'Rename to match pattern (e.g., 01-setup)');
|
|
625
638
|
}
|
|
626
639
|
}
|
|
@@ -645,12 +658,30 @@ function cmdValidateHealth(cwd, options, raw) {
|
|
|
645
658
|
}
|
|
646
659
|
} catch {}
|
|
647
660
|
|
|
661
|
+
// ─── Check 7b: Nyquist VALIDATION.md consistency ────────────────────────
|
|
662
|
+
try {
|
|
663
|
+
const phaseEntries = fs.readdirSync(phasesDir, { withFileTypes: true });
|
|
664
|
+
for (const e of phaseEntries) {
|
|
665
|
+
if (!e.isDirectory()) continue;
|
|
666
|
+
const phaseFiles = fs.readdirSync(path.join(phasesDir, e.name));
|
|
667
|
+
const hasResearch = phaseFiles.some(f => f.endsWith('-RESEARCH.md'));
|
|
668
|
+
const hasValidation = phaseFiles.some(f => f.endsWith('-VALIDATION.md'));
|
|
669
|
+
if (hasResearch && !hasValidation) {
|
|
670
|
+
const researchFile = phaseFiles.find(f => f.endsWith('-RESEARCH.md'));
|
|
671
|
+
const researchContent = fs.readFileSync(path.join(phasesDir, e.name, researchFile), 'utf-8');
|
|
672
|
+
if (researchContent.includes('## Validation Architecture')) {
|
|
673
|
+
addIssue('warning', 'W009', `Phase ${e.name}: has Validation Architecture in RESEARCH.md but no VALIDATION.md`, 'Re-run /gsd-plan-phase with --research to regenerate');
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
} catch {}
|
|
678
|
+
|
|
648
679
|
// ─── Check 8: Run existing consistency checks ─────────────────────────────
|
|
649
680
|
// Inline subset of cmdValidateConsistency
|
|
650
681
|
if (fs.existsSync(roadmapPath)) {
|
|
651
682
|
const roadmapContent = fs.readFileSync(roadmapPath, 'utf-8');
|
|
652
683
|
const roadmapPhases = new Set();
|
|
653
|
-
const phasePattern = /#{2,4}\s*Phase\s+(\d+(?:\.\d+)
|
|
684
|
+
const phasePattern = /#{2,4}\s*Phase\s+(\d+[A-Z]?(?:\.\d+)*)\s*:/gi;
|
|
654
685
|
let m;
|
|
655
686
|
while ((m = phasePattern.exec(roadmapContent)) !== null) {
|
|
656
687
|
roadmapPhases.add(m[1]);
|
|
@@ -661,7 +692,7 @@ function cmdValidateHealth(cwd, options, raw) {
|
|
|
661
692
|
const entries = fs.readdirSync(phasesDir, { withFileTypes: true });
|
|
662
693
|
for (const e of entries) {
|
|
663
694
|
if (e.isDirectory()) {
|
|
664
|
-
const dm = e.name.match(/^(\d+(?:\.\d+)
|
|
695
|
+
const dm = e.name.match(/^(\d+[A-Z]?(?:\.\d+)*)/i);
|
|
665
696
|
if (dm) diskPhases.add(dm[1]);
|
|
666
697
|
}
|
|
667
698
|
}
|
|
@@ -725,10 +756,27 @@ function cmdValidateHealth(cwd, options, raw) {
|
|
|
725
756
|
stateContent += `**Status:** Resuming\n\n`;
|
|
726
757
|
stateContent += `## Session Log\n\n`;
|
|
727
758
|
stateContent += `- ${new Date().toISOString().split('T')[0]}: STATE.md regenerated by /gsd-health --repair\n`;
|
|
728
|
-
|
|
759
|
+
writeStateMd(statePath, stateContent, cwd);
|
|
729
760
|
repairActions.push({ action: repair, success: true, path: 'STATE.md' });
|
|
730
761
|
break;
|
|
731
762
|
}
|
|
763
|
+
case 'addNyquistKey': {
|
|
764
|
+
if (fs.existsSync(configPath)) {
|
|
765
|
+
try {
|
|
766
|
+
const configRaw = fs.readFileSync(configPath, 'utf-8');
|
|
767
|
+
const configParsed = JSON.parse(configRaw);
|
|
768
|
+
if (!configParsed.workflow) configParsed.workflow = {};
|
|
769
|
+
if (configParsed.workflow.nyquist_validation === undefined) {
|
|
770
|
+
configParsed.workflow.nyquist_validation = true;
|
|
771
|
+
fs.writeFileSync(configPath, JSON.stringify(configParsed, null, 2), 'utf-8');
|
|
772
|
+
}
|
|
773
|
+
repairActions.push({ action: repair, success: true, path: 'config.json' });
|
|
774
|
+
} catch (err) {
|
|
775
|
+
repairActions.push({ action: repair, success: false, error: err.message });
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
break;
|
|
779
|
+
}
|
|
732
780
|
}
|
|
733
781
|
} catch (err) {
|
|
734
782
|
repairActions.push({ action: repair, success: false, error: err.message });
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* allow-read-config.test.cjs — Tests for allow-read-config command
|
|
3
|
+
*
|
|
4
|
+
* Tests the allow-read-config command functionality:
|
|
5
|
+
* - Permission creation
|
|
6
|
+
* - Idempotency (detecting existing permission)
|
|
7
|
+
* - Dry-run mode
|
|
8
|
+
* - Backup creation
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const os = require('os');
|
|
14
|
+
const { execSync } = require('child_process');
|
|
15
|
+
|
|
16
|
+
const CLI_PATH = path.join(__dirname, '../gsd-oc-commands/allow-read-config.cjs');
|
|
17
|
+
const TOOLS_PATH = path.join(__dirname, '../gsd-oc-tools.cjs');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Create a temporary test directory
|
|
21
|
+
*/
|
|
22
|
+
function createTestDir() {
|
|
23
|
+
const testDir = path.join(os.tmpdir(), `gsd-oc-test-${Date.now()}`);
|
|
24
|
+
fs.mkdirSync(testDir, { recursive: true });
|
|
25
|
+
return testDir;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Clean up test directory
|
|
30
|
+
*/
|
|
31
|
+
function cleanupTestDir(testDir) {
|
|
32
|
+
if (fs.existsSync(testDir)) {
|
|
33
|
+
fs.rmSync(testDir, { recursive: true, force: true });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Run CLI command and parse JSON output
|
|
39
|
+
*/
|
|
40
|
+
function runCLI(testDir, args) {
|
|
41
|
+
const cmd = `node ${TOOLS_PATH} allow-read-config ${args.join(' ')}`;
|
|
42
|
+
const output = execSync(cmd, { cwd: testDir, encoding: 'utf8' });
|
|
43
|
+
return JSON.parse(output);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Test: Create new opencode.json with permission
|
|
48
|
+
*/
|
|
49
|
+
function testCreatePermission() {
|
|
50
|
+
console.log('Test: Create new opencode.json with permission...');
|
|
51
|
+
|
|
52
|
+
const testDir = createTestDir();
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
const result = runCLI(testDir, []);
|
|
56
|
+
|
|
57
|
+
if (!result.success) {
|
|
58
|
+
throw new Error(`Expected success, got: ${JSON.stringify(result)}`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (result.data.action !== 'add_permission') {
|
|
62
|
+
throw new Error(`Expected action 'add_permission', got: ${result.data.action}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (result.data.created !== true) {
|
|
66
|
+
throw new Error(`Expected created=true, got: ${result.data.created}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Verify opencode.json was created
|
|
70
|
+
const opencodePath = path.join(testDir, 'opencode.json');
|
|
71
|
+
if (!fs.existsSync(opencodePath)) {
|
|
72
|
+
throw new Error('opencode.json was not created');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const content = JSON.parse(fs.readFileSync(opencodePath, 'utf8'));
|
|
76
|
+
if (!content.permission?.external_directory) {
|
|
77
|
+
throw new Error('Permission not added to opencode.json');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
console.log('✓ PASS: Create permission\n');
|
|
81
|
+
return true;
|
|
82
|
+
} catch (err) {
|
|
83
|
+
console.error('✗ FAIL:', err.message, '\n');
|
|
84
|
+
return false;
|
|
85
|
+
} finally {
|
|
86
|
+
cleanupTestDir(testDir);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Test: Idempotency - detect existing permission
|
|
92
|
+
*/
|
|
93
|
+
function testIdempotency() {
|
|
94
|
+
console.log('Test: Idempotency (detect existing permission)...');
|
|
95
|
+
|
|
96
|
+
const testDir = createTestDir();
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
// First call - create permission
|
|
100
|
+
runCLI(testDir, []);
|
|
101
|
+
|
|
102
|
+
// Second call - should detect existing
|
|
103
|
+
const result = runCLI(testDir, []);
|
|
104
|
+
|
|
105
|
+
if (!result.success) {
|
|
106
|
+
throw new Error(`Expected success, got: ${JSON.stringify(result)}`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (result.data.action !== 'permission_exists') {
|
|
110
|
+
throw new Error(`Expected action 'permission_exists', got: ${result.data.action}`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
console.log('✓ PASS: Idempotency\n');
|
|
114
|
+
return true;
|
|
115
|
+
} catch (err) {
|
|
116
|
+
console.error('✗ FAIL:', err.message, '\n');
|
|
117
|
+
return false;
|
|
118
|
+
} finally {
|
|
119
|
+
cleanupTestDir(testDir);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Test: Dry-run mode
|
|
125
|
+
*/
|
|
126
|
+
function testDryRun() {
|
|
127
|
+
console.log('Test: Dry-run mode...');
|
|
128
|
+
|
|
129
|
+
const testDir = createTestDir();
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
const result = runCLI(testDir, ['--dry-run']);
|
|
133
|
+
|
|
134
|
+
if (!result.success) {
|
|
135
|
+
throw new Error(`Expected success, got: ${JSON.stringify(result)}`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (result.data.dryRun !== true) {
|
|
139
|
+
throw new Error(`Expected dryRun=true, got: ${result.data.dryRun}`);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Verify opencode.json was NOT created
|
|
143
|
+
const opencodePath = path.join(testDir, 'opencode.json');
|
|
144
|
+
if (fs.existsSync(opencodePath)) {
|
|
145
|
+
throw new Error('opencode.json should not be created in dry-run mode');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
console.log('✓ PASS: Dry-run mode\n');
|
|
149
|
+
return true;
|
|
150
|
+
} catch (err) {
|
|
151
|
+
console.error('✗ FAIL:', err.message, '\n');
|
|
152
|
+
return false;
|
|
153
|
+
} finally {
|
|
154
|
+
cleanupTestDir(testDir);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Test: Backup creation on update
|
|
160
|
+
*/
|
|
161
|
+
function testBackupCreation() {
|
|
162
|
+
console.log('Test: Backup creation on update...');
|
|
163
|
+
|
|
164
|
+
const testDir = createTestDir();
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
// Create initial opencode.json
|
|
168
|
+
const opencodePath = path.join(testDir, 'opencode.json');
|
|
169
|
+
const initialContent = {
|
|
170
|
+
"$schema": "https://opencode.ai/config.json",
|
|
171
|
+
"model": "test/model"
|
|
172
|
+
};
|
|
173
|
+
fs.writeFileSync(opencodePath, JSON.stringify(initialContent, null, 2) + '\n');
|
|
174
|
+
|
|
175
|
+
// Run allow-read-config
|
|
176
|
+
const result = runCLI(testDir, []);
|
|
177
|
+
|
|
178
|
+
if (!result.success) {
|
|
179
|
+
throw new Error(`Expected success, got: ${JSON.stringify(result)}`);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (!result.data.backup) {
|
|
183
|
+
throw new Error('Expected backup path, got none');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (!fs.existsSync(result.data.backup)) {
|
|
187
|
+
throw new Error(`Backup file does not exist: ${result.data.backup}`);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Verify backup content matches original
|
|
191
|
+
const backupContent = JSON.parse(fs.readFileSync(result.data.backup, 'utf8'));
|
|
192
|
+
if (JSON.stringify(backupContent) !== JSON.stringify(initialContent)) {
|
|
193
|
+
throw new Error('Backup content does not match original');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
console.log('✓ PASS: Backup creation\n');
|
|
197
|
+
return true;
|
|
198
|
+
} catch (err) {
|
|
199
|
+
console.error('✗ FAIL:', err.message, '\n');
|
|
200
|
+
return false;
|
|
201
|
+
} finally {
|
|
202
|
+
cleanupTestDir(testDir);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Test: Verbose output
|
|
208
|
+
*/
|
|
209
|
+
function testVerbose() {
|
|
210
|
+
console.log('Test: Verbose output...');
|
|
211
|
+
|
|
212
|
+
const testDir = createTestDir();
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
const cmd = `node ${TOOLS_PATH} allow-read-config --verbose`;
|
|
216
|
+
const output = execSync(cmd, { cwd: testDir, encoding: 'utf8', stdio: 'pipe' });
|
|
217
|
+
|
|
218
|
+
// Verbose output should contain log messages to stderr
|
|
219
|
+
// We just verify it doesn't crash
|
|
220
|
+
console.log('✓ PASS: Verbose output\n');
|
|
221
|
+
return true;
|
|
222
|
+
} catch (err) {
|
|
223
|
+
console.error('✗ FAIL:', err.message, '\n');
|
|
224
|
+
return false;
|
|
225
|
+
} finally {
|
|
226
|
+
cleanupTestDir(testDir);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Run all tests
|
|
232
|
+
*/
|
|
233
|
+
function runTests() {
|
|
234
|
+
console.log('Running allow-read-config tests...\n');
|
|
235
|
+
console.log('=' .repeat(50));
|
|
236
|
+
console.log();
|
|
237
|
+
|
|
238
|
+
const results = [
|
|
239
|
+
testCreatePermission(),
|
|
240
|
+
testIdempotency(),
|
|
241
|
+
testDryRun(),
|
|
242
|
+
testBackupCreation(),
|
|
243
|
+
testVerbose()
|
|
244
|
+
];
|
|
245
|
+
|
|
246
|
+
const passed = results.filter(r => r).length;
|
|
247
|
+
const total = results.length;
|
|
248
|
+
|
|
249
|
+
console.log('=' .repeat(50));
|
|
250
|
+
console.log(`Results: ${passed}/${total} tests passed`);
|
|
251
|
+
|
|
252
|
+
if (passed === total) {
|
|
253
|
+
console.log('✓ All tests passed!\n');
|
|
254
|
+
process.exit(0);
|
|
255
|
+
} else {
|
|
256
|
+
console.error(`✗ ${total - passed} test(s) failed\n`);
|
|
257
|
+
process.exit(1);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Run tests
|
|
262
|
+
runTests();
|
|
@@ -8,7 +8,7 @@ Plans execute autonomously. Checkpoints formalize interaction points where human
|
|
|
8
8
|
2. **OpenCode sets up the verification environment** - Start dev servers, seed databases, configure env vars
|
|
9
9
|
3. **User only does what requires human judgment** - Visual checks, UX evaluation, "does this feel right?"
|
|
10
10
|
4. **Secrets come from user, automation comes from OpenCode** - Ask for API keys, then OpenCode uses them via CLI
|
|
11
|
-
5. **Auto-mode bypasses verification/decision checkpoints** — When `workflow.auto_advance` is true in config: human-verify auto-approves, decision auto-selects first option, human-action still stops (auth gates cannot be automated)
|
|
11
|
+
5. **Auto-mode bypasses verification/decision checkpoints** — When `workflow._auto_chain_active` or `workflow.auto_advance` is true in config: human-verify auto-approves, decision auto-selects first option, human-action still stops (auth gates cannot be automated)
|
|
12
12
|
</overview>
|
|
13
13
|
|
|
14
14
|
<checkpoint_types>
|
|
@@ -6,7 +6,7 @@ Calculate the next decimal phase number for urgent insertions.
|
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
# Get next decimal phase after phase 6
|
|
9
|
-
node
|
|
9
|
+
node "$HOME/.config/opencode/get-shit-done/bin/gsd-tools.cjs" phase next-decimal 6
|
|
10
10
|
```
|
|
11
11
|
|
|
12
12
|
Output:
|
|
@@ -32,14 +32,14 @@ With existing decimals:
|
|
|
32
32
|
## Extract Values
|
|
33
33
|
|
|
34
34
|
```bash
|
|
35
|
-
DECIMAL_INFO=$(node
|
|
36
|
-
DECIMAL_PHASE=$(
|
|
37
|
-
BASE_PHASE=$(
|
|
35
|
+
DECIMAL_INFO=$(node "$HOME/.config/opencode/get-shit-done/bin/gsd-tools.cjs" phase next-decimal "${AFTER_PHASE}")
|
|
36
|
+
DECIMAL_PHASE=$(printf '%s\n' "$DECIMAL_INFO" | jq -r '.next')
|
|
37
|
+
BASE_PHASE=$(printf '%s\n' "$DECIMAL_INFO" | jq -r '.base_phase')
|
|
38
38
|
```
|
|
39
39
|
|
|
40
40
|
Or with --raw flag:
|
|
41
41
|
```bash
|
|
42
|
-
DECIMAL_PHASE=$(node
|
|
42
|
+
DECIMAL_PHASE=$(node "$HOME/.config/opencode/get-shit-done/bin/gsd-tools.cjs" phase next-decimal "${AFTER_PHASE}" --raw)
|
|
43
43
|
# Returns just: 06.1
|
|
44
44
|
```
|
|
45
45
|
|
|
@@ -57,7 +57,7 @@ DECIMAL_PHASE=$(node ~/.config/opencode/get-shit-done/bin/gsd-tools.cjs phase ne
|
|
|
57
57
|
Decimal phase directories use the full decimal number:
|
|
58
58
|
|
|
59
59
|
```bash
|
|
60
|
-
SLUG=$(node
|
|
60
|
+
SLUG=$(node "$HOME/.config/opencode/get-shit-done/bin/gsd-tools.cjs" generate-slug "$DESCRIPTION" --raw)
|
|
61
61
|
PHASE_DIR=".planning/phases/${DECIMAL_PHASE}-${SLUG}"
|
|
62
62
|
mkdir -p "$PHASE_DIR"
|
|
63
63
|
```
|
|
@@ -51,7 +51,7 @@ Phases:
|
|
|
51
51
|
What to commit:
|
|
52
52
|
|
|
53
53
|
```bash
|
|
54
|
-
node
|
|
54
|
+
node "$HOME/.config/opencode/get-shit-done/bin/gsd-tools.cjs" commit "docs: initialize [project-name] ([N] phases)" --files .planning/
|
|
55
55
|
```
|
|
56
56
|
|
|
57
57
|
</format>
|
|
@@ -129,7 +129,7 @@ SUMMARY: .planning/phases/XX-name/{phase}-{plan}-SUMMARY.md
|
|
|
129
129
|
What to commit:
|
|
130
130
|
|
|
131
131
|
```bash
|
|
132
|
-
node
|
|
132
|
+
node "$HOME/.config/opencode/get-shit-done/bin/gsd-tools.cjs" commit "docs({phase}-{plan}): complete [plan-name] plan" --files .planning/phases/XX-name/{phase}-{plan}-PLAN.md .planning/phases/XX-name/{phase}-{plan}-SUMMARY.md .planning/STATE.md .planning/ROADMAP.md
|
|
133
133
|
```
|
|
134
134
|
|
|
135
135
|
**Note:** Code files NOT included - already committed per-task.
|
|
@@ -149,7 +149,7 @@ Current: [task name]
|
|
|
149
149
|
What to commit:
|
|
150
150
|
|
|
151
151
|
```bash
|
|
152
|
-
node
|
|
152
|
+
node "$HOME/.config/opencode/get-shit-done/bin/gsd-tools.cjs" commit "wip: [phase-name] paused at task [X]/[Y]" --files .planning/
|
|
153
153
|
```
|
|
154
154
|
|
|
155
155
|
</format>
|
|
@@ -7,7 +7,7 @@ Commit planning artifacts using the gsd-tools CLI, which automatically checks `c
|
|
|
7
7
|
Always use `gsd-tools.cjs commit` for `.planning/` files — it handles `commit_docs` and gitignore checks automatically:
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
node
|
|
10
|
+
node "$HOME/.config/opencode/get-shit-done/bin/gsd-tools.cjs" commit "docs({scope}): {description}" --files .planning/STATE.md .planning/ROADMAP.md
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
The CLI will return `skipped` (with reason) if `commit_docs` is `false` or `.planning/` is gitignored. No manual conditional checks needed.
|
|
@@ -17,7 +17,7 @@ The CLI will return `skipped` (with reason) if `commit_docs` is `false` or `.pla
|
|
|
17
17
|
To fold `.planning/` file changes into the previous commit:
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
node
|
|
20
|
+
node "$HOME/.config/opencode/get-shit-done/bin/gsd-tools.cjs" commit "" --files .planning/codebase/*.md --amend
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
## Commit Message Patterns
|
|
@@ -12,7 +12,7 @@ Default: `simple` if not set or config missing.
|
|
|
12
12
|
|
|
13
13
|
## Lookup Table
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
@$HOME/.config/opencode/get-shit-done/references/model-profiles.md
|
|
16
16
|
|
|
17
17
|
Look up the agent in the table for the resolved profile. Pass the model parameter to task calls:
|
|
18
18
|
|
|
@@ -17,6 +17,7 @@ Model profiles control which OpenCode model each GSD agent uses. This allows bal
|
|
|
17
17
|
| gsd-verifier | sonnet | sonnet | haiku |
|
|
18
18
|
| gsd-plan-checker | sonnet | sonnet | haiku |
|
|
19
19
|
| gsd-integration-checker | sonnet | sonnet | haiku |
|
|
20
|
+
| gsd-nyquist-auditor | sonnet | sonnet | haiku |
|
|
20
21
|
|
|
21
22
|
## Profile Philosophy
|
|
22
23
|
|
|
@@ -14,7 +14,7 @@ From `$ARGUMENTS`:
|
|
|
14
14
|
The `find-phase` command handles normalization and validation in one step:
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
|
-
PHASE_INFO=$(node
|
|
17
|
+
PHASE_INFO=$(node "$HOME/.config/opencode/get-shit-done/bin/gsd-tools.cjs" find-phase "${PHASE}")
|
|
18
18
|
```
|
|
19
19
|
|
|
20
20
|
Returns JSON with:
|
|
@@ -45,8 +45,8 @@ fi
|
|
|
45
45
|
Use `roadmap get-phase` to validate phase exists:
|
|
46
46
|
|
|
47
47
|
```bash
|
|
48
|
-
PHASE_CHECK=$(node
|
|
49
|
-
if [ "$(
|
|
48
|
+
PHASE_CHECK=$(node "$HOME/.config/opencode/get-shit-done/bin/gsd-tools.cjs" roadmap get-phase "${PHASE}")
|
|
49
|
+
if [ "$(printf '%s\n' "$PHASE_CHECK" | jq -r '.found')" = "false" ]; then
|
|
50
50
|
echo "ERROR: Phase ${PHASE} not found in roadmap"
|
|
51
51
|
exit 1
|
|
52
52
|
fi
|
|
@@ -57,5 +57,5 @@ fi
|
|
|
57
57
|
Use `find-phase` for directory lookup:
|
|
58
58
|
|
|
59
59
|
```bash
|
|
60
|
-
PHASE_DIR=$(node
|
|
60
|
+
PHASE_DIR=$(node "$HOME/.config/opencode/get-shit-done/bin/gsd-tools.cjs" find-phase "${PHASE}" --raw)
|
|
61
61
|
```
|
|
@@ -40,14 +40,16 @@ Configuration options for `.planning/` directory behavior.
|
|
|
40
40
|
|
|
41
41
|
```bash
|
|
42
42
|
# Commit with automatic commit_docs + gitignore checks:
|
|
43
|
-
node
|
|
43
|
+
node "$HOME/.config/opencode/get-shit-done/bin/gsd-tools.cjs" commit "docs: update state" --files .planning/STATE.md
|
|
44
44
|
|
|
45
45
|
# Load config via state load (returns JSON):
|
|
46
|
-
INIT=$(node
|
|
46
|
+
INIT=$(node "$HOME/.config/opencode/get-shit-done/bin/gsd-tools.cjs" state load)
|
|
47
|
+
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
|
47
48
|
# commit_docs is available in the JSON output
|
|
48
49
|
|
|
49
50
|
# Or use init commands which include commit_docs:
|
|
50
|
-
INIT=$(node
|
|
51
|
+
INIT=$(node "$HOME/.config/opencode/get-shit-done/bin/gsd-tools.cjs" init execute-phase "1")
|
|
52
|
+
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
|
51
53
|
# commit_docs is included in all init command outputs
|
|
52
54
|
```
|
|
53
55
|
|
|
@@ -56,7 +58,7 @@ INIT=$(node ~/.config/opencode/get-shit-done/bin/gsd-tools.cjs init execute-phas
|
|
|
56
58
|
**Commit via CLI (handles checks automatically):**
|
|
57
59
|
|
|
58
60
|
```bash
|
|
59
|
-
node
|
|
61
|
+
node "$HOME/.config/opencode/get-shit-done/bin/gsd-tools.cjs" commit "docs: update state" --files .planning/STATE.md
|
|
60
62
|
```
|
|
61
63
|
|
|
62
64
|
The CLI checks `commit_docs` config and gitignore status internally — no manual conditionals needed.
|
|
@@ -144,13 +146,15 @@ To use uncommitted mode:
|
|
|
144
146
|
|
|
145
147
|
Use `init execute-phase` which returns all config as JSON:
|
|
146
148
|
```bash
|
|
147
|
-
INIT=$(node
|
|
149
|
+
INIT=$(node "$HOME/.config/opencode/get-shit-done/bin/gsd-tools.cjs" init execute-phase "1")
|
|
150
|
+
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
|
148
151
|
# JSON output includes: branching_strategy, phase_branch_template, milestone_branch_template
|
|
149
152
|
```
|
|
150
153
|
|
|
151
154
|
Or use `state load` for the config values:
|
|
152
155
|
```bash
|
|
153
|
-
INIT=$(node
|
|
156
|
+
INIT=$(node "$HOME/.config/opencode/get-shit-done/bin/gsd-tools.cjs" state load)
|
|
157
|
+
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
|
154
158
|
# Parse branching_strategy, phase_branch_template, milestone_branch_template from JSON
|
|
155
159
|
```
|
|
156
160
|
|
|
@@ -100,6 +100,23 @@ Users who want a slightly modified version of an option can select "Other" and r
|
|
|
100
100
|
|
|
101
101
|
</using_askuserquestion>
|
|
102
102
|
|
|
103
|
+
<freeform_rule>
|
|
104
|
+
|
|
105
|
+
**When the user wants to explain freely, STOP using question.**
|
|
106
|
+
|
|
107
|
+
If a user selects "Other" and their response signals they want to describe something in their own words (e.g., "let me describe it", "I'll explain", "something else", or any open-ended reply that isn't choosing/modifying an existing option), you MUST:
|
|
108
|
+
|
|
109
|
+
1. **Ask your follow-up as plain text** — NOT via question
|
|
110
|
+
2. **Wait for them to type at the normal prompt**
|
|
111
|
+
3. **Resume question** only after processing their freeform response
|
|
112
|
+
|
|
113
|
+
The same applies if YOU include a freeform-indicating option (like "Let me explain" or "Describe in detail") and the user selects it.
|
|
114
|
+
|
|
115
|
+
**Wrong:** User says "let me describe it" → question("What feature?", ["Feature A", "Feature B", "Describe in detail"])
|
|
116
|
+
**Right:** User says "let me describe it" → "Go ahead — what are you thinking?"
|
|
117
|
+
|
|
118
|
+
</freeform_rule>
|
|
119
|
+
|
|
103
120
|
<context_checklist>
|
|
104
121
|
|
|
105
122
|
Use this as a **background checklist**, not a conversation structure. Check these mentally as you go. If gaps remain, weave questions naturally.
|
|
@@ -600,7 +600,7 @@ Some things can't be verified programmatically. Flag these for human testing:
|
|
|
600
600
|
|
|
601
601
|
For automation-first checkpoint patterns, server lifecycle management, CLI installation handling, and error recovery protocols, see:
|
|
602
602
|
|
|
603
|
-
|
|
603
|
+
**@$HOME/.config/opencode/get-shit-done/references/checkpoints.md** → `<automation_reference>` section
|
|
604
604
|
|
|
605
605
|
Key principles:
|
|
606
606
|
- OpenCode sets up verification environment BEFORE presenting checkpoints
|
|
@@ -8,7 +8,7 @@ Template for `.planning/debug/[slug].md` — active debug session tracking.
|
|
|
8
8
|
|
|
9
9
|
```markdown
|
|
10
10
|
---
|
|
11
|
-
status: gathering | investigating | fixing | verifying | resolved
|
|
11
|
+
status: gathering | investigating | fixing | verifying | awaiting_human_verify | resolved
|
|
12
12
|
trigger: "[verbatim user input]"
|
|
13
13
|
created: [ISO timestamp]
|
|
14
14
|
updated: [ISO timestamp]
|
|
@@ -127,9 +127,14 @@ files_changed: []
|
|
|
127
127
|
- Update Resolution.verification with results
|
|
128
128
|
- If verification fails: status → "investigating", try again
|
|
129
129
|
|
|
130
|
+
**After self-verification passes:**
|
|
131
|
+
- status -> "awaiting_human_verify"
|
|
132
|
+
- Request explicit user confirmation in a checkpoint
|
|
133
|
+
- Do NOT move file to resolved yet
|
|
134
|
+
|
|
130
135
|
**On resolution:**
|
|
131
136
|
- status → "resolved"
|
|
132
|
-
- Move file to .planning/debug/resolved/
|
|
137
|
+
- Move file to .planning/debug/resolved/ (only after user confirms fix)
|
|
133
138
|
|
|
134
139
|
</lifecycle>
|
|
135
140
|
|