gsd-opencode 1.33.2 → 1.35.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-advisor-researcher.md +23 -0
- package/agents/gsd-ai-researcher.md +142 -0
- package/agents/gsd-code-fixer.md +523 -0
- package/agents/gsd-code-reviewer.md +361 -0
- package/agents/gsd-debugger.md +14 -1
- package/agents/gsd-domain-researcher.md +162 -0
- package/agents/gsd-eval-auditor.md +170 -0
- package/agents/gsd-eval-planner.md +161 -0
- package/agents/gsd-executor.md +70 -7
- package/agents/gsd-framework-selector.md +167 -0
- package/agents/gsd-intel-updater.md +320 -0
- package/agents/gsd-phase-researcher.md +26 -0
- package/agents/gsd-plan-checker.md +12 -0
- package/agents/gsd-planner.md +16 -6
- package/agents/gsd-project-researcher.md +23 -0
- package/agents/gsd-ui-researcher.md +23 -0
- package/agents/gsd-verifier.md +55 -1
- package/commands/gsd/gsd-add-backlog.md +1 -1
- package/commands/gsd/gsd-add-phase.md +1 -1
- package/commands/gsd/gsd-add-todo.md +1 -1
- package/commands/gsd/gsd-ai-integration-phase.md +36 -0
- package/commands/gsd/gsd-audit-fix.md +33 -0
- package/commands/gsd/gsd-autonomous.md +1 -0
- package/commands/gsd/gsd-check-todos.md +1 -1
- package/commands/gsd/gsd-code-review-fix.md +52 -0
- package/commands/gsd/gsd-code-review.md +55 -0
- package/commands/gsd/gsd-complete-milestone.md +1 -1
- package/commands/gsd/gsd-debug.md +1 -1
- package/commands/gsd/gsd-eval-review.md +32 -0
- package/commands/gsd/gsd-explore.md +27 -0
- package/commands/gsd/gsd-from-gsd2.md +45 -0
- package/commands/gsd/gsd-health.md +1 -1
- package/commands/gsd/gsd-import.md +36 -0
- package/commands/gsd/gsd-insert-phase.md +1 -1
- package/commands/gsd/gsd-intel.md +183 -0
- package/commands/gsd/gsd-manager.md +1 -1
- package/commands/gsd/gsd-next.md +2 -0
- package/commands/gsd/gsd-reapply-patches.md +58 -3
- package/commands/gsd/gsd-remove-phase.md +1 -1
- package/commands/gsd/gsd-review.md +4 -2
- package/commands/gsd/gsd-scan.md +26 -0
- package/commands/gsd/gsd-set-profile.md +1 -1
- package/commands/gsd/gsd-thread.md +1 -1
- package/commands/gsd/gsd-undo.md +34 -0
- package/commands/gsd/gsd-workstreams.md +6 -6
- package/get-shit-done/bin/gsd-tools.cjs +143 -5
- package/get-shit-done/bin/lib/commands.cjs +10 -2
- package/get-shit-done/bin/lib/config.cjs +71 -37
- package/get-shit-done/bin/lib/core.cjs +70 -8
- package/get-shit-done/bin/lib/gsd2-import.cjs +511 -0
- package/get-shit-done/bin/lib/init.cjs +20 -6
- package/get-shit-done/bin/lib/intel.cjs +660 -0
- package/get-shit-done/bin/lib/learnings.cjs +378 -0
- package/get-shit-done/bin/lib/milestone.cjs +25 -15
- package/get-shit-done/bin/lib/model-profiles.cjs +17 -17
- package/get-shit-done/bin/lib/phase.cjs +148 -112
- package/get-shit-done/bin/lib/roadmap.cjs +12 -5
- package/get-shit-done/bin/lib/security.cjs +119 -0
- package/get-shit-done/bin/lib/state.cjs +283 -221
- package/get-shit-done/bin/lib/template.cjs +8 -4
- package/get-shit-done/bin/lib/verify.cjs +42 -5
- package/get-shit-done/references/ai-evals.md +156 -0
- package/get-shit-done/references/ai-frameworks.md +186 -0
- package/get-shit-done/references/common-bug-patterns.md +114 -0
- package/get-shit-done/references/few-shot-examples/plan-checker.md +73 -0
- package/get-shit-done/references/few-shot-examples/verifier.md +109 -0
- package/get-shit-done/references/gates.md +70 -0
- package/get-shit-done/references/ios-scaffold.md +123 -0
- package/get-shit-done/references/model-profile-resolution.md +6 -7
- package/get-shit-done/references/model-profiles.md +20 -14
- package/get-shit-done/references/planning-config.md +237 -0
- package/get-shit-done/references/thinking-models-debug.md +44 -0
- package/get-shit-done/references/thinking-models-execution.md +50 -0
- package/get-shit-done/references/thinking-models-planning.md +62 -0
- package/get-shit-done/references/thinking-models-research.md +50 -0
- package/get-shit-done/references/thinking-models-verification.md +55 -0
- package/get-shit-done/references/thinking-partner.md +96 -0
- package/get-shit-done/references/universal-anti-patterns.md +6 -1
- package/get-shit-done/references/verification-overrides.md +227 -0
- package/get-shit-done/templates/AI-SPEC.md +246 -0
- package/get-shit-done/workflows/add-tests.md +3 -0
- package/get-shit-done/workflows/add-todo.md +2 -0
- package/get-shit-done/workflows/ai-integration-phase.md +284 -0
- package/get-shit-done/workflows/audit-fix.md +154 -0
- package/get-shit-done/workflows/autonomous.md +33 -2
- package/get-shit-done/workflows/check-todos.md +2 -0
- package/get-shit-done/workflows/cleanup.md +2 -0
- package/get-shit-done/workflows/code-review-fix.md +497 -0
- package/get-shit-done/workflows/code-review.md +515 -0
- package/get-shit-done/workflows/complete-milestone.md +40 -15
- package/get-shit-done/workflows/diagnose-issues.md +1 -1
- package/get-shit-done/workflows/discovery-phase.md +3 -1
- package/get-shit-done/workflows/discuss-phase-assumptions.md +1 -1
- package/get-shit-done/workflows/discuss-phase.md +21 -7
- package/get-shit-done/workflows/do.md +2 -0
- package/get-shit-done/workflows/docs-update.md +2 -0
- package/get-shit-done/workflows/eval-review.md +155 -0
- package/get-shit-done/workflows/execute-phase.md +307 -57
- package/get-shit-done/workflows/execute-plan.md +64 -93
- package/get-shit-done/workflows/explore.md +136 -0
- package/get-shit-done/workflows/help.md +1 -1
- package/get-shit-done/workflows/import.md +273 -0
- package/get-shit-done/workflows/inbox.md +387 -0
- package/get-shit-done/workflows/manager.md +4 -10
- package/get-shit-done/workflows/new-milestone.md +3 -1
- package/get-shit-done/workflows/new-project.md +2 -0
- package/get-shit-done/workflows/new-workspace.md +2 -0
- package/get-shit-done/workflows/next.md +56 -0
- package/get-shit-done/workflows/note.md +2 -0
- package/get-shit-done/workflows/plan-phase.md +97 -17
- package/get-shit-done/workflows/plant-seed.md +3 -0
- package/get-shit-done/workflows/pr-branch.md +41 -13
- package/get-shit-done/workflows/profile-user.md +4 -2
- package/get-shit-done/workflows/quick.md +99 -4
- package/get-shit-done/workflows/remove-workspace.md +2 -0
- package/get-shit-done/workflows/review.md +53 -6
- package/get-shit-done/workflows/scan.md +98 -0
- package/get-shit-done/workflows/secure-phase.md +2 -0
- package/get-shit-done/workflows/settings.md +18 -3
- package/get-shit-done/workflows/ship.md +3 -0
- package/get-shit-done/workflows/ui-phase.md +10 -2
- package/get-shit-done/workflows/ui-review.md +2 -0
- package/get-shit-done/workflows/undo.md +314 -0
- package/get-shit-done/workflows/update.md +2 -0
- package/get-shit-done/workflows/validate-phase.md +2 -0
- package/get-shit-done/workflows/verify-phase.md +83 -0
- package/get-shit-done/workflows/verify-work.md +12 -1
- package/package.json +1 -1
- package/skills/gsd-code-review/SKILL.md +48 -0
- package/skills/gsd-code-review-fix/SKILL.md +44 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: gsd-undo
|
|
3
|
+
description: "Safe git revert. Roll back phase or plan commits using the phase manifest with dependency checks."
|
|
4
|
+
argument-hint: "--last N | --phase NN | --plan NN-MM"
|
|
5
|
+
permissions:
|
|
6
|
+
read: true
|
|
7
|
+
bash: true
|
|
8
|
+
glob: true
|
|
9
|
+
grep: true
|
|
10
|
+
question: true
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
<objective>
|
|
14
|
+
Safe git revert — roll back GSD phase or plan commits using the phase manifest, with dependency checks and a confirmation gate before execution.
|
|
15
|
+
|
|
16
|
+
Three modes:
|
|
17
|
+
- **--last N**: Show recent GSD commits for interactive selection
|
|
18
|
+
- **--phase NN**: Revert all commits for a phase (manifest + git log fallback)
|
|
19
|
+
- **--plan NN-MM**: Revert all commits for a specific plan
|
|
20
|
+
</objective>
|
|
21
|
+
|
|
22
|
+
<execution_context>
|
|
23
|
+
@$HOME/.config/opencode/get-shit-done/workflows/undo.md
|
|
24
|
+
@$HOME/.config/opencode/get-shit-done/references/ui-brand.md
|
|
25
|
+
@$HOME/.config/opencode/get-shit-done/references/gate-prompts.md
|
|
26
|
+
</execution_context>
|
|
27
|
+
|
|
28
|
+
<context>
|
|
29
|
+
$ARGUMENTS
|
|
30
|
+
</context>
|
|
31
|
+
|
|
32
|
+
<process>
|
|
33
|
+
Execute the undo workflow from @$HOME/.config/opencode/get-shit-done/workflows/undo.md end-to-end.
|
|
34
|
+
</process>
|
|
@@ -36,30 +36,30 @@ If no subcommand given, default to `list`.
|
|
|
36
36
|
## Step 2: Execute Operation
|
|
37
37
|
|
|
38
38
|
### list
|
|
39
|
-
Run: `node "$
|
|
39
|
+
Run: `node "$HOME/.config/opencode/get-shit-done/bin/gsd-tools.cjs" workstream list --raw --cwd "$CWD"`
|
|
40
40
|
Display the workstreams in a table format showing name, status, current phase, and progress.
|
|
41
41
|
|
|
42
42
|
### create
|
|
43
|
-
Run: `node "$
|
|
43
|
+
Run: `node "$HOME/.config/opencode/get-shit-done/bin/gsd-tools.cjs" workstream create <name> --raw --cwd "$CWD"`
|
|
44
44
|
After creation, display the new workstream path and suggest next steps:
|
|
45
45
|
- `/gsd-new-milestone --ws <name>` to set up the milestone
|
|
46
46
|
|
|
47
47
|
### status
|
|
48
|
-
Run: `node "$
|
|
48
|
+
Run: `node "$HOME/.config/opencode/get-shit-done/bin/gsd-tools.cjs" workstream status <name> --raw --cwd "$CWD"`
|
|
49
49
|
Display detailed phase breakdown and state information.
|
|
50
50
|
|
|
51
51
|
### switch
|
|
52
|
-
Run: `node "$
|
|
52
|
+
Run: `node "$HOME/.config/opencode/get-shit-done/bin/gsd-tools.cjs" workstream set <name> --raw --cwd "$CWD"`
|
|
53
53
|
Also set `GSD_WORKSTREAM` for the current session when the runtime supports it.
|
|
54
54
|
If the runtime exposes a session identifier, GSD also stores the active workstream
|
|
55
55
|
session-locally so concurrent sessions do not overwrite each other.
|
|
56
56
|
|
|
57
57
|
### progress
|
|
58
|
-
Run: `node "$
|
|
58
|
+
Run: `node "$HOME/.config/opencode/get-shit-done/bin/gsd-tools.cjs" workstream progress --raw --cwd "$CWD"`
|
|
59
59
|
Display a progress overview across all workstreams.
|
|
60
60
|
|
|
61
61
|
### complete
|
|
62
|
-
Run: `node "$
|
|
62
|
+
Run: `node "$HOME/.config/opencode/get-shit-done/bin/gsd-tools.cjs" workstream complete <name> --raw --cwd "$CWD"`
|
|
63
63
|
Archive the workstream to milestones/.
|
|
64
64
|
|
|
65
65
|
### resume
|
|
@@ -70,6 +70,16 @@
|
|
|
70
70
|
* audit-uat Scan all phases for unresolved UAT/verification items
|
|
71
71
|
* uat render-checkpoint --file <path> Render the current UAT checkpoint block
|
|
72
72
|
*
|
|
73
|
+
* Intel:
|
|
74
|
+
* intel query <term> Query intel files for a term
|
|
75
|
+
* intel status Show intel file freshness
|
|
76
|
+
* intel update Trigger intel refresh (returns agent spawn hint)
|
|
77
|
+
* intel diff Show changed intel entries since last snapshot
|
|
78
|
+
* intel snapshot Save current intel state as diff baseline
|
|
79
|
+
* intel patch-meta <file> Update _meta.updated_at in an intel file
|
|
80
|
+
* intel validate Validate intel file structure
|
|
81
|
+
* intel extract-exports <file> Extract exported symbols from a source file
|
|
82
|
+
*
|
|
73
83
|
* Scaffolding:
|
|
74
84
|
* scaffold context --phase <N> Create CONTEXT.md template
|
|
75
85
|
* scaffold uat --phase <N> Create UAT.md template
|
|
@@ -137,6 +147,17 @@
|
|
|
137
147
|
*
|
|
138
148
|
* Documentation:
|
|
139
149
|
* docs-init Project context for docs-update workflow
|
|
150
|
+
*
|
|
151
|
+
* Learnings:
|
|
152
|
+
* learnings list List all global learnings (JSON)
|
|
153
|
+
* learnings query --tag <tag> Query learnings by tag
|
|
154
|
+
* learnings copy Copy from current project's LEARNINGS.md
|
|
155
|
+
* learnings prune --older-than <dur> Remove entries older than duration (e.g. 90d)
|
|
156
|
+
* learnings delete <id> Delete a learning by ID
|
|
157
|
+
*
|
|
158
|
+
* GSD-2 Migration:
|
|
159
|
+
* from-gsd2 [--path <dir>] [--force] [--dry-run]
|
|
160
|
+
* Import a GSD-2 (.gsd/) project back to GSD v1 (.planning/) format
|
|
140
161
|
*/
|
|
141
162
|
|
|
142
163
|
const fs = require('fs');
|
|
@@ -157,6 +178,7 @@ const profilePipeline = require('./lib/profile-pipeline.cjs');
|
|
|
157
178
|
const profileOutput = require('./lib/profile-output.cjs');
|
|
158
179
|
const workstream = require('./lib/workstream.cjs');
|
|
159
180
|
const docs = require('./lib/docs.cjs');
|
|
181
|
+
const learnings = require('./lib/learnings.cjs');
|
|
160
182
|
|
|
161
183
|
// ─── Arg parsing helpers ──────────────────────────────────────────────────────
|
|
162
184
|
|
|
@@ -276,12 +298,33 @@ async function main() {
|
|
|
276
298
|
args.splice(pickIdx, 2);
|
|
277
299
|
}
|
|
278
300
|
|
|
301
|
+
// --default <value>: for config-get, return this value instead of erroring
|
|
302
|
+
// when the key is absent. Allows workflows to express optional config reads
|
|
303
|
+
// without defensive `2>/dev/null || true` boilerplate (#1893).
|
|
304
|
+
const defaultIdx = args.indexOf('--default');
|
|
305
|
+
let defaultValue = undefined;
|
|
306
|
+
if (defaultIdx !== -1) {
|
|
307
|
+
defaultValue = args[defaultIdx + 1];
|
|
308
|
+
if (defaultValue === undefined) defaultValue = '';
|
|
309
|
+
args.splice(defaultIdx, 2);
|
|
310
|
+
}
|
|
311
|
+
|
|
279
312
|
const command = args[0];
|
|
280
313
|
|
|
281
314
|
if (!command) {
|
|
282
315
|
error('Usage: gsd-tools <command> [args] [--raw] [--pick <field>] [--cwd <path>] [--ws <name>]\nCommands: state, resolve-model, find-phase, commit, verify-summary, verify, frontmatter, template, generate-slug, current-timestamp, list-todos, verify-path-exists, config-ensure-section, config-new-project, init, workstream, docs-init');
|
|
283
316
|
}
|
|
284
317
|
|
|
318
|
+
// Reject flags that are never valid for any gsd-tools command. AI agents
|
|
319
|
+
// sometimes hallucinate --help or --version on tool invocations; silently
|
|
320
|
+
// ignoring them can cause destructive operations to proceed unchecked.
|
|
321
|
+
const NEVER_VALID_FLAGS = new Set(['-h', '--help', '-?', '--h', '--version', '-v', '--usage']);
|
|
322
|
+
for (const arg of args) {
|
|
323
|
+
if (NEVER_VALID_FLAGS.has(arg)) {
|
|
324
|
+
error(`Unknown flag: ${arg}\ngsd-tools does not accept help or version flags. Run "gsd-tools" with no arguments for usage.`);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
285
328
|
// Multi-repo guard: resolve project root for commands that read/write .planning/.
|
|
286
329
|
// Skip for pure-utility commands that don't touch .planning/ to avoid unnecessary
|
|
287
330
|
// filesystem traversal on every invocation.
|
|
@@ -318,7 +361,7 @@ async function main() {
|
|
|
318
361
|
}
|
|
319
362
|
};
|
|
320
363
|
try {
|
|
321
|
-
await runCommand(command, args, cwd, raw);
|
|
364
|
+
await runCommand(command, args, cwd, raw, defaultValue);
|
|
322
365
|
cleanup();
|
|
323
366
|
} catch (e) {
|
|
324
367
|
fs.writeSync = origWriteSync;
|
|
@@ -327,7 +370,27 @@ async function main() {
|
|
|
327
370
|
return;
|
|
328
371
|
}
|
|
329
372
|
|
|
330
|
-
|
|
373
|
+
// Intercept stdout to transparently resolve @file: references (#1891).
|
|
374
|
+
// core.cjs output() writes @file:<path> when JSON > 50KB. The --pick path
|
|
375
|
+
// already resolves this, but the normal path wrote @file: to stdout, forcing
|
|
376
|
+
// every workflow to have a bash-specific `if [[ "$INIT" == @file:* ]]` check
|
|
377
|
+
// that breaks on PowerShell and other non-bash shells.
|
|
378
|
+
const origWriteSync2 = fs.writeSync;
|
|
379
|
+
const outChunks = [];
|
|
380
|
+
fs.writeSync = function (fd, data, ...rest) {
|
|
381
|
+
if (fd === 1) { outChunks.push(String(data)); return; }
|
|
382
|
+
return origWriteSync2.call(fs, fd, data, ...rest);
|
|
383
|
+
};
|
|
384
|
+
try {
|
|
385
|
+
await runCommand(command, args, cwd, raw, defaultValue);
|
|
386
|
+
} finally {
|
|
387
|
+
fs.writeSync = origWriteSync2;
|
|
388
|
+
}
|
|
389
|
+
let captured = outChunks.join('');
|
|
390
|
+
if (captured.startsWith('@file:')) {
|
|
391
|
+
captured = fs.readFileSync(captured.slice(6), 'utf-8');
|
|
392
|
+
}
|
|
393
|
+
origWriteSync2.call(fs, 1, captured);
|
|
331
394
|
}
|
|
332
395
|
|
|
333
396
|
/**
|
|
@@ -353,7 +416,7 @@ function extractField(obj, fieldPath) {
|
|
|
353
416
|
return current;
|
|
354
417
|
}
|
|
355
418
|
|
|
356
|
-
async function runCommand(command, args, cwd, raw) {
|
|
419
|
+
async function runCommand(command, args, cwd, raw, defaultValue) {
|
|
357
420
|
switch (command) {
|
|
358
421
|
case 'state': {
|
|
359
422
|
const subcommand = args[1];
|
|
@@ -561,7 +624,7 @@ async function runCommand(command, args, cwd, raw) {
|
|
|
561
624
|
}
|
|
562
625
|
|
|
563
626
|
case 'config-get': {
|
|
564
|
-
config.cmdConfigGet(cwd, args[1], raw);
|
|
627
|
+
config.cmdConfigGet(cwd, args[1], raw, defaultValue);
|
|
565
628
|
break;
|
|
566
629
|
}
|
|
567
630
|
|
|
@@ -592,7 +655,7 @@ async function runCommand(command, args, cwd, raw) {
|
|
|
592
655
|
};
|
|
593
656
|
phase.cmdPhasesList(cwd, options, raw);
|
|
594
657
|
} else if (subcommand === 'clear') {
|
|
595
|
-
milestone.cmdPhasesClear(cwd, raw);
|
|
658
|
+
milestone.cmdPhasesClear(cwd, raw, args.slice(2));
|
|
596
659
|
} else {
|
|
597
660
|
error('Unknown phases subcommand. Available: list, clear');
|
|
598
661
|
}
|
|
@@ -937,6 +1000,45 @@ async function runCommand(command, args, cwd, raw) {
|
|
|
937
1000
|
break;
|
|
938
1001
|
}
|
|
939
1002
|
|
|
1003
|
+
// ─── Intel ────────────────────────────────────────────────────────────
|
|
1004
|
+
|
|
1005
|
+
case 'intel': {
|
|
1006
|
+
const intel = require('./lib/intel.cjs');
|
|
1007
|
+
const subcommand = args[1];
|
|
1008
|
+
if (subcommand === 'query') {
|
|
1009
|
+
const term = args[2];
|
|
1010
|
+
if (!term) error('Usage: gsd-tools intel query <term>');
|
|
1011
|
+
const planningDir = path.join(cwd, '.planning');
|
|
1012
|
+
core.output(intel.intelQuery(term, planningDir), raw);
|
|
1013
|
+
} else if (subcommand === 'status') {
|
|
1014
|
+
const planningDir = path.join(cwd, '.planning');
|
|
1015
|
+
core.output(intel.intelStatus(planningDir), raw);
|
|
1016
|
+
} else if (subcommand === 'diff') {
|
|
1017
|
+
const planningDir = path.join(cwd, '.planning');
|
|
1018
|
+
core.output(intel.intelDiff(planningDir), raw);
|
|
1019
|
+
} else if (subcommand === 'snapshot') {
|
|
1020
|
+
const planningDir = path.join(cwd, '.planning');
|
|
1021
|
+
core.output(intel.intelSnapshot(planningDir), raw);
|
|
1022
|
+
} else if (subcommand === 'patch-meta') {
|
|
1023
|
+
const filePath = args[2];
|
|
1024
|
+
if (!filePath) error('Usage: gsd-tools intel patch-meta <file-path>');
|
|
1025
|
+
core.output(intel.intelPatchMeta(path.resolve(cwd, filePath)), raw);
|
|
1026
|
+
} else if (subcommand === 'validate') {
|
|
1027
|
+
const planningDir = path.join(cwd, '.planning');
|
|
1028
|
+
core.output(intel.intelValidate(planningDir), raw);
|
|
1029
|
+
} else if (subcommand === 'extract-exports') {
|
|
1030
|
+
const filePath = args[2];
|
|
1031
|
+
if (!filePath) error('Usage: gsd-tools intel extract-exports <file-path>');
|
|
1032
|
+
core.output(intel.intelExtractExports(path.resolve(cwd, filePath)), raw);
|
|
1033
|
+
} else if (subcommand === 'update') {
|
|
1034
|
+
const planningDir = path.join(cwd, '.planning');
|
|
1035
|
+
core.output(intel.intelUpdate(planningDir), raw);
|
|
1036
|
+
} else {
|
|
1037
|
+
error('Unknown intel subcommand. Available: query, status, update, diff, snapshot, patch-meta, validate, extract-exports');
|
|
1038
|
+
}
|
|
1039
|
+
break;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
940
1042
|
// ─── Documentation ────────────────────────────────────────────────────
|
|
941
1043
|
|
|
942
1044
|
case 'docs-init': {
|
|
@@ -944,6 +1046,42 @@ async function runCommand(command, args, cwd, raw) {
|
|
|
944
1046
|
break;
|
|
945
1047
|
}
|
|
946
1048
|
|
|
1049
|
+
// ─── Learnings ─────────────────────────────────────────────────────────
|
|
1050
|
+
|
|
1051
|
+
case 'learnings': {
|
|
1052
|
+
const subcommand = args[1];
|
|
1053
|
+
if (subcommand === 'list') {
|
|
1054
|
+
learnings.cmdLearningsList(raw);
|
|
1055
|
+
} else if (subcommand === 'query') {
|
|
1056
|
+
const tagIdx = args.indexOf('--tag');
|
|
1057
|
+
const tag = tagIdx !== -1 ? args[tagIdx + 1] : null;
|
|
1058
|
+
if (!tag) error('Usage: gsd-tools learnings query --tag <tag>');
|
|
1059
|
+
learnings.cmdLearningsQuery(tag, raw);
|
|
1060
|
+
} else if (subcommand === 'copy') {
|
|
1061
|
+
learnings.cmdLearningsCopy(cwd, raw);
|
|
1062
|
+
} else if (subcommand === 'prune') {
|
|
1063
|
+
const olderIdx = args.indexOf('--older-than');
|
|
1064
|
+
const olderThan = olderIdx !== -1 ? args[olderIdx + 1] : null;
|
|
1065
|
+
if (!olderThan) error('Usage: gsd-tools learnings prune --older-than <duration>');
|
|
1066
|
+
learnings.cmdLearningsPrune(olderThan, raw);
|
|
1067
|
+
} else if (subcommand === 'delete') {
|
|
1068
|
+
const id = args[2];
|
|
1069
|
+
if (!id) error('Usage: gsd-tools learnings delete <id>');
|
|
1070
|
+
learnings.cmdLearningsDelete(id, raw);
|
|
1071
|
+
} else {
|
|
1072
|
+
error('Unknown learnings subcommand. Available: list, query, copy, prune, delete');
|
|
1073
|
+
}
|
|
1074
|
+
break;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
// ─── GSD-2 Reverse Migration ───────────────────────────────────────────
|
|
1078
|
+
|
|
1079
|
+
case 'from-gsd2': {
|
|
1080
|
+
const gsd2Import = require('./lib/gsd2-import.cjs');
|
|
1081
|
+
gsd2Import.cmdFromGsd2(args.slice(1), cwd, raw);
|
|
1082
|
+
break;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
947
1085
|
default:
|
|
948
1086
|
error(`Unknown command: ${command}`);
|
|
949
1087
|
}
|
|
@@ -313,11 +313,19 @@ function cmdCommit(cwd, message, files, raw, amend, noVerify) {
|
|
|
313
313
|
}
|
|
314
314
|
|
|
315
315
|
// Stage files
|
|
316
|
-
const
|
|
316
|
+
const explicitFiles = files && files.length > 0;
|
|
317
|
+
const filesToStage = explicitFiles ? files : ['.planning/'];
|
|
317
318
|
for (const file of filesToStage) {
|
|
318
319
|
const fullPath = path.join(cwd, file);
|
|
319
320
|
if (!fs.existsSync(fullPath)) {
|
|
320
|
-
|
|
321
|
+
if (explicitFiles) {
|
|
322
|
+
// Caller passed an explicit --files list: missing files are skipped.
|
|
323
|
+
// Staging a deletion here would silently remove tracked planning files
|
|
324
|
+
// (e.g. STATE.md, ROADMAP.md) when they are temporarily absent (#2014).
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
// Default mode (staging all of .planning/): stage the deletion so
|
|
328
|
+
// removed planning files are not left dangling in the index.
|
|
321
329
|
execGit(cwd, ['rm', '--cached', '--ignore-unmatch', file]);
|
|
322
330
|
} else {
|
|
323
331
|
execGit(cwd, ['add', file]);
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
const fs = require('fs');
|
|
6
6
|
const path = require('path');
|
|
7
|
-
const { output, error,
|
|
7
|
+
const { output, error, planningDir, withPlanningLock, CONFIG_DEFAULTS, atomicWriteFileSync } = require('./core.cjs');
|
|
8
8
|
const {
|
|
9
9
|
VALID_PROFILES,
|
|
10
10
|
getAgentToModelMapForProfile,
|
|
@@ -15,7 +15,7 @@ const VALID_CONFIG_KEYS = new Set([
|
|
|
15
15
|
'mode', 'granularity', 'parallelization', 'commit_docs', 'model_profile',
|
|
16
16
|
'search_gitignored', 'brave_search', 'firecrawl', 'exa_search',
|
|
17
17
|
'workflow.research', 'workflow.plan_check', 'workflow.verifier',
|
|
18
|
-
'workflow.nyquist_validation', 'workflow.ui_phase', 'workflow.ui_safety_gate',
|
|
18
|
+
'workflow.nyquist_validation', 'workflow.ai_integration_phase', 'workflow.ui_phase', 'workflow.ui_safety_gate',
|
|
19
19
|
'workflow.auto_advance', 'workflow.node_repair', 'workflow.node_repair_budget',
|
|
20
20
|
'workflow.text_mode',
|
|
21
21
|
'workflow.research_before_questions',
|
|
@@ -23,13 +23,20 @@ const VALID_CONFIG_KEYS = new Set([
|
|
|
23
23
|
'workflow.skip_discuss',
|
|
24
24
|
'workflow._auto_chain_active',
|
|
25
25
|
'workflow.use_worktrees',
|
|
26
|
+
'workflow.code_review',
|
|
27
|
+
'workflow.code_review_depth',
|
|
26
28
|
'git.branching_strategy', 'git.base_branch', 'git.phase_branch_template', 'git.milestone_branch_template', 'git.quick_branch_template',
|
|
27
29
|
'planning.commit_docs', 'planning.search_gitignored',
|
|
28
30
|
'workflow.subagent_timeout',
|
|
29
31
|
'hooks.context_warnings',
|
|
32
|
+
'features.thinking_partner',
|
|
33
|
+
'context',
|
|
34
|
+
'features.global_learnings',
|
|
35
|
+
'learnings.max_inject',
|
|
30
36
|
'project_code', 'phase_naming',
|
|
31
37
|
'manager.flags.discuss', 'manager.flags.plan', 'manager.flags.execute',
|
|
32
38
|
'response_language',
|
|
39
|
+
'intel.enabled',
|
|
33
40
|
]);
|
|
34
41
|
|
|
35
42
|
/**
|
|
@@ -41,6 +48,12 @@ function isValidConfigKey(keyPath) {
|
|
|
41
48
|
if (VALID_CONFIG_KEYS.has(keyPath)) return true;
|
|
42
49
|
// Allow agent_skills.<agent-type> with any agent type string
|
|
43
50
|
if (/^agent_skills\.[a-zA-Z0-9_-]+$/.test(keyPath)) return true;
|
|
51
|
+
// Allow review.models.<cli-name> for per-CLI model selection in /gsd-review
|
|
52
|
+
if (/^review\.models\.[a-zA-Z0-9_-]+$/.test(keyPath)) return true;
|
|
53
|
+
// Allow features.<feature_name> — dynamic namespace for feature flags.
|
|
54
|
+
// Intentionally open-ended so new flags (e.g., features.global_learnings) work
|
|
55
|
+
// without updating VALID_CONFIG_KEYS each time.
|
|
56
|
+
if (/^features\.[a-zA-Z0-9_]+$/.test(keyPath)) return true;
|
|
44
57
|
return false;
|
|
45
58
|
}
|
|
46
59
|
|
|
@@ -50,6 +63,11 @@ const CONFIG_KEY_SUGGESTIONS = {
|
|
|
50
63
|
'nyquist.validation_enabled': 'workflow.nyquist_validation',
|
|
51
64
|
'hooks.research_questions': 'workflow.research_before_questions',
|
|
52
65
|
'workflow.research_questions': 'workflow.research_before_questions',
|
|
66
|
+
'workflow.codereview': 'workflow.code_review',
|
|
67
|
+
'workflow.review': 'workflow.code_review',
|
|
68
|
+
'workflow.code_review_level': 'workflow.code_review_depth',
|
|
69
|
+
'workflow.review_depth': 'workflow.code_review_depth',
|
|
70
|
+
'review.model': 'review.models.<cli-name>',
|
|
53
71
|
};
|
|
54
72
|
|
|
55
73
|
function validateKnownConfigKeyPath(keyPath) {
|
|
@@ -129,10 +147,13 @@ function buildNewProjectConfig(userChoices) {
|
|
|
129
147
|
node_repair_budget: 2,
|
|
130
148
|
ui_phase: true,
|
|
131
149
|
ui_safety_gate: true,
|
|
150
|
+
ai_integration_phase: true,
|
|
132
151
|
text_mode: false,
|
|
133
152
|
research_before_questions: false,
|
|
134
153
|
discuss_mode: 'discuss',
|
|
135
154
|
skip_discuss: false,
|
|
155
|
+
code_review: true,
|
|
156
|
+
code_review_depth: 'standard',
|
|
136
157
|
},
|
|
137
158
|
hooks: {
|
|
138
159
|
context_warnings: true,
|
|
@@ -180,7 +201,7 @@ function buildNewProjectConfig(userChoices) {
|
|
|
180
201
|
* Idempotent: if config.json already exists, returns { created: false }.
|
|
181
202
|
*/
|
|
182
203
|
function cmdConfigNewProject(cwd, choicesJson, raw) {
|
|
183
|
-
const planningBase =
|
|
204
|
+
const planningBase = planningDir(cwd);
|
|
184
205
|
const configPath = path.join(planningBase, 'config.json');
|
|
185
206
|
|
|
186
207
|
// Idempotent: don't overwrite existing config
|
|
@@ -211,7 +232,7 @@ function cmdConfigNewProject(cwd, choicesJson, raw) {
|
|
|
211
232
|
const config = buildNewProjectConfig(userChoices);
|
|
212
233
|
|
|
213
234
|
try {
|
|
214
|
-
|
|
235
|
+
atomicWriteFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
|
|
215
236
|
output({ created: true, path: '.planning/config.json' }, raw, 'created');
|
|
216
237
|
} catch (err) {
|
|
217
238
|
error('Failed to write config.json: ' + err.message);
|
|
@@ -225,7 +246,7 @@ function cmdConfigNewProject(cwd, choicesJson, raw) {
|
|
|
225
246
|
* the happy path. But note that `error()` will still `exit(1)` out of the process.
|
|
226
247
|
*/
|
|
227
248
|
function ensureConfigFile(cwd) {
|
|
228
|
-
const planningBase =
|
|
249
|
+
const planningBase = planningDir(cwd);
|
|
229
250
|
const configPath = path.join(planningBase, 'config.json');
|
|
230
251
|
|
|
231
252
|
// Ensure .planning directory exists
|
|
@@ -245,7 +266,7 @@ function ensureConfigFile(cwd) {
|
|
|
245
266
|
const config = buildNewProjectConfig({});
|
|
246
267
|
|
|
247
268
|
try {
|
|
248
|
-
|
|
269
|
+
atomicWriteFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
|
|
249
270
|
return { created: true, path: '.planning/config.json' };
|
|
250
271
|
} catch (err) {
|
|
251
272
|
error('Failed to create config.json: ' + err.message);
|
|
@@ -275,38 +296,40 @@ function cmdConfigEnsureSection(cwd, raw) {
|
|
|
275
296
|
* the happy path. But note that `error()` will still `exit(1)` out of the process.
|
|
276
297
|
*/
|
|
277
298
|
function setConfigValue(cwd, keyPath, parsedValue) {
|
|
278
|
-
const configPath = path.join(
|
|
299
|
+
const configPath = path.join(planningDir(cwd), 'config.json');
|
|
279
300
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
301
|
+
return withPlanningLock(cwd, () => {
|
|
302
|
+
// Load existing config or start with empty object
|
|
303
|
+
let config = {};
|
|
304
|
+
try {
|
|
305
|
+
if (fs.existsSync(configPath)) {
|
|
306
|
+
config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
307
|
+
}
|
|
308
|
+
} catch (err) {
|
|
309
|
+
error('Failed to read config.json: ' + err.message);
|
|
285
310
|
}
|
|
286
|
-
} catch (err) {
|
|
287
|
-
error('Failed to read config.json: ' + err.message);
|
|
288
|
-
}
|
|
289
311
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
312
|
+
// Set nested value using dot notation (e.g., "workflow.research")
|
|
313
|
+
const keys = keyPath.split('.');
|
|
314
|
+
let current = config;
|
|
315
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
316
|
+
const key = keys[i];
|
|
317
|
+
if (current[key] === undefined || typeof current[key] !== 'object') {
|
|
318
|
+
current[key] = {};
|
|
319
|
+
}
|
|
320
|
+
current = current[key];
|
|
297
321
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
const previousValue = current[keys[keys.length - 1]]; // Capture previous value before overwriting
|
|
301
|
-
current[keys[keys.length - 1]] = parsedValue;
|
|
322
|
+
const previousValue = current[keys[keys.length - 1]]; // Capture previous value before overwriting
|
|
323
|
+
current[keys[keys.length - 1]] = parsedValue;
|
|
302
324
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
325
|
+
// write back
|
|
326
|
+
try {
|
|
327
|
+
atomicWriteFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
|
|
328
|
+
return { updated: true, key: keyPath, value: parsedValue, previousValue };
|
|
329
|
+
} catch (err) {
|
|
330
|
+
error('Failed to write config.json: ' + err.message);
|
|
331
|
+
}
|
|
332
|
+
});
|
|
310
333
|
}
|
|
311
334
|
|
|
312
335
|
/**
|
|
@@ -324,7 +347,7 @@ function cmdConfigSet(cwd, keyPath, value, raw) {
|
|
|
324
347
|
validateKnownConfigKeyPath(keyPath);
|
|
325
348
|
|
|
326
349
|
if (!isValidConfigKey(keyPath)) {
|
|
327
|
-
error(`Unknown config key: "${keyPath}". Valid keys: ${[...VALID_CONFIG_KEYS].sort().join(', ')}, agent_skills.<agent-type>`);
|
|
350
|
+
error(`Unknown config key: "${keyPath}". Valid keys: ${[...VALID_CONFIG_KEYS].sort().join(', ')}, agent_skills.<agent-type>, features.<feature_name>`);
|
|
328
351
|
}
|
|
329
352
|
|
|
330
353
|
// Parse value (handle booleans, numbers, and JSON arrays/objects)
|
|
@@ -336,21 +359,30 @@ function cmdConfigSet(cwd, keyPath, value, raw) {
|
|
|
336
359
|
try { parsedValue = JSON.parse(value); } catch { /* keep as string */ }
|
|
337
360
|
}
|
|
338
361
|
|
|
362
|
+
const VALID_CONTEXT_VALUES = ['dev', 'research', 'review'];
|
|
363
|
+
if (keyPath === 'context' && !VALID_CONTEXT_VALUES.includes(String(parsedValue))) {
|
|
364
|
+
error(`Invalid context value '${value}'. Valid values: ${VALID_CONTEXT_VALUES.join(', ')}`);
|
|
365
|
+
}
|
|
366
|
+
|
|
339
367
|
const setConfigValueResult = setConfigValue(cwd, keyPath, parsedValue);
|
|
340
368
|
output(setConfigValueResult, raw, `${keyPath}=${parsedValue}`);
|
|
341
369
|
}
|
|
342
370
|
|
|
343
|
-
function cmdConfigGet(cwd, keyPath, raw) {
|
|
344
|
-
const configPath = path.join(
|
|
371
|
+
function cmdConfigGet(cwd, keyPath, raw, defaultValue) {
|
|
372
|
+
const configPath = path.join(planningDir(cwd), 'config.json');
|
|
373
|
+
const hasDefault = defaultValue !== undefined;
|
|
345
374
|
|
|
346
375
|
if (!keyPath) {
|
|
347
|
-
error('Usage: config-get <key.path>');
|
|
376
|
+
error('Usage: config-get <key.path> [--default <value>]');
|
|
348
377
|
}
|
|
349
378
|
|
|
350
379
|
let config = {};
|
|
351
380
|
try {
|
|
352
381
|
if (fs.existsSync(configPath)) {
|
|
353
382
|
config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
383
|
+
} else if (hasDefault) {
|
|
384
|
+
output(defaultValue, raw, String(defaultValue));
|
|
385
|
+
return;
|
|
354
386
|
} else {
|
|
355
387
|
error('No config.json found at ' + configPath);
|
|
356
388
|
}
|
|
@@ -364,12 +396,14 @@ function cmdConfigGet(cwd, keyPath, raw) {
|
|
|
364
396
|
let current = config;
|
|
365
397
|
for (const key of keys) {
|
|
366
398
|
if (current === undefined || current === null || typeof current !== 'object') {
|
|
399
|
+
if (hasDefault) { output(defaultValue, raw, String(defaultValue)); return; }
|
|
367
400
|
error(`Key not found: ${keyPath}`);
|
|
368
401
|
}
|
|
369
402
|
current = current[key];
|
|
370
403
|
}
|
|
371
404
|
|
|
372
405
|
if (current === undefined) {
|
|
406
|
+
if (hasDefault) { output(defaultValue, raw, String(defaultValue)); return; }
|
|
373
407
|
error(`Key not found: ${keyPath}`);
|
|
374
408
|
}
|
|
375
409
|
|