gsdd-cli 0.26.0 → 0.28.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 (82) hide show
  1. package/README.md +55 -23
  2. package/agents/DISTILLATION.md +2 -2
  3. package/agents/README.md +4 -4
  4. package/agents/approach-explorer.md +4 -4
  5. package/agents/executor.md +14 -14
  6. package/agents/integration-checker.md +2 -2
  7. package/agents/researcher.md +2 -2
  8. package/agents/roadmapper.md +6 -6
  9. package/agents/synthesizer.md +18 -18
  10. package/agents/verifier.md +2 -2
  11. package/bin/adapters/agents.mjs +3 -3
  12. package/bin/adapters/claude.mjs +16 -14
  13. package/bin/adapters/opencode.mjs +15 -12
  14. package/bin/gsdd.mjs +16 -13
  15. package/bin/lib/{models.mjs → config.mjs} +23 -16
  16. package/bin/lib/control-map.mjs +17 -488
  17. package/bin/lib/global-install.mjs +16 -3
  18. package/bin/lib/health-truth.mjs +8 -13
  19. package/bin/lib/health.mjs +25 -39
  20. package/bin/lib/init-flow.mjs +44 -38
  21. package/bin/lib/init-prompts.mjs +3 -3
  22. package/bin/lib/init-runtime.mjs +17 -33
  23. package/bin/lib/lifecycle-preflight.mjs +297 -404
  24. package/bin/lib/lifecycle-state.mjs +2 -1
  25. package/bin/lib/next.mjs +243 -20
  26. package/bin/lib/phase.mjs +10 -315
  27. package/bin/lib/rendering.mjs +64 -44
  28. package/bin/lib/runtime-freshness.mjs +18 -15
  29. package/bin/lib/state-dir.mjs +45 -0
  30. package/bin/lib/templates.mjs +59 -22
  31. package/bin/lib/work-context.mjs +12 -1
  32. package/bin/lib/workflows.mjs +0 -1
  33. package/bin/lib/workspace-root.mjs +11 -6
  34. package/distilled/DESIGN.md +58 -2
  35. package/distilled/EVIDENCE-INDEX.md +15 -2
  36. package/distilled/README.md +23 -33
  37. package/distilled/SKILL.md +9 -10
  38. package/distilled/templates/agents.block.md +5 -5
  39. package/distilled/templates/approach.md +3 -3
  40. package/distilled/templates/auth-matrix.md +2 -2
  41. package/distilled/templates/brownfield-change/CHANGE.md +1 -1
  42. package/distilled/templates/delegates/approach-explorer.md +5 -5
  43. package/distilled/templates/delegates/mapper-arch.md +3 -3
  44. package/distilled/templates/delegates/mapper-concerns.md +4 -4
  45. package/distilled/templates/delegates/mapper-quality.md +3 -3
  46. package/distilled/templates/delegates/mapper-tech.md +3 -3
  47. package/distilled/templates/delegates/plan-checker.md +5 -5
  48. package/distilled/templates/delegates/researcher-architecture.md +3 -3
  49. package/distilled/templates/delegates/researcher-features.md +3 -3
  50. package/distilled/templates/delegates/researcher-pitfalls.md +3 -3
  51. package/distilled/templates/delegates/researcher-stack.md +3 -3
  52. package/distilled/templates/delegates/researcher-synthesizer.md +7 -7
  53. package/distilled/templates/research/architecture.md +1 -1
  54. package/distilled/templates/research/pitfalls.md +1 -1
  55. package/distilled/templates/research/stack.md +1 -1
  56. package/distilled/templates/roadmap.md +4 -4
  57. package/distilled/templates/spec.md +2 -2
  58. package/distilled/workflows/audit-milestone.md +22 -22
  59. package/distilled/workflows/complete-milestone.md +35 -35
  60. package/distilled/workflows/execute.md +29 -29
  61. package/distilled/workflows/map-codebase.md +30 -30
  62. package/distilled/workflows/new-milestone.md +18 -18
  63. package/distilled/workflows/new-project.md +45 -45
  64. package/distilled/workflows/pause.md +15 -15
  65. package/distilled/workflows/plan.md +63 -63
  66. package/distilled/workflows/progress.md +40 -39
  67. package/distilled/workflows/quick.md +43 -43
  68. package/distilled/workflows/resume.md +23 -22
  69. package/distilled/workflows/verify-work.md +7 -7
  70. package/distilled/workflows/verify.md +20 -20
  71. package/docs/BROWNFIELD-PROOF.md +1 -1
  72. package/docs/RUNTIME-SUPPORT.md +16 -16
  73. package/docs/USER-GUIDE.md +21 -21
  74. package/docs/claude/context-monitor.md +1 -1
  75. package/docs/proof/consumer-node-cli/README.md +1 -1
  76. package/package.json +3 -3
  77. package/bin/lib/closeout-report.mjs +0 -318
  78. package/bin/lib/evidence-contract.mjs +0 -325
  79. package/bin/lib/provenance.mjs +0 -390
  80. package/bin/lib/session-fingerprint.mjs +0 -223
  81. package/bin/lib/ui-proof.mjs +0 -1007
  82. package/distilled/workflows/plan-milestone-gaps.md +0 -204
@@ -28,7 +28,8 @@ import {
28
28
  getRuntimeModelOverride,
29
29
  loadProjectModelConfig,
30
30
  resolveRuntimeAgentModel,
31
- } from './models.mjs';
31
+ } from './config.mjs';
32
+ import { resolveStateDir } from './state-dir.mjs';
32
33
 
33
34
  const __filename = fileURLToPath(import.meta.url);
34
35
  const __dirname = dirname(__filename);
@@ -68,7 +69,7 @@ function compareGeneratedFile({ cwd, runtime, relativePath, expectedContent, rep
68
69
  };
69
70
  }
70
71
 
71
- function buildClaudeEntries({ cwd, workflows }) {
72
+ function buildClaudeEntries({ cwd, workflows, stateDirName = '.work' }) {
72
73
  const checkerModelAlias = resolveRuntimeAgentModel({
73
74
  cwd,
74
75
  runtime: 'claude',
@@ -85,8 +86,8 @@ function buildClaudeEntries({ cwd, workflows }) {
85
86
  const entries = workflows.map((workflow) => ({
86
87
  relativePath: `.claude/skills/${workflow.name}/SKILL.md`,
87
88
  expectedContent: workflow.name === 'gsdd-plan'
88
- ? renderClaudePlanSkill()
89
- : renderSkillContent(workflow),
89
+ ? renderClaudePlanSkill({ stateDirName })
90
+ : renderSkillContent(workflow, { stateDirName }),
90
91
  }));
91
92
 
92
93
  entries.push(
@@ -107,7 +108,7 @@ function buildClaudeEntries({ cwd, workflows }) {
107
108
  return entries;
108
109
  }
109
110
 
110
- function buildOpenCodeEntries({ cwd, workflows }) {
111
+ function buildOpenCodeEntries({ cwd, workflows, stateDirName = '.work' }) {
111
112
  const config = loadProjectModelConfig(cwd);
112
113
  const checkerModelId = getRuntimeModelOverride(config, 'opencode', 'plan-checker');
113
114
  const explorerModelId = getRuntimeModelOverride(config, 'opencode', 'approach-explorer');
@@ -115,8 +116,8 @@ function buildOpenCodeEntries({ cwd, workflows }) {
115
116
  const entries = workflows.map((workflow) => ({
116
117
  relativePath: `.opencode/commands/${workflow.name}.md`,
117
118
  expectedContent: workflow.name === 'gsdd-plan'
118
- ? renderOpenCodePlanCommand()
119
- : renderOpenCodeCommandContent(workflow),
119
+ ? renderOpenCodePlanCommand({ stateDirName })
120
+ : renderOpenCodeCommandContent(workflow, { stateDirName }),
120
121
  }));
121
122
 
122
123
  entries.push(
@@ -150,31 +151,33 @@ function buildCodexEntries({ cwd }) {
150
151
  ];
151
152
  }
152
153
 
153
- function buildWorkspaceHelperEntries() {
154
+ function buildWorkspaceHelperEntries(stateDirName) {
154
155
  return buildPlanningCliHelperEntries({
155
156
  packageName: PACKAGE_JSON.name,
156
157
  packageVersion: PACKAGE_JSON.version,
158
+ stateDirName,
157
159
  }).map((entry) => ({
158
- relativePath: `.planning/${entry.relativePath}`,
160
+ relativePath: `${stateDirName}/${entry.relativePath}`,
159
161
  expectedContent: entry.content,
160
162
  }));
161
163
  }
162
164
 
163
165
  export function collectExpectedRuntimeSurfaceGroups({ cwd = process.cwd(), workflows }) {
166
+ const stateDirName = resolveStateDir(cwd).name;
164
167
  return [
165
168
  {
166
169
  runtime: 'workspace-helper',
167
170
  label: 'workspace workflow helper',
168
- root: '.planning/bin',
171
+ root: `${stateDirName}/bin`,
169
172
  repairCommand: 'npx -y gsdd-cli update',
170
- entries: buildWorkspaceHelperEntries(),
173
+ entries: buildWorkspaceHelperEntries(stateDirName),
171
174
  },
172
175
  {
173
176
  runtime: 'portable',
174
177
  label: 'portable skills',
175
178
  root: '.agents/skills',
176
179
  repairCommand: 'npx -y gsdd-cli update',
177
- entries: buildPortableSkillEntries(workflows).map((entry) => ({
180
+ entries: buildPortableSkillEntries(workflows, { stateDirName }).map((entry) => ({
178
181
  relativePath: entry.relativePath,
179
182
  expectedContent: entry.content,
180
183
  })),
@@ -184,14 +187,14 @@ export function collectExpectedRuntimeSurfaceGroups({ cwd = process.cwd(), workf
184
187
  label: 'Claude Code native surfaces',
185
188
  root: '.claude',
186
189
  repairCommand: 'npx -y gsdd-cli update --tools claude',
187
- entries: buildClaudeEntries({ cwd, workflows }),
190
+ entries: buildClaudeEntries({ cwd, workflows, stateDirName }),
188
191
  },
189
192
  {
190
193
  runtime: 'opencode',
191
194
  label: 'OpenCode native surfaces',
192
195
  root: '.opencode',
193
196
  repairCommand: 'npx -y gsdd-cli update --tools opencode',
194
- entries: buildOpenCodeEntries({ cwd, workflows }),
197
+ entries: buildOpenCodeEntries({ cwd, workflows, stateDirName }),
195
198
  },
196
199
  {
197
200
  runtime: 'codex',
@@ -206,7 +209,7 @@ export function collectExpectedRuntimeSurfaceGroups({ cwd = process.cwd(), workf
206
209
  export function evaluateRuntimeFreshness({ cwd = process.cwd(), workflows = [] }) {
207
210
  const groups = collectExpectedRuntimeSurfaceGroups({ cwd, workflows }).map((group) => {
208
211
  const installed = group.runtime === 'workspace-helper'
209
- ? existsSync(join(cwd, '.planning'))
212
+ ? existsSync(resolveStateDir(cwd).dir)
210
213
  : existsSync(join(cwd, group.root));
211
214
  const comparisons = installed
212
215
  ? group.entries.map((entry) => compareGeneratedFile({
@@ -0,0 +1,45 @@
1
+ // state-dir.mjs — Single source of truth for the Workspine state directory.
2
+ //
3
+ // Workspine keeps everything in ONE folder: .work/. Older projects used
4
+ // .planning/. This module is the only place that decides which folder the tool
5
+ // reads and writes for a given repo root. No other module may hardcode the name.
6
+
7
+ import { existsSync } from 'fs';
8
+ import { join } from 'path';
9
+
10
+ export const STATE_DIR_NAME = '.work';
11
+ export const LEGACY_STATE_DIR_NAME = '.planning';
12
+
13
+ export const MIGRATION_NOTICE =
14
+ 'Note: reading legacy .planning/ — new Workspine projects use .work/. Your .planning/ still works; move its files under .work/ when convenient.';
15
+
16
+ // True when a repo root already holds Workspine state in EITHER the current
17
+ // (.work) or legacy (.planning) location. Used for workspace-root discovery.
18
+ export function hasStateMarker(root) {
19
+ return (
20
+ existsSync(join(root, STATE_DIR_NAME, 'config.json')) ||
21
+ existsSync(join(root, STATE_DIR_NAME)) ||
22
+ existsSync(join(root, LEGACY_STATE_DIR_NAME, 'config.json')) ||
23
+ existsSync(join(root, LEGACY_STATE_DIR_NAME))
24
+ );
25
+ }
26
+
27
+ // Decide which state directory to use for a repo root.
28
+ // Precedence (config.json is the "really initialized" marker):
29
+ // 1. .work/config.json -> .work (migration done / new project)
30
+ // 2. .planning/config.json -> .planning (legacy; dual-read + notice)
31
+ // 3. .work/ dir -> .work
32
+ // 4. .planning/ dir -> .planning (legacy; dual-read + notice)
33
+ // 5. neither -> .work (brand-new repo default)
34
+ export function resolveStateDir(root) {
35
+ const workDir = join(root, STATE_DIR_NAME);
36
+ const legacyDir = join(root, LEGACY_STATE_DIR_NAME);
37
+ const work = { dir: workDir, name: STATE_DIR_NAME, legacy: false, migrationNotice: null };
38
+ const legacy = { dir: legacyDir, name: LEGACY_STATE_DIR_NAME, legacy: true, migrationNotice: MIGRATION_NOTICE };
39
+
40
+ if (existsSync(join(workDir, 'config.json'))) return work;
41
+ if (existsSync(join(legacyDir, 'config.json'))) return legacy;
42
+ if (existsSync(workDir)) return work;
43
+ if (existsSync(legacyDir)) return legacy;
44
+ return work;
45
+ }
@@ -1,17 +1,20 @@
1
1
  // templates.mjs - Project template and role installation/refresh helpers
2
2
 
3
- import { existsSync, mkdirSync, readdirSync, cpSync, unlinkSync } from 'fs';
4
- import { join } from 'path';
3
+ import { createHash } from 'crypto';
4
+ import { existsSync, mkdirSync, readdirSync, cpSync, unlinkSync, readFileSync, statSync, writeFileSync } from 'fs';
5
+ import { basename, join } from 'path';
5
6
  import { fileHash, readManifest } from './manifest.mjs';
7
+ import { localizeStateDirReferences } from './rendering.mjs';
6
8
 
7
- export function installProjectTemplates({ planningDir, distilledDir, agentsDir }) {
9
+ export function installProjectTemplates({ planningDir, distilledDir, agentsDir, stateDirName = '.work' }) {
8
10
  const localTemplatesDir = join(planningDir, 'templates');
9
11
  const globalTemplatesDir = join(distilledDir, 'templates');
12
+ const stateName = basename(planningDir);
10
13
 
11
14
  if (!existsSync(localTemplatesDir)) {
12
15
  if (existsSync(globalTemplatesDir)) {
13
- cpSync(globalTemplatesDir, localTemplatesDir, { recursive: true });
14
- console.log(' - copied templates to .planning/templates/');
16
+ copyTemplateTree(globalTemplatesDir, localTemplatesDir, { stateDirName });
17
+ console.log(` - copied templates to ${stateName}/templates/`);
15
18
  // Warn-only by design: init should not fail on missing templates because
16
19
  // the user may still proceed and fix later. The hard gate lives in
17
20
  // `gsdd health` (E6/E7/E8) which reports these as errors. This is the
@@ -33,7 +36,7 @@ export function installProjectTemplates({ planningDir, distilledDir, agentsDir }
33
36
  console.log(' - WARN: missing distilled/templates/; cannot copy templates');
34
37
  }
35
38
  } else {
36
- console.log(' - .planning/templates/ already exists');
39
+ console.log(` - ${stateName}/templates/ already exists`);
37
40
  }
38
41
 
39
42
  const localRolesDir = join(localTemplatesDir, 'roles');
@@ -41,18 +44,18 @@ export function installProjectTemplates({ planningDir, distilledDir, agentsDir }
41
44
  if (existsSync(agentsDir)) {
42
45
  mkdirSync(localRolesDir, { recursive: true });
43
46
  for (const file of listRoleFiles(agentsDir)) {
44
- cpSync(join(agentsDir, file), join(localRolesDir, file));
47
+ copyTemplateFile(join(agentsDir, file), join(localRolesDir, file), { stateDirName });
45
48
  }
46
- console.log(' - copied role contracts to .planning/templates/roles/');
49
+ console.log(` - copied role contracts to ${stateName}/templates/roles/`);
47
50
  } else {
48
51
  console.log(' - WARN: missing agents/; cannot copy role contracts');
49
52
  }
50
53
  } else {
51
- console.log(' - .planning/templates/roles/ already exists');
54
+ console.log(` - ${stateName}/templates/roles/ already exists`);
52
55
  }
53
56
  }
54
57
 
55
- export function refreshTemplates({ planningDir, distilledDir, agentsDir, isDry = false }) {
58
+ export function refreshTemplates({ planningDir, distilledDir, agentsDir, isDry = false, stateDirName = '.work' }) {
56
59
  const existingManifest = readManifest(planningDir);
57
60
  const globalTemplatesDir = join(distilledDir, 'templates');
58
61
  const localTemplatesDir = join(planningDir, 'templates');
@@ -65,11 +68,45 @@ export function refreshTemplates({ planningDir, distilledDir, agentsDir, isDry =
65
68
  ];
66
69
 
67
70
  for (const category of categories) {
68
- refreshCategory(category, existingManifest, isDry);
71
+ refreshCategory(category, existingManifest, isDry, { stateDirName });
69
72
  }
70
73
 
71
- refreshRootTemplates(globalTemplatesDir, localTemplatesDir, existingManifest, isDry);
72
- refreshRoles(agentsDir, join(localTemplatesDir, 'roles'), existingManifest, isDry);
74
+ refreshRootTemplates(globalTemplatesDir, localTemplatesDir, existingManifest, isDry, { stateDirName });
75
+ refreshRoles(agentsDir, join(localTemplatesDir, 'roles'), existingManifest, isDry, { stateDirName });
76
+ }
77
+
78
+ function contentHash(content) {
79
+ return createHash('sha256').update(content).digest('hex');
80
+ }
81
+
82
+ function localizedTemplateContent(srcPath, { stateDirName = '.work' } = {}) {
83
+ return localizeStateDirReferences(readFileSync(srcPath, 'utf-8'), { stateDirName });
84
+ }
85
+
86
+ function sourceTemplateHash(srcPath, { stateDirName = '.work' } = {}) {
87
+ if (!srcPath.endsWith('.md')) return fileHash(srcPath);
88
+ return contentHash(localizedTemplateContent(srcPath, { stateDirName }));
89
+ }
90
+
91
+ function copyTemplateFile(srcPath, destPath, { stateDirName = '.work' } = {}) {
92
+ if (!srcPath.endsWith('.md')) {
93
+ cpSync(srcPath, destPath);
94
+ return;
95
+ }
96
+ writeFileSync(destPath, localizedTemplateContent(srcPath, { stateDirName }));
97
+ }
98
+
99
+ function copyTemplateTree(srcDir, destDir, { stateDirName = '.work' } = {}) {
100
+ mkdirSync(destDir, { recursive: true });
101
+ for (const entry of readdirSync(srcDir)) {
102
+ const srcPath = join(srcDir, entry);
103
+ const destPath = join(destDir, entry);
104
+ if (statSync(srcPath).isDirectory()) {
105
+ copyTemplateTree(srcPath, destPath, { stateDirName });
106
+ } else {
107
+ copyTemplateFile(srcPath, destPath, { stateDirName });
108
+ }
109
+ }
73
110
  }
74
111
 
75
112
  function listRoleFiles(agentsDir) {
@@ -78,7 +115,7 @@ function listRoleFiles(agentsDir) {
78
115
  );
79
116
  }
80
117
 
81
- function refreshCategory({ name, src, dest, manifestKey }, existingManifest, isDry) {
118
+ function refreshCategory({ name, src, dest, manifestKey }, existingManifest, isDry, { stateDirName = '.work' } = {}) {
82
119
  if (!existsSync(src)) return;
83
120
  if (!existsSync(dest) && !isDry) {
84
121
  mkdirSync(dest, { recursive: true });
@@ -91,7 +128,7 @@ function refreshCategory({ name, src, dest, manifestKey }, existingManifest, isD
91
128
  for (const file of sourceFiles) {
92
129
  const srcPath = join(src, file);
93
130
  const destPath = join(dest, file);
94
- const srcHash = fileHash(srcPath);
131
+ const srcHash = sourceTemplateHash(srcPath, { stateDirName });
95
132
 
96
133
  if (existsSync(destPath)) {
97
134
  const destHash = fileHash(destPath);
@@ -106,7 +143,7 @@ function refreshCategory({ name, src, dest, manifestKey }, existingManifest, isD
106
143
  if (isDry) {
107
144
  console.log(` - would refresh ${name}/${file}`);
108
145
  } else {
109
- cpSync(srcPath, destPath);
146
+ copyTemplateFile(srcPath, destPath, { stateDirName });
110
147
  console.log(` - refreshed ${name}/${file}`);
111
148
  }
112
149
  }
@@ -126,7 +163,7 @@ function refreshCategory({ name, src, dest, manifestKey }, existingManifest, isD
126
163
  }
127
164
  }
128
165
 
129
- function refreshRootTemplates(globalTemplatesDir, localTemplatesDir, existingManifest, isDry) {
166
+ function refreshRootTemplates(globalTemplatesDir, localTemplatesDir, existingManifest, isDry, { stateDirName = '.work' } = {}) {
130
167
  if (!existsSync(globalTemplatesDir)) return;
131
168
 
132
169
  const manifestHashes = existingManifest?.templates?.root || null;
@@ -135,7 +172,7 @@ function refreshRootTemplates(globalTemplatesDir, localTemplatesDir, existingMan
135
172
  for (const file of sourceFiles) {
136
173
  const srcPath = join(globalTemplatesDir, file);
137
174
  const destPath = join(localTemplatesDir, file);
138
- const srcHash = fileHash(srcPath);
175
+ const srcHash = sourceTemplateHash(srcPath, { stateDirName });
139
176
 
140
177
  if (existsSync(destPath)) {
141
178
  const destHash = fileHash(destPath);
@@ -150,7 +187,7 @@ function refreshRootTemplates(globalTemplatesDir, localTemplatesDir, existingMan
150
187
  if (isDry) {
151
188
  console.log(` - would refresh templates/${file}`);
152
189
  } else {
153
- cpSync(srcPath, destPath);
190
+ copyTemplateFile(srcPath, destPath, { stateDirName });
154
191
  console.log(` - refreshed templates/${file}`);
155
192
  }
156
193
  }
@@ -174,7 +211,7 @@ function refreshRootTemplates(globalTemplatesDir, localTemplatesDir, existingMan
174
211
  }
175
212
  }
176
213
 
177
- function refreshRoles(agentsDir, localRolesDir, existingManifest, isDry) {
214
+ function refreshRoles(agentsDir, localRolesDir, existingManifest, isDry, { stateDirName = '.work' } = {}) {
178
215
  if (!existsSync(agentsDir)) return;
179
216
  if (!existsSync(localRolesDir) && !isDry) {
180
217
  mkdirSync(localRolesDir, { recursive: true });
@@ -189,7 +226,7 @@ function refreshRoles(agentsDir, localRolesDir, existingManifest, isDry) {
189
226
  for (const file of sourceFiles) {
190
227
  const srcPath = join(agentsDir, file);
191
228
  const destPath = join(localRolesDir, file);
192
- const srcHash = fileHash(srcPath);
229
+ const srcHash = sourceTemplateHash(srcPath, { stateDirName });
193
230
 
194
231
  if (existsSync(destPath)) {
195
232
  const destHash = fileHash(destPath);
@@ -204,7 +241,7 @@ function refreshRoles(agentsDir, localRolesDir, existingManifest, isDry) {
204
241
  if (isDry) {
205
242
  console.log(` - would refresh roles/${file}`);
206
243
  } else {
207
- cpSync(srcPath, destPath);
244
+ copyTemplateFile(srcPath, destPath, { stateDirName });
208
245
  console.log(` - refreshed roles/${file}`);
209
246
  }
210
247
  }
@@ -12,6 +12,8 @@ import {
12
12
  writeSync,
13
13
  } from 'fs';
14
14
  import { basename, dirname, join, relative, resolve } from 'path';
15
+ import { evaluateLifecycleState } from './lifecycle-state.mjs';
16
+ import { resolveStateDir } from './state-dir.mjs';
15
17
 
16
18
  export const WORK_DIR_NAME = '.work';
17
19
 
@@ -657,14 +659,22 @@ export function inspectWorkContext(cwd = process.cwd()) {
657
659
  const questions = readOpenQuestions(paths.workDir);
658
660
  const evidence = readJsonIfExists(paths.evidenceManifest);
659
661
  const graph = readGraphEvents(paths.workDir);
660
- const planningDir = join(paths.root, '.planning');
662
+ const { dir: planningDir, name: stateDirName, migrationNotice } = resolveStateDir(paths.root);
663
+ const lifecycle = evaluateLifecycleState({ planningDir });
661
664
  const planning = {
662
665
  exists: existsSync(planningDir),
663
666
  has_spec: existsSync(join(planningDir, 'SPEC.md')),
664
667
  has_roadmap: existsSync(join(planningDir, 'ROADMAP.md')),
665
668
  has_milestones: existsSync(join(planningDir, 'MILESTONES.md')),
666
669
  has_config: existsSync(join(planningDir, 'config.json')),
670
+ has_brownfield_change: lifecycle.brownfieldChange.exists,
671
+ non_phase_state: lifecycle.nonPhaseState,
672
+ brownfield_change: lifecycle.brownfieldChange,
673
+ current_phase: lifecycle.currentPhase?.number || null,
674
+ next_phase: lifecycle.nextPhase?.number || null,
675
+ counts: lifecycle.counts,
667
676
  phases: scanPhaseEvidence(planningDir),
677
+ state_dir_name: stateDirName,
668
678
  };
669
679
  return {
670
680
  paths,
@@ -676,6 +686,7 @@ export function inspectWorkContext(cwd = process.cwd()) {
676
686
  evidence,
677
687
  graph,
678
688
  planning,
689
+ migration_notice: migrationNotice,
679
690
  milestone: inspectWorkMilestone(paths.workDir),
680
691
  focus_exists: existsSync(paths.focus),
681
692
  handoff_exists: existsSync(paths.handoff),
@@ -17,7 +17,6 @@ export const WORKFLOWS = [
17
17
  defineWorkflow({ name: 'gsdd-audit-milestone', workflow: 'audit-milestone.md', description: 'Audit a completed milestone - cross-phase integration, requirements coverage, E2E flows' }),
18
18
  defineWorkflow({ name: 'gsdd-complete-milestone', workflow: 'complete-milestone.md', description: 'Complete milestone - archive, evolve spec, collapse roadmap' }),
19
19
  defineWorkflow({ name: 'gsdd-new-milestone', workflow: 'new-milestone.md', description: 'New milestone - gather goals, define requirements, create roadmap phases' }),
20
- defineWorkflow({ name: 'gsdd-plan-milestone-gaps', workflow: 'plan-milestone-gaps.md', description: 'Plan gap closure phases from audit results' }),
21
20
  defineWorkflow({ name: 'gsdd-quick', workflow: 'quick.md', description: 'Quick task - plan and execute a sub-hour task outside the phase cycle' }),
22
21
  defineWorkflow({ name: 'gsdd-pause', workflow: 'pause.md', description: 'Pause work - save session context for seamless resumption' }),
23
22
  defineWorkflow({ name: 'gsdd-resume', workflow: 'resume.md', description: 'Resume work - restore context and route to next action' }),
@@ -1,13 +1,14 @@
1
1
  import { existsSync } from 'fs';
2
2
  import { dirname, join, resolve } from 'path';
3
3
  import { fileURLToPath } from 'url';
4
+ import { hasStateMarker, resolveStateDir } from './state-dir.mjs';
4
5
 
5
6
  function normalizePath(value, cwd) {
6
7
  return resolve(cwd, String(value));
7
8
  }
8
9
 
9
10
  function hasPlanningMarker(root) {
10
- return existsSync(join(root, '.planning', 'config.json')) || existsSync(join(root, '.planning'));
11
+ return hasStateMarker(root);
11
12
  }
12
13
 
13
14
  export function consumeWorkspaceRootArg(rawArgs = []) {
@@ -71,7 +72,7 @@ export function resolveWorkspaceContext(rawArgs = [], { cwd = process.cwd(), env
71
72
  invalid: true,
72
73
  error: 'Usage: --workspace-root <path>',
73
74
  workspaceRoot: resolve(cwd),
74
- planningDir: join(resolve(cwd), '.planning'),
75
+ planningDir: resolveStateDir(resolve(cwd)).dir,
75
76
  };
76
77
  }
77
78
 
@@ -81,9 +82,9 @@ export function resolveWorkspaceContext(rawArgs = [], { cwd = process.cwd(), env
81
82
  return {
82
83
  args,
83
84
  invalid: true,
84
- error: `Workspace root does not contain .planning/: ${workspaceRootArg}`,
85
+ error: `Workspace root does not contain .work/ or .planning/: ${workspaceRootArg}`,
85
86
  workspaceRoot: explicitRoot,
86
- planningDir: join(explicitRoot, '.planning'),
87
+ planningDir: resolveStateDir(explicitRoot).dir,
87
88
  };
88
89
  }
89
90
  }
@@ -105,7 +106,9 @@ export function resolveWorkspaceContext(rawArgs = [], { cwd = process.cwd(), env
105
106
  args,
106
107
  invalid: false,
107
108
  workspaceRoot: candidate,
108
- planningDir: join(candidate, '.planning'),
109
+ planningDir: resolveStateDir(candidate).dir,
110
+ stateDirName: resolveStateDir(candidate).name,
111
+ migrationNotice: resolveStateDir(candidate).migrationNotice,
109
112
  };
110
113
  }
111
114
  }
@@ -115,7 +118,9 @@ export function resolveWorkspaceContext(rawArgs = [], { cwd = process.cwd(), env
115
118
  args,
116
119
  invalid: false,
117
120
  workspaceRoot: fallbackRoot,
118
- planningDir: join(fallbackRoot, '.planning'),
121
+ planningDir: resolveStateDir(fallbackRoot).dir,
122
+ stateDirName: resolveStateDir(fallbackRoot).name,
123
+ migrationNotice: resolveStateDir(fallbackRoot).migrationNotice,
119
124
  };
120
125
  }
121
126
 
@@ -74,6 +74,7 @@
74
74
  61. [Deliberate Subagent Contract](#d61---deliberate-subagent-contract)
75
75
  62. [Repo-Native UI Proof Contract](#d62---repo-native-ui-proof-contract)
76
76
  63. [Computed-First Control Map](#d63---computed-first-control-map)
77
+ 64. [Work-Native Continuity Authority](#d64---work-native-continuity-authority)
77
78
 
78
79
  ---
79
80
 
@@ -2507,7 +2508,7 @@ Sub-gap (b) was closed by D28's `<persistence>` mandate and guarded by G30. Sub-
2507
2508
 
2508
2509
  ## D56 - Executable Brownfield Routing And Widen-Only Escalation
2509
2510
 
2510
- **Decision (2026-04-21):** Brownfield routing should treat `CHANGE.md` as the default operational anchor and allow a surviving `phase` or `quick` checkpoint to outrank it only when one strict-match rule proves the checkpoint is still the active execution surface. From concrete brownfield state, `/gsdd-new-project` remains available only as an intentional widen path into full milestone setup, not as an accidental fallback recommendation.
2511
+ **Decision (2026-04-21; revised 2026-06-30):** Brownfield routing should treat `CHANGE.md` as the default operational anchor and allow a surviving `phase` or `quick` checkpoint to outrank it only when one strict-match rule proves the checkpoint is still the active execution surface. From concrete brownfield state, `/gsdd-new-project` remains available only as an intentional widen path into full milestone setup, not as an accidental fallback recommendation. Active bounded brownfield planning uses the existing `gsdd-plan` workflow with explicit `brownfield-change` authority instead of being forced through unrelated ROADMAP phase membership.
2511
2512
 
2512
2513
  **Context:**
2513
2514
  - D54 defined the bounded `brownfield-change/` contract and D55 established its continuity floor, but the routing matrix above those artifacts was still inconsistent: `progress` could still route back through stale checkpoint residue, `resume` could still make a non-matching checkpoint primary, and `quick` / `map-codebase` / `new-project` still risked implying that full lifecycle initialization was the normal fallback from already-concrete bounded work.
@@ -2524,12 +2525,17 @@ Sub-gap (b) was closed by D28's `<persistence>` mandate and guarded by G30. Sub-
2524
2525
  - `quick` uses it when the bounded change is still undefined or when the user intentionally widens scope
2525
2526
  - `map-codebase` frames it as full lifecycle setup only when the user wants to widen beyond bounded brownfield work
2526
2527
  - `new-project` itself recognizes existing `CHANGE.md` continuity and treats invocation from that state as deliberate promotion, not mistaken routing
2528
+ - Keep `gsdd-plan` as the bounded brownfield planning entrypoint when an active `.planning/brownfield-change/CHANGE.md` exists:
2529
+ - `gsdd next --json` reports `authority: brownfield_change` and routes to `gsdd-plan`
2530
+ - `gsdd-plan` classifies the target before phase preflight
2531
+ - `lifecycle-preflight plan brownfield-change` authorizes the change-folder lane without requiring the active ROADMAP phase to match a PBI or consumer ticket
2527
2532
  - Lock the contract with helper, guard, and generated-surface scenario coverage.
2528
2533
 
2529
2534
  **Why this fits the codebase:**
2530
2535
  - It extends the existing provenance helper and generated-surface workflow model instead of inventing another routing registry or hidden state store.
2531
2536
  - It preserves the D55 one-anchor split: checkpoints stay visible and resume-owned, but they do not silently demote the active brownfield contract unless the repo can prove the checkpoint is still current.
2532
2537
  - It keeps the brownfield lane bounded by removing accidental fallback escalation while still preserving an intentional route into milestone planning.
2538
+ - It fixes the consumer failure mode where PBI/ticket-sized brownfield work was blocked by an unrelated active ROADMAP phase while still avoiding a new workflow or auto-runner surface.
2533
2539
  - It matches the repo's anti-drift posture: ambiguous or weak routing proof fails closed in favor of the current operational anchor.
2534
2540
 
2535
2541
  **Evidence:**
@@ -2549,7 +2555,7 @@ Sub-gap (b) was closed by D28's `<persistence>` mandate and guarded by G30. Sub-
2549
2555
  - `get-shit-done/workflows/progress.md`, `get-shit-done/workflows/resume.md`, `get-shit-done/workflows/quick.md`, `get-shit-done/workflows/new-project.md`
2550
2556
 
2551
2557
  **Consequences:**
2552
- - Brownfield next-step routing now has one deterministic precedence rule instead of five workflow-local interpretations.
2558
+ - Brownfield next-step routing now has one deterministic precedence rule instead of five workflow-local interpretations, and active bounded brownfield planning has one explicit preflight authority.
2553
2559
  - A stale or unrelated `phase` / `quick` checkpoint can remain reviewable without automatically hijacking the current brownfield lane.
2554
2560
  - `/gsdd-new-project` remains part of the system, but as deliberate promotion from concrete brownfield work rather than as a generic fallback.
2555
2561
  - Phase 43 can now focus on bounded growth and milestone handoff instead of first repairing invalid routing recommendations.
@@ -2919,6 +2925,56 @@ Posture compatibility is part of that closeout contract: `repo_closeout` and `ru
2919
2925
  - `closeout-report` is a compact replay/report helper, not `progress`, `verify`, milestone audit, release automation, cleanup, or a dashboard. The source CLI path includes full health diagnostics; the generated helper reports health availability as a typed warning if the full health builder is not present in that helper runtime.
2920
2926
  - Future health hardening can consume the same helper output for stricter reporting, but must avoid turning local annotations into product truth.
2921
2927
 
2928
+ ## D64 - Work-Native Continuity Authority
2929
+
2930
+ **Decision (2026-06-29; revised 2026-06-30):** `.work` becomes the canonical continuity surface for `gsdd next` and future work-native state, while `.planning` remains readable legacy lifecycle input during migration and for existing workflows that still own their write paths. The authority used by routing, preflight, verification, and auto-gate packets must be explicit in machine-readable output. Repo policy, not Workspine itself, decides whether `.work` is committed or local-only; in the Workspine framework repo, `.work` is local dogfood/runtime state and should not be tracked. During migration, `gsdd next` may also route existing `.planning` brownfield-change authority into `gsdd-plan` without treating `.planning` ROADMAP phases as the only valid plan target.
2931
+
2932
+ **Context:**
2933
+ - PR #116 merged the first `gsdd next` / `.work` continuity slice, but the framework repo still had split truth: `.work/milestone` described a locally implemented continuity milestone while `.planning` still described v2.0.0 parallel orchestration and P65/P66.
2934
+ - Old v2.0.0 parallel orchestration is too distant to migrate wholesale. Its useful ideas are partial extraction candidates: write ownership and closeout truth gates move into work-native auto-gate safety now; the PR #113 registry material remains parked for later extraction or rescue.
2935
+ - OpenSpec's change/archive model and LeanSpec's compact spec guidance both reinforce that long-lived state needs clear ownership and small, reviewable truth surfaces rather than scattered status prose.
2936
+ - The cited OpenAI and Anthropic agent-orchestration guidance favors manager-owned orchestration, explicit handoffs, guardrails, evaluator loops, and bounded tool authority rather than an unbounded agent loop.
2937
+ - MCP and agent-tooling security guidance reinforce that tool/resource outputs and local memory surfaces are untrusted inputs unless their provenance, privacy posture, and authority boundary are explicit.
2938
+
2939
+ **Decision:**
2940
+ - Treat `.work` as the source of truth for `gsdd next` continuity routing, focus packets, work-native milestone state, decisions, questions, evidence pointers, dogfood findings, and bounded auto-gate state.
2941
+ - Keep `.planning` readable as legacy lifecycle input. Existing `gsdd-plan`, `gsdd-execute`, `gsdd-verify`, audit, complete-milestone, phase-status, and helper write paths continue to work against `.planning` until each surface is deliberately bridged or migrated.
2942
+ - Require routing and preflight output to report authority explicitly, using scoped values such as `work_milestone`, `planning`, or `blocked/conflict`; `.work` authority must not silently mask repo truth, PR truth, or unrelated `.planning` blockers.
2943
+ - Include `brownfield_change` as an explicit scoped authority for existing bounded-change artifacts. This is a compatibility bridge, not a new lifecycle root: it reuses the current `gsdd-plan` workflow and the brownfield-change folder contract.
2944
+ - Preserve repo/control-map truth as the highest authority for branch, PR, worktree, dirty-state, and delivery claims. `.work` can carry intent and continuity; it cannot convert local prose into integrated repo truth.
2945
+ - Define execute-until-gate as task-bounded automation, not session-bounded autonomy. Auto mode may run typed, reviewed, `type=auto` tasks and bounded verification/repair cycles only until a human gate, verification gap, repeated blocker, authority conflict, trust boundary, or scope expansion stops it.
2946
+ - Keep milestone completion user-owned. `gsdd next` may route to completion approval, but it must not mark a milestone complete autonomously.
2947
+ - Treat `.work` tracking as repo policy. Consumer projects may commit `.work` if that fits their privacy and collaboration model. The Workspine framework repo keeps `.work` local-only because it is dogfooding its own runtime state while developing the framework.
2948
+ - Reframe the old v2.0.0 parallel-orchestration milestone as legacy input rather than the active next milestone. Preserve write-set ownership and closeout truth gates in the new work-native authority milestone; park PR #113/P65 registry material as extract-later.
2949
+
2950
+ **Leverage:**
2951
+ - Lost: the old clean "v2.0.0 parallel PR orchestration next" story and the convenience of treating `.planning` as the only lifecycle state root.
2952
+ - Kept: repo-native files, plain markdown workflow contracts, existing `.planning` compatibility, computed-first control-map authority, plan/execute/verify separation, and human-owned completion.
2953
+ - Gained: a clear continuity authority for `gsdd next`, explicit migration semantics, safer execute-until-gate foundations, repo-policy-driven tracking, a brownfield compatibility bridge for bounded consumer changes, and a typed authority boundary that prevents future auto mode from running on stale or contradictory state.
2954
+
2955
+ **Evidence:**
2956
+ - `bin/lib/next.mjs`, `bin/lib/work-context.mjs`, `bin/lib/lifecycle-preflight.mjs`
2957
+ - `tests/gsdd.next.test.cjs` and `tests/phase.test.cjs`
2958
+ - `README.md` for the public `.work` tracking policy
2959
+ - PR #116, merged 2026-06-29 with merge commit `b91a138c42f2ab3ff7376317031208c7a716decd`: `https://github.com/PatrickSys/workspine/pull/116`
2960
+ - PR #113, still open as the parked registry extraction candidate: `https://github.com/PatrickSys/workspine/pull/113`
2961
+ - GSD comparison sources: `agents/_archive/gsd-roadmapper.md`, `agents/_archive/gsd-planner.md`, `agents/_archive/gsd-executor.md`, and `agents/_archive/gsd-verifier.md`. These preserve lifecycle rigor around requirements, plans, state, execution, and verification, but they do not define a separate work-native `next` authority root; GSDD preserves the rigor while moving agent-facing continuity into `.work`.
2962
+ - OpenSpec docs: `https://openspec.dev/`
2963
+ - LeanSpec docs: `https://www.lean-spec.dev/docs/guide/first-principles`
2964
+ - OpenAI Agents SDK orchestration docs: `https://developers.openai.com/api/docs/guides/agents/orchestration`
2965
+ - OpenAI guardrails and approvals docs: `https://developers.openai.com/api/docs/guides/agents/guardrails-approvals`
2966
+ - OpenAI agent evals docs: `https://developers.openai.com/api/docs/guides/agent-evals`
2967
+ - Anthropic agent engineering guidance: `https://www.anthropic.com/engineering/building-effective-agents`
2968
+ - Anthropic long-running harness guidance: `https://www.anthropic.com/engineering/harness-design-long-running-apps`
2969
+ - Model Context Protocol security guidance: `https://modelcontextprotocol.io/specification/2025-06-18/basic/security_best_practices`
2970
+ - GitHub Copilot repository instructions docs: `https://docs.github.com/en/copilot/concepts/prompting/response-customization`
2971
+
2972
+ **Consequences:**
2973
+ - Future `gsdd next` and auto-gate work should start by reconciling `.work`, `.planning`, repo/control-map, PR truth, and active brownfield-change authority into one conservative next action or one explicit blocker.
2974
+ - Future milestones must not say "P65 shipped, start P66" unless repo, PR, `.work`, and legacy `.planning` truth agree.
2975
+ - Future auto mode must expose typed gates, loop guards, evidence requirements, stop reasons, and authority source in JSON. It must never rely on prose such as "continue autonomously" as execution permission.
2976
+ - Future framework work should not add tracked `.work` runtime state by default. Durable product changes belong in source/design/workflow/test files; `.work` dogfood state remains local unless explicitly promoted.
2977
+
2922
2978
  ---
2923
2979
 
2924
2980
  ## Maintenance
@@ -430,10 +430,11 @@
430
430
  ## D56 — Executable Brownfield Routing And Widen-Only Escalation
431
431
  - `.planning/SPEC.md` (`BROWNFIELD-03`, Current State, key decision row)
432
432
  - `.planning/phases/42-executable-brownfield-routing/42-PLAN.md`
433
- - `bin/lib/provenance.mjs`
433
+ - `bin/lib/provenance.mjs`, `bin/lib/lifecycle-state.mjs`, `bin/lib/next.mjs`, `bin/lib/work-context.mjs`, `bin/lib/lifecycle-preflight.mjs`
434
434
  - `distilled/workflows/progress.md`, `distilled/workflows/resume.md`, `distilled/workflows/quick.md`, `distilled/workflows/map-codebase.md`, `distilled/workflows/new-project.md`
435
+ - `distilled/workflows/plan.md`
435
436
  - `.internal-research/TODO.md`, `.internal-research/gaps.md`
436
- - `tests/phase.test.cjs`, `tests/gsdd.guards.test.cjs`, `tests/gsdd.scenarios.test.cjs`
437
+ - `tests/phase.test.cjs`, `tests/gsdd.next.test.cjs`, `tests/gsdd.init.test.cjs`, `tests/gsdd.guards.test.cjs`, `tests/gsdd.scenarios.test.cjs`
437
438
  - `get-shit-done/workflows/progress.md`, `get-shit-done/workflows/resume.md`, `get-shit-done/workflows/quick.md`, `get-shit-done/workflows/new-project.md`
438
439
 
439
440
  ## D57 — Bounded Brownfield Growth And Context-Preserving Milestone Handoff
@@ -515,6 +516,18 @@
515
516
  - OpenSpec comparison: OpenSpec change archive flow does not own long-running multi-worktree local-state reconciliation as a portable harness surface
516
517
  - Harness sources: https://www.anthropic.com/engineering/harness-design-long-running-apps, https://code.claude.com/docs/en/worktrees, https://developers.openai.com/codex/cloud, https://developers.openai.com/api/docs/guides/agents/orchestration, https://developers.openai.com/api/docs/guides/agents/guardrails-approvals, https://developers.openai.com/api/docs/guides/agent-evals, https://docs.github.com/en/copilot/concepts/agents/cloud-agent/about-cloud-agent, https://agent-browser.dev/sessions, https://developer.chrome.com/docs/devtools/agents
517
518
 
519
+ ## D64 — Work-Native Continuity Authority
520
+ - `bin/lib/next.mjs`, `bin/lib/work-context.mjs`, `bin/lib/lifecycle-preflight.mjs`, `bin/lib/rendering.mjs`
521
+ - `distilled/workflows/plan.md`
522
+ - `tests/gsdd.next.test.cjs`, `tests/phase.test.cjs`, `tests/gsdd.init.test.cjs`, `tests/gsdd.guards.test.cjs`, `tests/gsdd.scenarios.test.cjs`
523
+ - `README.md` for the public `.work` tracking policy
524
+ - PR #116 merged 2026-06-29, merge commit `b91a138c42f2ab3ff7376317031208c7a716decd`: https://github.com/PatrickSys/workspine/pull/116
525
+ - PR #113 remains open as parked registry extraction input: https://github.com/PatrickSys/workspine/pull/113
526
+ - GSD comparison sources: `agents/_archive/gsd-roadmapper.md`, `agents/_archive/gsd-planner.md`, `agents/_archive/gsd-executor.md`, `agents/_archive/gsd-verifier.md`; upstream GSD lifecycle rigor does not define a separate work-native `next` authority root, so GSDD keeps the lifecycle rigor while moving agent-facing continuity into `.work`
527
+ - Spec framework sources: https://openspec.dev/, https://www.lean-spec.dev/docs/guide/first-principles
528
+ - Orchestrator sources: https://developers.openai.com/api/docs/guides/agents/orchestration, https://www.anthropic.com/engineering/building-effective-agents
529
+ - Industry guidance sources: https://developers.openai.com/api/docs/guides/agents/guardrails-approvals, https://developers.openai.com/api/docs/guides/agent-evals, https://www.anthropic.com/engineering/harness-design-long-running-apps, https://modelcontextprotocol.io/specification/2025-06-18/basic/security_best_practices, https://docs.github.com/en/copilot/concepts/prompting/response-customization
530
+
518
531
  ---
519
532
 
520
533
  ## Maintenance