gsd-opencode 1.22.1 → 1.33.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.
Files changed (188) hide show
  1. package/agents/gsd-advisor-researcher.md +112 -0
  2. package/agents/gsd-assumptions-analyzer.md +110 -0
  3. package/agents/gsd-codebase-mapper.md +0 -2
  4. package/agents/gsd-debugger.md +117 -2
  5. package/agents/gsd-doc-verifier.md +207 -0
  6. package/agents/gsd-doc-writer.md +608 -0
  7. package/agents/gsd-executor.md +45 -4
  8. package/agents/gsd-integration-checker.md +0 -2
  9. package/agents/gsd-nyquist-auditor.md +0 -2
  10. package/agents/gsd-phase-researcher.md +191 -5
  11. package/agents/gsd-plan-checker.md +152 -5
  12. package/agents/gsd-planner.md +131 -157
  13. package/agents/gsd-project-researcher.md +28 -3
  14. package/agents/gsd-research-synthesizer.md +0 -2
  15. package/agents/gsd-roadmapper.md +29 -2
  16. package/agents/gsd-security-auditor.md +129 -0
  17. package/agents/gsd-ui-auditor.md +485 -0
  18. package/agents/gsd-ui-checker.md +305 -0
  19. package/agents/gsd-ui-researcher.md +368 -0
  20. package/agents/gsd-user-profiler.md +173 -0
  21. package/agents/gsd-verifier.md +207 -22
  22. package/commands/gsd/gsd-add-backlog.md +76 -0
  23. package/commands/gsd/gsd-analyze-dependencies.md +34 -0
  24. package/commands/gsd/gsd-audit-uat.md +24 -0
  25. package/commands/gsd/gsd-autonomous.md +45 -0
  26. package/commands/gsd/gsd-cleanup.md +5 -0
  27. package/commands/gsd/gsd-debug.md +29 -21
  28. package/commands/gsd/gsd-discuss-phase.md +15 -36
  29. package/commands/gsd/gsd-do.md +30 -0
  30. package/commands/gsd/gsd-docs-update.md +48 -0
  31. package/commands/gsd/gsd-execute-phase.md +24 -2
  32. package/commands/gsd/gsd-fast.md +30 -0
  33. package/commands/gsd/gsd-forensics.md +56 -0
  34. package/commands/gsd/gsd-help.md +2 -0
  35. package/commands/gsd/gsd-join-discord.md +2 -1
  36. package/commands/gsd/gsd-list-workspaces.md +19 -0
  37. package/commands/gsd/gsd-manager.md +40 -0
  38. package/commands/gsd/gsd-milestone-summary.md +51 -0
  39. package/commands/gsd/gsd-new-project.md +4 -0
  40. package/commands/gsd/gsd-new-workspace.md +44 -0
  41. package/commands/gsd/gsd-next.md +24 -0
  42. package/commands/gsd/gsd-note.md +34 -0
  43. package/commands/gsd/gsd-plan-phase.md +8 -1
  44. package/commands/gsd/gsd-plant-seed.md +28 -0
  45. package/commands/gsd/gsd-pr-branch.md +25 -0
  46. package/commands/gsd/gsd-profile-user.md +46 -0
  47. package/commands/gsd/gsd-quick.md +7 -3
  48. package/commands/gsd/gsd-reapply-patches.md +178 -45
  49. package/commands/gsd/gsd-remove-workspace.md +26 -0
  50. package/commands/gsd/gsd-research-phase.md +7 -12
  51. package/commands/gsd/gsd-review-backlog.md +62 -0
  52. package/commands/gsd/gsd-review.md +38 -0
  53. package/commands/gsd/gsd-secure-phase.md +35 -0
  54. package/commands/gsd/gsd-session-report.md +19 -0
  55. package/commands/gsd/gsd-set-profile.md +24 -23
  56. package/commands/gsd/gsd-ship.md +23 -0
  57. package/commands/gsd/gsd-stats.md +18 -0
  58. package/commands/gsd/gsd-thread.md +127 -0
  59. package/commands/gsd/gsd-ui-phase.md +34 -0
  60. package/commands/gsd/gsd-ui-review.md +32 -0
  61. package/commands/gsd/gsd-workstreams.md +71 -0
  62. package/get-shit-done/bin/gsd-tools.cjs +450 -90
  63. package/get-shit-done/bin/lib/commands.cjs +489 -24
  64. package/get-shit-done/bin/lib/config.cjs +329 -48
  65. package/get-shit-done/bin/lib/core.cjs +1143 -102
  66. package/get-shit-done/bin/lib/docs.cjs +267 -0
  67. package/get-shit-done/bin/lib/frontmatter.cjs +125 -43
  68. package/get-shit-done/bin/lib/init.cjs +918 -106
  69. package/get-shit-done/bin/lib/milestone.cjs +65 -33
  70. package/get-shit-done/bin/lib/model-profiles.cjs +70 -0
  71. package/get-shit-done/bin/lib/phase.cjs +434 -404
  72. package/get-shit-done/bin/lib/profile-output.cjs +1048 -0
  73. package/get-shit-done/bin/lib/profile-pipeline.cjs +539 -0
  74. package/get-shit-done/bin/lib/roadmap.cjs +156 -101
  75. package/get-shit-done/bin/lib/schema-detect.cjs +238 -0
  76. package/get-shit-done/bin/lib/security.cjs +384 -0
  77. package/get-shit-done/bin/lib/state.cjs +711 -79
  78. package/get-shit-done/bin/lib/template.cjs +2 -2
  79. package/get-shit-done/bin/lib/uat.cjs +282 -0
  80. package/get-shit-done/bin/lib/verify.cjs +254 -42
  81. package/get-shit-done/bin/lib/workstream.cjs +495 -0
  82. package/get-shit-done/references/agent-contracts.md +79 -0
  83. package/get-shit-done/references/artifact-types.md +113 -0
  84. package/get-shit-done/references/checkpoints.md +12 -10
  85. package/get-shit-done/references/context-budget.md +49 -0
  86. package/get-shit-done/references/continuation-format.md +15 -15
  87. package/get-shit-done/references/decimal-phase-calculation.md +2 -3
  88. package/get-shit-done/references/domain-probes.md +125 -0
  89. package/get-shit-done/references/gate-prompts.md +100 -0
  90. package/get-shit-done/references/git-integration.md +47 -0
  91. package/get-shit-done/references/model-profile-resolution.md +2 -0
  92. package/get-shit-done/references/model-profiles.md +62 -16
  93. package/get-shit-done/references/phase-argument-parsing.md +2 -2
  94. package/get-shit-done/references/planner-gap-closure.md +62 -0
  95. package/get-shit-done/references/planner-reviews.md +39 -0
  96. package/get-shit-done/references/planner-revision.md +87 -0
  97. package/get-shit-done/references/planning-config.md +18 -1
  98. package/get-shit-done/references/revision-loop.md +97 -0
  99. package/get-shit-done/references/ui-brand.md +2 -2
  100. package/get-shit-done/references/universal-anti-patterns.md +58 -0
  101. package/get-shit-done/references/user-profiling.md +681 -0
  102. package/get-shit-done/references/workstream-flag.md +111 -0
  103. package/get-shit-done/templates/SECURITY.md +61 -0
  104. package/get-shit-done/templates/UAT.md +21 -3
  105. package/get-shit-done/templates/UI-SPEC.md +100 -0
  106. package/get-shit-done/templates/VALIDATION.md +3 -3
  107. package/get-shit-done/templates/claude-md.md +145 -0
  108. package/get-shit-done/templates/config.json +14 -3
  109. package/get-shit-done/templates/context.md +61 -6
  110. package/get-shit-done/templates/debug-subagent-prompt.md +2 -6
  111. package/get-shit-done/templates/dev-preferences.md +21 -0
  112. package/get-shit-done/templates/discussion-log.md +63 -0
  113. package/get-shit-done/templates/phase-prompt.md +46 -5
  114. package/get-shit-done/templates/planner-subagent-prompt.md +2 -10
  115. package/get-shit-done/templates/project.md +2 -0
  116. package/get-shit-done/templates/state.md +2 -2
  117. package/get-shit-done/templates/user-profile.md +146 -0
  118. package/get-shit-done/workflows/add-phase.md +4 -4
  119. package/get-shit-done/workflows/add-tests.md +4 -4
  120. package/get-shit-done/workflows/add-todo.md +4 -4
  121. package/get-shit-done/workflows/analyze-dependencies.md +96 -0
  122. package/get-shit-done/workflows/audit-milestone.md +20 -16
  123. package/get-shit-done/workflows/audit-uat.md +109 -0
  124. package/get-shit-done/workflows/autonomous.md +1036 -0
  125. package/get-shit-done/workflows/check-todos.md +4 -4
  126. package/get-shit-done/workflows/cleanup.md +4 -4
  127. package/get-shit-done/workflows/complete-milestone.md +22 -10
  128. package/get-shit-done/workflows/diagnose-issues.md +21 -7
  129. package/get-shit-done/workflows/discovery-phase.md +2 -2
  130. package/get-shit-done/workflows/discuss-phase-assumptions.md +671 -0
  131. package/get-shit-done/workflows/discuss-phase-power.md +291 -0
  132. package/get-shit-done/workflows/discuss-phase.md +558 -47
  133. package/get-shit-done/workflows/do.md +104 -0
  134. package/get-shit-done/workflows/docs-update.md +1093 -0
  135. package/get-shit-done/workflows/execute-phase.md +741 -58
  136. package/get-shit-done/workflows/execute-plan.md +77 -12
  137. package/get-shit-done/workflows/fast.md +105 -0
  138. package/get-shit-done/workflows/forensics.md +265 -0
  139. package/get-shit-done/workflows/health.md +28 -6
  140. package/get-shit-done/workflows/help.md +127 -7
  141. package/get-shit-done/workflows/insert-phase.md +4 -4
  142. package/get-shit-done/workflows/list-phase-assumptions.md +2 -2
  143. package/get-shit-done/workflows/list-workspaces.md +56 -0
  144. package/get-shit-done/workflows/manager.md +363 -0
  145. package/get-shit-done/workflows/map-codebase.md +83 -44
  146. package/get-shit-done/workflows/milestone-summary.md +223 -0
  147. package/get-shit-done/workflows/new-milestone.md +133 -25
  148. package/get-shit-done/workflows/new-project.md +216 -54
  149. package/get-shit-done/workflows/new-workspace.md +237 -0
  150. package/get-shit-done/workflows/next.md +97 -0
  151. package/get-shit-done/workflows/node-repair.md +92 -0
  152. package/get-shit-done/workflows/note.md +156 -0
  153. package/get-shit-done/workflows/pause-work.md +132 -15
  154. package/get-shit-done/workflows/plan-milestone-gaps.md +6 -7
  155. package/get-shit-done/workflows/plan-phase.md +513 -62
  156. package/get-shit-done/workflows/plant-seed.md +169 -0
  157. package/get-shit-done/workflows/pr-branch.md +129 -0
  158. package/get-shit-done/workflows/profile-user.md +450 -0
  159. package/get-shit-done/workflows/progress.md +154 -29
  160. package/get-shit-done/workflows/quick.md +285 -111
  161. package/get-shit-done/workflows/remove-phase.md +2 -2
  162. package/get-shit-done/workflows/remove-workspace.md +90 -0
  163. package/get-shit-done/workflows/research-phase.md +13 -9
  164. package/get-shit-done/workflows/resume-project.md +37 -18
  165. package/get-shit-done/workflows/review.md +281 -0
  166. package/get-shit-done/workflows/secure-phase.md +154 -0
  167. package/get-shit-done/workflows/session-report.md +146 -0
  168. package/get-shit-done/workflows/set-profile.md +2 -2
  169. package/get-shit-done/workflows/settings.md +91 -11
  170. package/get-shit-done/workflows/ship.md +237 -0
  171. package/get-shit-done/workflows/stats.md +60 -0
  172. package/get-shit-done/workflows/transition.md +150 -23
  173. package/get-shit-done/workflows/ui-phase.md +292 -0
  174. package/get-shit-done/workflows/ui-review.md +183 -0
  175. package/get-shit-done/workflows/update.md +262 -30
  176. package/get-shit-done/workflows/validate-phase.md +14 -17
  177. package/get-shit-done/workflows/verify-phase.md +143 -11
  178. package/get-shit-done/workflows/verify-work.md +141 -39
  179. package/package.json +1 -1
  180. package/skills/gsd-audit-milestone/SKILL.md +29 -0
  181. package/skills/gsd-cleanup/SKILL.md +19 -0
  182. package/skills/gsd-complete-milestone/SKILL.md +131 -0
  183. package/skills/gsd-discuss-phase/SKILL.md +54 -0
  184. package/skills/gsd-execute-phase/SKILL.md +49 -0
  185. package/skills/gsd-plan-phase/SKILL.md +37 -0
  186. package/skills/gsd-ui-phase/SKILL.md +24 -0
  187. package/skills/gsd-ui-review/SKILL.md +24 -0
  188. package/skills/gsd-verify-work/SKILL.md +30 -0
@@ -0,0 +1,495 @@
1
+ /**
2
+ * Workstream — CRUD operations for workstream namespacing
3
+ *
4
+ * Workstreams enable parallel milestones by scoping ROADMAP.md, STATE.md,
5
+ * REQUIREMENTS.md, and phases/ into .planning/workstreams/{name}/ directories.
6
+ *
7
+ * When no workstreams/ directory exists, GSD operates in "flat mode" with
8
+ * everything at .planning/ — backward compatible with pre-workstream installs.
9
+ */
10
+
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+ const { output, error, planningPaths, planningRoot, toPosixPath, getMilestoneInfo, generateSlugInternal, setActiveWorkstream, getActiveWorkstream, filterPlanFiles, filterSummaryFiles, readSubdirectories } = require('./core.cjs');
14
+ const { stateExtractField } = require('./state.cjs');
15
+
16
+ // ─── Migration ──────────────────────────────────────────────────────────────
17
+
18
+ /**
19
+ * Migrate flat .planning/ layout to workstream mode.
20
+ * Moves per-workstream files (ROADMAP.md, STATE.md, REQUIREMENTS.md, phases/)
21
+ * into .planning/workstreams/{name}/. Shared files (PROJECT.md, config.json,
22
+ * milestones/, research/, codebase/, todos/) stay in place.
23
+ */
24
+ function migrateToWorkstreams(cwd, workstreamName) {
25
+ if (!workstreamName || /[/\\]/.test(workstreamName) || workstreamName === '.' || workstreamName === '..') {
26
+ throw new Error('Invalid workstream name for migration');
27
+ }
28
+
29
+ const baseDir = planningRoot(cwd);
30
+ const wsDir = path.join(baseDir, 'workstreams', workstreamName);
31
+
32
+ if (fs.existsSync(path.join(baseDir, 'workstreams'))) {
33
+ throw new Error('Already in workstream mode — .planning/workstreams/ exists');
34
+ }
35
+
36
+ const toMove = [
37
+ { name: 'ROADMAP.md', type: 'file' },
38
+ { name: 'STATE.md', type: 'file' },
39
+ { name: 'REQUIREMENTS.md', type: 'file' },
40
+ { name: 'phases', type: 'dir' },
41
+ ];
42
+
43
+ fs.mkdirSync(wsDir, { recursive: true });
44
+
45
+ const filesMoved = [];
46
+ try {
47
+ for (const item of toMove) {
48
+ const src = path.join(baseDir, item.name);
49
+ if (fs.existsSync(src)) {
50
+ const dest = path.join(wsDir, item.name);
51
+ fs.renameSync(src, dest);
52
+ filesMoved.push(item.name);
53
+ }
54
+ }
55
+ } catch (err) {
56
+ for (const name of filesMoved) {
57
+ try { fs.renameSync(path.join(wsDir, name), path.join(baseDir, name)); } catch {}
58
+ }
59
+ try { fs.rmSync(wsDir, { recursive: true }); } catch {}
60
+ try { fs.rmdirSync(path.join(baseDir, 'workstreams')); } catch {}
61
+ throw err;
62
+ }
63
+
64
+ return { migrated: true, workstream: workstreamName, files_moved: filesMoved };
65
+ }
66
+
67
+ // ─── CRUD Commands ──────────────────────────────────────────────────────────
68
+
69
+ function cmdWorkstreamCreate(cwd, name, options, raw) {
70
+ if (!name) {
71
+ error('workstream name required. Usage: workstream create <name>');
72
+ }
73
+
74
+ const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
75
+ if (!slug) {
76
+ error('Invalid workstream name — must contain at least one alphanumeric character');
77
+ }
78
+
79
+ const baseDir = planningRoot(cwd);
80
+ if (!fs.existsSync(baseDir)) {
81
+ error('.planning/ directory not found — run /gsd-new-project first');
82
+ }
83
+
84
+ const wsRoot = path.join(baseDir, 'workstreams');
85
+ const wsDir = path.join(wsRoot, slug);
86
+
87
+ if (fs.existsSync(wsDir) && fs.existsSync(path.join(wsDir, 'STATE.md'))) {
88
+ output({ created: false, error: 'already_exists', workstream: slug, path: toPosixPath(path.relative(cwd, wsDir)) }, raw);
89
+ return;
90
+ }
91
+
92
+ const isFlatMode = !fs.existsSync(wsRoot);
93
+ let migration = null;
94
+ if (isFlatMode && options.migrate !== false) {
95
+ const hasExistingWork = fs.existsSync(path.join(baseDir, 'ROADMAP.md')) ||
96
+ fs.existsSync(path.join(baseDir, 'STATE.md')) ||
97
+ fs.existsSync(path.join(baseDir, 'phases'));
98
+
99
+ if (hasExistingWork) {
100
+ const migrateName = options.migrateName || null;
101
+ let existingWsName;
102
+ if (migrateName) {
103
+ existingWsName = migrateName;
104
+ } else {
105
+ try {
106
+ const milestone = getMilestoneInfo(cwd);
107
+ existingWsName = generateSlugInternal(milestone.name) || 'default';
108
+ } catch {
109
+ existingWsName = 'default';
110
+ }
111
+ }
112
+
113
+ try {
114
+ migration = migrateToWorkstreams(cwd, existingWsName);
115
+ } catch (e) {
116
+ output({ created: false, error: 'migration_failed', message: e.message }, raw);
117
+ return;
118
+ }
119
+ } else {
120
+ fs.mkdirSync(wsRoot, { recursive: true });
121
+ }
122
+ }
123
+
124
+ fs.mkdirSync(wsDir, { recursive: true });
125
+ fs.mkdirSync(path.join(wsDir, 'phases'), { recursive: true });
126
+
127
+ const today = new Date().toISOString().split('T')[0];
128
+ const stateContent = [
129
+ '---',
130
+ `workstream: ${slug}`,
131
+ `created: ${today}`,
132
+ '---',
133
+ '',
134
+ '# Project State',
135
+ '',
136
+ '## Current Position',
137
+ '**Status:** Not started',
138
+ '**Current Phase:** None',
139
+ `**Last Activity:** ${today}`,
140
+ '**Last Activity Description:** Workstream created',
141
+ '',
142
+ '## Progress',
143
+ '**Phases Complete:** 0',
144
+ '**Current Plan:** N/A',
145
+ '',
146
+ '## Session Continuity',
147
+ '**Stopped At:** N/A',
148
+ '**Resume File:** None',
149
+ '',
150
+ ].join('\n');
151
+
152
+ const statePath = path.join(wsDir, 'STATE.md');
153
+ if (!fs.existsSync(statePath)) {
154
+ fs.writeFileSync(statePath, stateContent, 'utf-8');
155
+ }
156
+
157
+ setActiveWorkstream(cwd, slug);
158
+
159
+ const relPath = toPosixPath(path.relative(cwd, wsDir));
160
+ output({
161
+ created: true,
162
+ workstream: slug,
163
+ path: relPath,
164
+ state_path: relPath + '/STATE.md',
165
+ phases_path: relPath + '/phases',
166
+ migration: migration || null,
167
+ active: true,
168
+ }, raw);
169
+ }
170
+
171
+ function cmdWorkstreamList(cwd, raw) {
172
+ const wsRoot = path.join(planningRoot(cwd), 'workstreams');
173
+
174
+ if (!fs.existsSync(wsRoot)) {
175
+ output({ mode: 'flat', workstreams: [], message: 'No workstreams — operating in flat mode' }, raw);
176
+ return;
177
+ }
178
+
179
+ const entries = fs.readdirSync(wsRoot, { withFileTypes: true });
180
+ const workstreams = [];
181
+
182
+ for (const entry of entries) {
183
+ if (!entry.isDirectory()) continue;
184
+
185
+ const wsDir = path.join(wsRoot, entry.name);
186
+ const phasesDir = path.join(wsDir, 'phases');
187
+
188
+ const phaseDirs = readSubdirectories(phasesDir);
189
+ const phaseCount = phaseDirs.length;
190
+ let completedCount = 0;
191
+ for (const d of phaseDirs) {
192
+ try {
193
+ const phaseFiles = fs.readdirSync(path.join(phasesDir, d));
194
+ const plans = filterPlanFiles(phaseFiles);
195
+ const summaries = filterSummaryFiles(phaseFiles);
196
+ if (plans.length > 0 && summaries.length >= plans.length) completedCount++;
197
+ } catch {}
198
+ }
199
+
200
+ let status = 'unknown', currentPhase = null;
201
+ try {
202
+ const stateContent = fs.readFileSync(path.join(wsDir, 'STATE.md'), 'utf-8');
203
+ status = stateExtractField(stateContent, 'Status') || 'unknown';
204
+ currentPhase = stateExtractField(stateContent, 'Current Phase');
205
+ } catch {}
206
+
207
+ workstreams.push({
208
+ name: entry.name,
209
+ path: toPosixPath(path.relative(cwd, wsDir)),
210
+ has_roadmap: fs.existsSync(path.join(wsDir, 'ROADMAP.md')),
211
+ has_state: fs.existsSync(path.join(wsDir, 'STATE.md')),
212
+ status,
213
+ current_phase: currentPhase,
214
+ phase_count: phaseCount,
215
+ completed_phases: completedCount,
216
+ });
217
+ }
218
+
219
+ output({ mode: 'workstream', workstreams, count: workstreams.length }, raw);
220
+ }
221
+
222
+ function cmdWorkstreamStatus(cwd, name, raw) {
223
+ if (!name) error('workstream name required. Usage: workstream status <name>');
224
+ if (/[/\\]/.test(name) || name === '.' || name === '..') error('Invalid workstream name');
225
+
226
+ const wsDir = path.join(planningRoot(cwd), 'workstreams', name);
227
+ if (!fs.existsSync(wsDir)) {
228
+ output({ found: false, workstream: name }, raw);
229
+ return;
230
+ }
231
+
232
+ const p = planningPaths(cwd, name);
233
+ const relPath = toPosixPath(path.relative(cwd, wsDir));
234
+
235
+ const files = {
236
+ roadmap: fs.existsSync(p.roadmap),
237
+ state: fs.existsSync(p.state),
238
+ requirements: fs.existsSync(p.requirements),
239
+ };
240
+
241
+ const phases = [];
242
+ for (const dir of readSubdirectories(p.phases).sort()) {
243
+ try {
244
+ const phaseFiles = fs.readdirSync(path.join(p.phases, dir));
245
+ const plans = filterPlanFiles(phaseFiles);
246
+ const summaries = filterSummaryFiles(phaseFiles);
247
+ phases.push({
248
+ directory: dir,
249
+ status: summaries.length >= plans.length && plans.length > 0 ? 'complete' :
250
+ plans.length > 0 ? 'in_progress' : 'pending',
251
+ plan_count: plans.length,
252
+ summary_count: summaries.length,
253
+ });
254
+ } catch {}
255
+ }
256
+
257
+ let stateInfo = {};
258
+ try {
259
+ const stateContent = fs.readFileSync(p.state, 'utf-8');
260
+ stateInfo = {
261
+ status: stateExtractField(stateContent, 'Status') || 'unknown',
262
+ current_phase: stateExtractField(stateContent, 'Current Phase'),
263
+ last_activity: stateExtractField(stateContent, 'Last Activity'),
264
+ };
265
+ } catch {}
266
+
267
+ output({
268
+ found: true,
269
+ workstream: name,
270
+ path: relPath,
271
+ files,
272
+ phases,
273
+ phase_count: phases.length,
274
+ completed_phases: phases.filter(ph => ph.status === 'complete').length,
275
+ ...stateInfo,
276
+ }, raw);
277
+ }
278
+
279
+ function cmdWorkstreamComplete(cwd, name, options, raw) {
280
+ if (!name) error('workstream name required. Usage: workstream complete <name>');
281
+ if (/[/\\]/.test(name) || name === '.' || name === '..') error('Invalid workstream name');
282
+
283
+ const root = planningRoot(cwd);
284
+ const wsRoot = path.join(root, 'workstreams');
285
+ const wsDir = path.join(wsRoot, name);
286
+
287
+ if (!fs.existsSync(wsDir)) {
288
+ output({ completed: false, error: 'not_found', workstream: name }, raw);
289
+ return;
290
+ }
291
+
292
+ const active = getActiveWorkstream(cwd);
293
+ if (active === name) setActiveWorkstream(cwd, null);
294
+
295
+ const archiveDir = path.join(root, 'milestones');
296
+ const today = new Date().toISOString().split('T')[0];
297
+ let archivePath = path.join(archiveDir, `ws-${name}-${today}`);
298
+ let suffix = 1;
299
+ while (fs.existsSync(archivePath)) {
300
+ archivePath = path.join(archiveDir, `ws-${name}-${today}-${suffix++}`);
301
+ }
302
+
303
+ fs.mkdirSync(archivePath, { recursive: true });
304
+
305
+ const filesMoved = [];
306
+ try {
307
+ const entries = fs.readdirSync(wsDir, { withFileTypes: true });
308
+ for (const entry of entries) {
309
+ fs.renameSync(path.join(wsDir, entry.name), path.join(archivePath, entry.name));
310
+ filesMoved.push(entry.name);
311
+ }
312
+ } catch (err) {
313
+ for (const fname of filesMoved) {
314
+ try { fs.renameSync(path.join(archivePath, fname), path.join(wsDir, fname)); } catch {}
315
+ }
316
+ try { fs.rmSync(archivePath, { recursive: true }); } catch {}
317
+ if (active === name) setActiveWorkstream(cwd, name);
318
+ output({ completed: false, error: 'archive_failed', message: err.message, workstream: name }, raw);
319
+ return;
320
+ }
321
+
322
+ try { fs.rmdirSync(wsDir); } catch {}
323
+
324
+ let remainingWs = 0;
325
+ try {
326
+ remainingWs = fs.readdirSync(wsRoot, { withFileTypes: true }).filter(e => e.isDirectory()).length;
327
+ if (remainingWs === 0) fs.rmdirSync(wsRoot);
328
+ } catch {}
329
+
330
+ output({
331
+ completed: true,
332
+ workstream: name,
333
+ archived_to: toPosixPath(path.relative(cwd, archivePath)),
334
+ remaining_workstreams: remainingWs,
335
+ reverted_to_flat: remainingWs === 0,
336
+ }, raw);
337
+ }
338
+
339
+ // ─── Active Workstream Commands ──────────────────────────────────────────────
340
+
341
+ function cmdWorkstreamSet(cwd, name, raw) {
342
+ if (!name || name === '--clear') {
343
+ if (name !== '--clear') {
344
+ error('Workstream name required. Usage: workstream set <name> (or workstream set --clear to unset)');
345
+ }
346
+ const previous = getActiveWorkstream(cwd);
347
+ setActiveWorkstream(cwd, null);
348
+ output({ active: null, cleared: true, previous: previous || null }, raw);
349
+ return;
350
+ }
351
+
352
+ if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
353
+ output({ active: null, error: 'invalid_name', message: 'Workstream name must be alphanumeric, hyphens, and underscores only' }, raw);
354
+ return;
355
+ }
356
+
357
+ const wsDir = path.join(planningRoot(cwd), 'workstreams', name);
358
+ if (!fs.existsSync(wsDir)) {
359
+ output({ active: null, error: 'not_found', workstream: name }, raw);
360
+ return;
361
+ }
362
+
363
+ setActiveWorkstream(cwd, name);
364
+ output({ active: name, set: true }, raw, name);
365
+ }
366
+
367
+ function cmdWorkstreamGet(cwd, raw) {
368
+ const active = getActiveWorkstream(cwd);
369
+ const wsRoot = path.join(planningRoot(cwd), 'workstreams');
370
+ output({ active, mode: fs.existsSync(wsRoot) ? 'workstream' : 'flat' }, raw, active || 'none');
371
+ }
372
+
373
+ function cmdWorkstreamProgress(cwd, raw) {
374
+ const root = planningRoot(cwd);
375
+ const wsRoot = path.join(root, 'workstreams');
376
+
377
+ if (!fs.existsSync(wsRoot)) {
378
+ output({ mode: 'flat', workstreams: [], message: 'No workstreams — operating in flat mode' }, raw);
379
+ return;
380
+ }
381
+
382
+ const active = getActiveWorkstream(cwd);
383
+ const entries = fs.readdirSync(wsRoot, { withFileTypes: true });
384
+ const workstreams = [];
385
+
386
+ for (const entry of entries) {
387
+ if (!entry.isDirectory()) continue;
388
+
389
+ const wsDir = path.join(wsRoot, entry.name);
390
+ const phasesDir = path.join(wsDir, 'phases');
391
+
392
+ const phaseDirsProgress = readSubdirectories(phasesDir);
393
+ const phaseCount = phaseDirsProgress.length;
394
+ let completedCount = 0, totalPlans = 0, completedPlans = 0;
395
+ for (const d of phaseDirsProgress) {
396
+ try {
397
+ const phaseFiles = fs.readdirSync(path.join(phasesDir, d));
398
+ const plans = filterPlanFiles(phaseFiles);
399
+ const summaries = filterSummaryFiles(phaseFiles);
400
+ totalPlans += plans.length;
401
+ completedPlans += Math.min(summaries.length, plans.length);
402
+ if (plans.length > 0 && summaries.length >= plans.length) completedCount++;
403
+ } catch {}
404
+ }
405
+
406
+ let roadmapPhaseCount = phaseCount;
407
+ try {
408
+ const roadmapContent = fs.readFileSync(path.join(wsDir, 'ROADMAP.md'), 'utf-8');
409
+ const phaseMatches = roadmapContent.match(/^###?\s+Phase\s+\d/gm);
410
+ if (phaseMatches) roadmapPhaseCount = phaseMatches.length;
411
+ } catch {}
412
+
413
+ let status = 'unknown', currentPhase = null;
414
+ try {
415
+ const stateContent = fs.readFileSync(path.join(wsDir, 'STATE.md'), 'utf-8');
416
+ status = stateExtractField(stateContent, 'Status') || 'unknown';
417
+ currentPhase = stateExtractField(stateContent, 'Current Phase');
418
+ } catch {}
419
+
420
+ workstreams.push({
421
+ name: entry.name,
422
+ active: entry.name === active,
423
+ status,
424
+ current_phase: currentPhase,
425
+ phases: `${completedCount}/${roadmapPhaseCount}`,
426
+ plans: `${completedPlans}/${totalPlans}`,
427
+ progress_percent: roadmapPhaseCount > 0 ? Math.round((completedCount / roadmapPhaseCount) * 100) : 0,
428
+ });
429
+ }
430
+
431
+ output({ mode: 'workstream', active, workstreams, count: workstreams.length }, raw);
432
+ }
433
+
434
+ // ─── Collision Detection ────────────────────────────────────────────────────
435
+
436
+ /**
437
+ * Return other workstreams that are NOT complete.
438
+ * Used to detect whether the milestone has active parallel work
439
+ * when a workstream finishes its last phase.
440
+ */
441
+ function getOtherActiveWorkstreams(cwd, excludeWs) {
442
+ const wsRoot = path.join(planningRoot(cwd), 'workstreams');
443
+ if (!fs.existsSync(wsRoot)) return [];
444
+
445
+ const entries = fs.readdirSync(wsRoot, { withFileTypes: true });
446
+ const others = [];
447
+
448
+ for (const entry of entries) {
449
+ if (!entry.isDirectory() || entry.name === excludeWs) continue;
450
+
451
+ const wsDir = path.join(wsRoot, entry.name);
452
+ const statePath = path.join(wsDir, 'STATE.md');
453
+
454
+ let status = 'unknown', currentPhase = null;
455
+ try {
456
+ const content = fs.readFileSync(statePath, 'utf-8');
457
+ status = stateExtractField(content, 'Status') || 'unknown';
458
+ currentPhase = stateExtractField(content, 'Current Phase');
459
+ } catch {}
460
+
461
+ if (status.toLowerCase().includes('milestone complete') ||
462
+ status.toLowerCase().includes('archived')) {
463
+ continue;
464
+ }
465
+
466
+ const phasesDir = path.join(wsDir, 'phases');
467
+ const phaseDirsOther = readSubdirectories(phasesDir);
468
+ const phaseCount = phaseDirsOther.length;
469
+ let completedCount = 0;
470
+ for (const d of phaseDirsOther) {
471
+ try {
472
+ const phaseFiles = fs.readdirSync(path.join(phasesDir, d));
473
+ const plans = filterPlanFiles(phaseFiles);
474
+ const summaries = filterSummaryFiles(phaseFiles);
475
+ if (plans.length > 0 && summaries.length >= plans.length) completedCount++;
476
+ } catch {}
477
+ }
478
+
479
+ others.push({ name: entry.name, status, current_phase: currentPhase, phases: `${completedCount}/${phaseCount}` });
480
+ }
481
+
482
+ return others;
483
+ }
484
+
485
+ module.exports = {
486
+ migrateToWorkstreams,
487
+ cmdWorkstreamCreate,
488
+ cmdWorkstreamList,
489
+ cmdWorkstreamStatus,
490
+ cmdWorkstreamComplete,
491
+ cmdWorkstreamSet,
492
+ cmdWorkstreamGet,
493
+ cmdWorkstreamProgress,
494
+ getOtherActiveWorkstreams,
495
+ };
@@ -0,0 +1,79 @@
1
+ # Agent Contracts
2
+
3
+ Completion markers and handoff schemas for all GSD agents. Workflows use these markers to detect agent completion and route accordingly.
4
+
5
+ This doc describes what IS, not what should be. Casing inconsistencies are documented as they appear in agent source files.
6
+
7
+ ---
8
+
9
+ ## Agent Registry
10
+
11
+ | Agent | Role | Completion Markers |
12
+ |-------|------|--------------------|
13
+ | gsd-planner | Plan creation | `## PLANNING COMPLETE` |
14
+ | gsd-executor | Plan execution | `## PLAN COMPLETE`, `## CHECKPOINT REACHED` |
15
+ | gsd-phase-researcher | Phase-scoped research | `## RESEARCH COMPLETE`, `## RESEARCH BLOCKED` |
16
+ | gsd-project-researcher | Project-wide research | `## RESEARCH COMPLETE`, `## RESEARCH BLOCKED` |
17
+ | gsd-plan-checker | Plan validation | `## VERIFICATION PASSED`, `## ISSUES FOUND` |
18
+ | gsd-research-synthesizer | Multi-research synthesis | `## SYNTHESIS COMPLETE`, `## SYNTHESIS BLOCKED` |
19
+ | gsd-debugger | Debug investigation | `## DEBUG COMPLETE`, `## ROOT CAUSE FOUND`, `## CHECKPOINT REACHED` |
20
+ | gsd-roadmapper | Roadmap creation/revision | `## ROADMAP CREATED`, `## ROADMAP REVISED`, `## ROADMAP BLOCKED` |
21
+ | gsd-ui-auditor | UI review | `## UI REVIEW COMPLETE` |
22
+ | gsd-ui-checker | UI validation | `## ISSUES FOUND` |
23
+ | gsd-ui-researcher | UI spec creation | `## UI-SPEC COMPLETE`, `## UI-SPEC BLOCKED` |
24
+ | gsd-verifier | Post-execution verification | `## Verification Complete` (title case) |
25
+ | gsd-integration-checker | Cross-phase integration check | `## Integration Check Complete` (title case) |
26
+ | gsd-nyquist-auditor | Sampling audit | `## PARTIAL`, `## ESCALATE` (non-standard) |
27
+ | gsd-security-auditor | Security audit | `## OPEN_THREATS`, `## ESCALATE` (non-standard) |
28
+ | gsd-codebase-mapper | Codebase analysis | No marker (writes docs directly) |
29
+ | gsd-assumptions-analyzer | Assumption extraction | No marker (returns `## Assumptions` sections) |
30
+ | gsd-doc-verifier | Doc validation | No marker (writes JSON to `.planning/tmp/`) |
31
+ | gsd-doc-writer | Doc generation | No marker (writes docs directly) |
32
+ | gsd-advisor-researcher | Advisory research | No marker (utility agent) |
33
+ | gsd-user-profiler | User profiling | No marker (returns JSON in analysis tags) |
34
+ | gsd-intel-updater | Codebase intelligence analysis | `## INTEL UPDATE COMPLETE`, `## INTEL UPDATE FAILED` |
35
+
36
+ ## Marker Rules
37
+
38
+ 1. **ALL-CAPS markers** (e.g., `## PLANNING COMPLETE`) are the standard convention
39
+ 2. **Title-case markers** (e.g., `## Verification Complete`) exist in gsd-verifier and gsd-integration-checker -- these are intentional as-is, not bugs
40
+ 3. **Non-standard markers** (e.g., `## PARTIAL`, `## ESCALATE`) in audit agents indicate partial results requiring orchestrator judgment
41
+ 4. **Agents without markers** either write artifacts directly to disk or return structured data (JSON/sections) that the caller parses
42
+ 5. Markers must appear as H2 headings (`## `) at the start of a line in the agent's final output
43
+
44
+ ## Key Handoff Contracts
45
+
46
+ ### Planner -> Executor (via PLAN.md)
47
+
48
+ | Field | Required | Description |
49
+ |-------|----------|-------------|
50
+ | Frontmatter | Yes | phase, plan, type, wave, depends_on, files_modified, autonomous, requirements |
51
+ | `<objective>` | Yes | What the plan achieves |
52
+ | `<tasks>` | Yes | Ordered task list with type, files, action, verify, acceptance_criteria |
53
+ | `<verification>` | Yes | Overall verification steps |
54
+ | `<success_criteria>` | Yes | Measurable completion criteria |
55
+
56
+ ### Executor -> Verifier (via SUMMARY.md)
57
+
58
+ | Field | Required | Description |
59
+ |-------|----------|-------------|
60
+ | Frontmatter | Yes | phase, plan, subsystem, tags, key-files, metrics |
61
+ | Commits table | Yes | Per-task commit hashes and descriptions |
62
+ | Deviations section | Yes | Auto-fixed issues or "None" |
63
+ | Self-Check | Yes | PASSED or FAILED with details |
64
+
65
+ ## Workflow Regex Patterns
66
+
67
+ Workflows match these markers to detect agent completion:
68
+
69
+ **plan-phase.md matches:**
70
+ - `## RESEARCH COMPLETE` / `## RESEARCH BLOCKED` (researcher output)
71
+ - `## PLANNING COMPLETE` (planner output)
72
+ - `## CHECKPOINT REACHED` (planner/executor pause)
73
+ - `## VERIFICATION PASSED` / `## ISSUES FOUND` (plan-checker output)
74
+
75
+ **execute-phase.md matches:**
76
+ - `## PHASE COMPLETE` (all plans in phase done)
77
+ - `## Self-Check: FAILED` (summary self-check)
78
+
79
+ > **NOTE:** `## PLAN COMPLETE` is the gsd-executor's completion marker but execute-phase.md does not regex-match it. Instead, it detects executor completion via spot-checks (SUMMARY.md existence, git commit state). This is intentional behavior, not a mismatch.
@@ -0,0 +1,113 @@
1
+ # GSD Artifact Types
2
+
3
+ This reference documents all artifact types in the GSD planning taxonomy. Each type has a defined
4
+ shape, lifecycle, location, and consumption mechanism. A well-formatted artifact that no workflow
5
+ reads is inert — the consumption mechanism is what gives an artifact meaning.
6
+
7
+ ---
8
+
9
+ ## Core Artifacts
10
+
11
+ ### ROADMAP.md
12
+ - **Shape**: Milestone + phase listing with goals and canonical refs
13
+ - **Lifecycle**: Created → Updated per milestone → Archived
14
+ - **Location**: `.planning/ROADMAP.md`
15
+ - **Consumed by**: `plan-phase`, `discuss-phase`, `execute-phase`, `progress`, `state` commands
16
+
17
+ ### STATE.md
18
+ - **Shape**: Current position tracker (phase, plan, progress, decisions)
19
+ - **Lifecycle**: Continuously updated throughout the project
20
+ - **Location**: `.planning/STATE.md`
21
+ - **Consumed by**: All orchestration workflows; `resume-project`, `progress`, `next` commands
22
+
23
+ ### REQUIREMENTS.md
24
+ - **Shape**: Numbered acceptance criteria with traceability table
25
+ - **Lifecycle**: Created at project start → Updated as requirements are satisfied
26
+ - **Location**: `.planning/REQUIREMENTS.md`
27
+ - **Consumed by**: `discuss-phase`, `plan-phase`, CONTEXT.md generation; executor marks complete
28
+
29
+ ### CONTEXT.md (per-phase)
30
+ - **Shape**: 6-section format: domain, decisions, canonical_refs, code_context, specifics, deferred
31
+ - **Lifecycle**: Created before planning → Used during planning and execution → Superseded by next phase
32
+ - **Location**: `.planning/phases/XX-name/XX-CONTEXT.md`
33
+ - **Consumed by**: `plan-phase` (reads decisions), `execute-phase` (reads code_context and canonical_refs)
34
+
35
+ ### PLAN.md (per-plan)
36
+ - **Shape**: Frontmatter + objective + tasks with types + success criteria + output spec
37
+ - **Lifecycle**: Created by planner → Executed → SUMMARY.md produced
38
+ - **Location**: `.planning/phases/XX-name/XX-YY-PLAN.md`
39
+ - **Consumed by**: `execute-phase` executor; task commits reference plan IDs
40
+
41
+ ### SUMMARY.md (per-plan)
42
+ - **Shape**: Frontmatter with dependency graph + narrative + deviations + self-check
43
+ - **Lifecycle**: Created at plan completion → read by subsequent plans in same phase
44
+ - **Location**: `.planning/phases/XX-name/XX-YY-SUMMARY.md`
45
+ - **Consumed by**: Orchestrator (progress), planner (context for future plans), `milestone-summary`
46
+
47
+ ### HANDOFF.json / .continue-here.md
48
+ - **Shape**: Structured pause state (JSON machine-readable + Markdown human-readable)
49
+ - **Lifecycle**: Created on pause → Consumed on resume → Replaced by next pause
50
+ - **Location**: `.planning/HANDOFF.json` + `.planning/phases/XX-name/.continue-here.md` (or spike/deliberation path)
51
+ - **Consumed by**: `resume-project` workflow
52
+
53
+ ---
54
+
55
+ ## Extended Artifacts
56
+
57
+ ### DISCUSSION-LOG.md (per-phase)
58
+ - **Shape**: Audit trail of assumptions and corrections from discuss-phase
59
+ - **Lifecycle**: Created at discussion time → read-only audit record
60
+ - **Location**: `.planning/phases/XX-name/XX-DISCUSSION-LOG.md`
61
+ - **Consumed by**: Human review; not read by automated workflows
62
+
63
+ ### USER-PROFILE.md
64
+ - **Shape**: Calibration tier and preferences profile
65
+ - **Lifecycle**: Created by `profile-user` → Updated as preferences are observed
66
+ - **Location**: `$HOME/.config/opencode/get-shit-done/USER-PROFILE.md`
67
+ - **Consumed by**: `discuss-phase-assumptions` (calibration tier), `plan-phase`
68
+
69
+ ### SPIKE.md / DESIGN.md (per-spike)
70
+ - **Shape**: Research question + methodology + findings + recommendation
71
+ - **Lifecycle**: Created → Investigated → Decided → Archived
72
+ - **Location**: `.planning/spikes/SPIKE-NNN/`
73
+ - **Consumed by**: Planner when spike is referenced; `pause-work` for spike context handoff
74
+
75
+ ---
76
+
77
+ ## Standing Reference Artifacts
78
+
79
+ ### METHODOLOGY.md
80
+
81
+ - **Shape**: Standing reference — reusable interpretive frameworks (lenses) that apply across phases
82
+ - **Lifecycle**: Created → Active → Superseded (when a lens is replaced by a better one)
83
+ - **Location**: `.planning/METHODOLOGY.md` (project-scoped, not phase-scoped)
84
+ - **Contents**: Named lenses, each documenting:
85
+ - What it diagnoses (the class of problem it detects)
86
+ - What it recommends (the class of response it prescribes)
87
+ - When to apply (triggering conditions)
88
+ - Example: Bayesian updating, STRIDE threat modeling, Cost-of-delay prioritization
89
+ - **Consumed by**:
90
+ - `discuss-phase-assumptions` — reads METHODOLOGY.md (if it exists) and applies active lenses
91
+ to the current assumption analysis before surfacing findings to the user
92
+ - `plan-phase` — reads METHODOLOGY.md to inform methodology selection for each plan
93
+ - `pause-work` — includes METHODOLOGY.md in the Required Reading section of `.continue-here.md`
94
+ so resuming agents inherit the project's analytical orientation
95
+
96
+ **Why consumption matters:** A METHODOLOGY.md that no workflow reads is inert. The lenses only
97
+ take effect when an agent loads them into its reasoning context before analysis. This is why
98
+ both the discuss-phase-assumptions and pause-work workflows explicitly reference this file.
99
+
100
+ **Example lens entry:**
101
+
102
+ ```markdown
103
+ ## Bayesian Updating
104
+
105
+ **Diagnoses:** Decisions made with stale priors — assumptions formed early that evidence has since
106
+ contradicted, but which remain embedded in the plan.
107
+
108
+ **Recommends:** Before confirming an assumption, ask: "What evidence would make me change this?"
109
+ If no evidence could change it, it's a belief, not an assumption. Flag for user review.
110
+
111
+ **Apply when:** Any assumption carries Confident label but was formed before recent architectural
112
+ changes, library upgrades, or scope corrections.
113
+ ```