@fission-ai/openspec 1.4.1 → 1.6.0-beta.1

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 (241) hide show
  1. package/README.md +37 -4
  2. package/dist/cli/index.d.ts +5 -0
  3. package/dist/cli/index.js +95 -95
  4. package/dist/commands/change.d.ts +5 -2
  5. package/dist/commands/change.js +23 -55
  6. package/dist/commands/config.js +6 -65
  7. package/dist/commands/context.d.ts +3 -0
  8. package/dist/commands/context.js +155 -0
  9. package/dist/commands/doctor.d.ts +8 -0
  10. package/dist/commands/doctor.js +163 -0
  11. package/dist/commands/shared-gather.d.ts +14 -0
  12. package/dist/commands/shared-gather.js +31 -0
  13. package/dist/commands/shared-output.d.ts +18 -0
  14. package/dist/commands/shared-output.js +61 -0
  15. package/dist/commands/show.d.ts +11 -6
  16. package/dist/commands/show.js +70 -25
  17. package/dist/commands/spec.d.ts +5 -1
  18. package/dist/commands/spec.js +15 -4
  19. package/dist/commands/store.d.ts +3 -0
  20. package/dist/commands/store.js +547 -0
  21. package/dist/commands/validate.d.ts +10 -0
  22. package/dist/commands/validate.js +88 -40
  23. package/dist/commands/workflow/index.d.ts +0 -2
  24. package/dist/commands/workflow/index.js +0 -1
  25. package/dist/commands/workflow/instructions.d.ts +11 -1
  26. package/dist/commands/workflow/instructions.js +71 -22
  27. package/dist/commands/workflow/new-change.d.ts +6 -3
  28. package/dist/commands/workflow/new-change.js +42 -77
  29. package/dist/commands/workflow/shared.d.ts +15 -3
  30. package/dist/commands/workflow/shared.js +20 -3
  31. package/dist/commands/workflow/status.d.ts +2 -0
  32. package/dist/commands/workflow/status.js +19 -17
  33. package/dist/commands/workset-input.d.ts +19 -0
  34. package/dist/commands/workset-input.js +112 -0
  35. package/dist/commands/workset-prompts.d.ts +12 -0
  36. package/dist/commands/workset-prompts.js +143 -0
  37. package/dist/commands/workset.d.ts +25 -0
  38. package/dist/commands/workset.js +446 -0
  39. package/dist/core/archive.d.ts +17 -6
  40. package/dist/core/archive.js +220 -68
  41. package/dist/core/artifact-graph/index.d.ts +1 -1
  42. package/dist/core/artifact-graph/instruction-loader.d.ts +18 -13
  43. package/dist/core/artifact-graph/instruction-loader.js +8 -31
  44. package/dist/core/change-metadata/schema.d.ts +1 -0
  45. package/dist/core/change-metadata/schema.js +4 -2
  46. package/dist/core/change-status-policy.d.ts +5 -18
  47. package/dist/core/change-status-policy.js +2 -37
  48. package/dist/core/command-generation/adapters/bob.js +1 -14
  49. package/dist/core/command-generation/adapters/claude.d.ts +1 -1
  50. package/dist/core/command-generation/adapters/claude.js +4 -15
  51. package/dist/core/command-generation/adapters/cursor.js +1 -14
  52. package/dist/core/command-generation/adapters/index.d.ts +2 -0
  53. package/dist/core/command-generation/adapters/index.js +2 -0
  54. package/dist/core/command-generation/adapters/oh-my-pi.d.ts +21 -0
  55. package/dist/core/command-generation/adapters/oh-my-pi.js +44 -0
  56. package/dist/core/command-generation/adapters/pi.js +1 -14
  57. package/dist/core/command-generation/adapters/trae.d.ts +13 -0
  58. package/dist/core/command-generation/adapters/trae.js +48 -0
  59. package/dist/core/command-generation/adapters/windsurf.js +1 -14
  60. package/dist/core/command-generation/registry.js +4 -0
  61. package/dist/core/command-generation/yaml.d.ts +22 -0
  62. package/dist/core/command-generation/yaml.js +38 -0
  63. package/dist/core/completions/command-registry.js +87 -289
  64. package/dist/core/completions/generators/zsh-generator.js +3 -1
  65. package/dist/core/completions/installers/bash-installer.js +3 -0
  66. package/dist/core/completions/installers/fish-installer.js +8 -0
  67. package/dist/core/completions/installers/powershell-installer.js +30 -2
  68. package/dist/core/completions/installers/zsh-installer.js +3 -0
  69. package/dist/core/completions/shared-flags.d.ts +1 -0
  70. package/dist/core/completions/shared-flags.js +5 -0
  71. package/dist/core/config-schema.d.ts +2 -1
  72. package/dist/core/config-schema.js +26 -0
  73. package/dist/core/config.js +1 -0
  74. package/dist/core/file-state.d.ts +36 -0
  75. package/dist/core/file-state.js +112 -0
  76. package/dist/core/global-config.d.ts +2 -0
  77. package/dist/core/id.d.ts +17 -0
  78. package/dist/core/id.js +30 -0
  79. package/dist/core/index.d.ts +3 -3
  80. package/dist/core/index.js +3 -3
  81. package/dist/core/init.js +27 -6
  82. package/dist/core/list.d.ts +2 -0
  83. package/dist/core/list.js +37 -14
  84. package/dist/core/openers.d.ts +77 -0
  85. package/dist/core/openers.js +251 -0
  86. package/dist/core/openspec-root.d.ts +45 -0
  87. package/dist/core/openspec-root.js +195 -0
  88. package/dist/core/parsers/change-parser.js +2 -1
  89. package/dist/core/parsers/markdown-parser.d.ts +0 -3
  90. package/dist/core/parsers/markdown-parser.js +5 -59
  91. package/dist/core/parsers/requirement-blocks.d.ts +11 -0
  92. package/dist/core/parsers/requirement-blocks.js +42 -8
  93. package/dist/core/parsers/requirement-text.d.ts +55 -0
  94. package/dist/core/parsers/requirement-text.js +136 -0
  95. package/dist/core/planning-home.d.ts +1 -6
  96. package/dist/core/planning-home.js +2 -43
  97. package/dist/core/profile-sync-drift.js +1 -0
  98. package/dist/core/profiles.d.ts +2 -2
  99. package/dist/core/profiles.js +2 -1
  100. package/dist/core/project-config.d.ts +44 -1
  101. package/dist/core/project-config.js +160 -10
  102. package/dist/core/references.d.ts +63 -0
  103. package/dist/core/references.js +310 -0
  104. package/dist/core/relationship-health.d.ts +65 -0
  105. package/dist/core/relationship-health.js +64 -0
  106. package/dist/core/root-selection.d.ts +122 -0
  107. package/dist/core/root-selection.js +337 -0
  108. package/dist/core/schemas/base.schema.js +7 -2
  109. package/dist/core/shared/allowed-tools.d.ts +12 -0
  110. package/dist/core/shared/allowed-tools.js +12 -0
  111. package/dist/core/shared/skill-generation.js +5 -1
  112. package/dist/core/shared/tool-detection.d.ts +2 -2
  113. package/dist/core/shared/tool-detection.js +2 -0
  114. package/dist/core/specs-apply.d.ts +6 -1
  115. package/dist/core/specs-apply.js +41 -5
  116. package/dist/core/store/errors.d.ts +20 -0
  117. package/dist/core/{context-store → store}/errors.js +3 -3
  118. package/dist/core/store/foundation.d.ts +56 -0
  119. package/dist/core/store/foundation.js +251 -0
  120. package/dist/core/store/git.d.ts +23 -0
  121. package/dist/core/store/git.js +137 -0
  122. package/dist/core/{context-store → store}/index.d.ts +0 -1
  123. package/dist/core/{context-store → store}/index.js +0 -1
  124. package/dist/core/store/operations.d.ts +114 -0
  125. package/dist/core/store/operations.js +803 -0
  126. package/dist/core/store/registry.d.ts +58 -0
  127. package/dist/core/store/registry.js +275 -0
  128. package/dist/core/templates/skill-templates.d.ts +1 -0
  129. package/dist/core/templates/skill-templates.js +1 -0
  130. package/dist/core/templates/workflows/apply-change.js +5 -4
  131. package/dist/core/templates/workflows/archive-change.js +5 -4
  132. package/dist/core/templates/workflows/bulk-archive-change.js +5 -4
  133. package/dist/core/templates/workflows/continue-change.js +7 -2
  134. package/dist/core/templates/workflows/explore.js +5 -0
  135. package/dist/core/templates/workflows/ff-change.js +5 -0
  136. package/dist/core/templates/workflows/new-change.js +5 -0
  137. package/dist/core/templates/workflows/onboard.js +4 -1
  138. package/dist/core/templates/workflows/propose.js +5 -0
  139. package/dist/core/templates/workflows/store-selection.d.ts +8 -0
  140. package/dist/core/templates/workflows/store-selection.js +8 -0
  141. package/dist/core/templates/workflows/sync-specs.js +5 -4
  142. package/dist/core/templates/workflows/update-change.d.ts +10 -0
  143. package/dist/core/templates/workflows/update-change.js +167 -0
  144. package/dist/core/templates/workflows/verify-change.js +5 -4
  145. package/dist/core/update.js +4 -4
  146. package/dist/core/validation/validator.d.ts +7 -0
  147. package/dist/core/validation/validator.js +101 -38
  148. package/dist/core/view.js +1 -1
  149. package/dist/core/working-set.d.ts +47 -0
  150. package/dist/core/working-set.js +43 -0
  151. package/dist/core/worksets.d.ts +75 -0
  152. package/dist/core/worksets.js +245 -0
  153. package/dist/core/zod-issues.d.ts +4 -0
  154. package/dist/core/zod-issues.js +10 -0
  155. package/dist/utils/change-metadata.d.ts +1 -0
  156. package/dist/utils/change-metadata.js +1 -1
  157. package/dist/utils/change-utils.js +15 -0
  158. package/dist/utils/file-system.js +36 -17
  159. package/dist/utils/task-progress.d.ts +10 -1
  160. package/dist/utils/task-progress.js +66 -2
  161. package/package.json +26 -23
  162. package/dist/commands/context-store.d.ts +0 -3
  163. package/dist/commands/context-store.js +0 -475
  164. package/dist/commands/initiative.d.ts +0 -13
  165. package/dist/commands/initiative.js +0 -318
  166. package/dist/commands/workflow/initiative-link.d.ts +0 -24
  167. package/dist/commands/workflow/initiative-link.js +0 -47
  168. package/dist/commands/workflow/set-change.d.ts +0 -13
  169. package/dist/commands/workflow/set-change.js +0 -87
  170. package/dist/commands/workspace/context-status.d.ts +0 -4
  171. package/dist/commands/workspace/context-status.js +0 -59
  172. package/dist/commands/workspace/open-target-selection.d.ts +0 -13
  173. package/dist/commands/workspace/open-target-selection.js +0 -146
  174. package/dist/commands/workspace/open-view.d.ts +0 -62
  175. package/dist/commands/workspace/open-view.js +0 -249
  176. package/dist/commands/workspace/open.d.ts +0 -37
  177. package/dist/commands/workspace/open.js +0 -110
  178. package/dist/commands/workspace/opener-selection.d.ts +0 -11
  179. package/dist/commands/workspace/opener-selection.js +0 -98
  180. package/dist/commands/workspace/operations.d.ts +0 -29
  181. package/dist/commands/workspace/operations.js +0 -543
  182. package/dist/commands/workspace/prompt-theme.d.ts +0 -29
  183. package/dist/commands/workspace/prompt-theme.js +0 -24
  184. package/dist/commands/workspace/registration.d.ts +0 -13
  185. package/dist/commands/workspace/registration.js +0 -84
  186. package/dist/commands/workspace/selection.d.ts +0 -8
  187. package/dist/commands/workspace/selection.js +0 -104
  188. package/dist/commands/workspace/setup-prompts.d.ts +0 -13
  189. package/dist/commands/workspace/setup-prompts.js +0 -121
  190. package/dist/commands/workspace/types.d.ts +0 -103
  191. package/dist/commands/workspace/types.js +0 -36
  192. package/dist/commands/workspace.d.ts +0 -5
  193. package/dist/commands/workspace.js +0 -574
  194. package/dist/core/collections/index.d.ts +0 -3
  195. package/dist/core/collections/index.js +0 -3
  196. package/dist/core/collections/initiatives/collection.d.ts +0 -4
  197. package/dist/core/collections/initiatives/collection.js +0 -17
  198. package/dist/core/collections/initiatives/index.d.ts +0 -6
  199. package/dist/core/collections/initiatives/index.js +0 -6
  200. package/dist/core/collections/initiatives/operations.d.ts +0 -49
  201. package/dist/core/collections/initiatives/operations.js +0 -175
  202. package/dist/core/collections/initiatives/resolution.d.ts +0 -87
  203. package/dist/core/collections/initiatives/resolution.js +0 -374
  204. package/dist/core/collections/initiatives/schema.d.ts +0 -41
  205. package/dist/core/collections/initiatives/schema.js +0 -134
  206. package/dist/core/collections/initiatives/templates.d.ts +0 -12
  207. package/dist/core/collections/initiatives/templates.js +0 -90
  208. package/dist/core/collections/runtime.d.ts +0 -46
  209. package/dist/core/collections/runtime.js +0 -194
  210. package/dist/core/context-store/binding.d.ts +0 -53
  211. package/dist/core/context-store/binding.js +0 -197
  212. package/dist/core/context-store/errors.d.ts +0 -20
  213. package/dist/core/context-store/foundation.d.ts +0 -55
  214. package/dist/core/context-store/foundation.js +0 -321
  215. package/dist/core/context-store/operations.d.ts +0 -85
  216. package/dist/core/context-store/operations.js +0 -528
  217. package/dist/core/context-store/registry.d.ts +0 -45
  218. package/dist/core/context-store/registry.js +0 -229
  219. package/dist/core/workspace/foundation.d.ts +0 -67
  220. package/dist/core/workspace/foundation.js +0 -295
  221. package/dist/core/workspace/index.d.ts +0 -8
  222. package/dist/core/workspace/index.js +0 -8
  223. package/dist/core/workspace/legacy-state.d.ts +0 -28
  224. package/dist/core/workspace/legacy-state.js +0 -200
  225. package/dist/core/workspace/link-input.d.ts +0 -9
  226. package/dist/core/workspace/link-input.js +0 -32
  227. package/dist/core/workspace/open-surface.d.ts +0 -45
  228. package/dist/core/workspace/open-surface.js +0 -215
  229. package/dist/core/workspace/openers.d.ts +0 -21
  230. package/dist/core/workspace/openers.js +0 -124
  231. package/dist/core/workspace/registry.d.ts +0 -24
  232. package/dist/core/workspace/registry.js +0 -146
  233. package/dist/core/workspace/skills.d.ts +0 -57
  234. package/dist/core/workspace/skills.js +0 -334
  235. package/dist/core/workspace/state-io.d.ts +0 -10
  236. package/dist/core/workspace/state-io.js +0 -121
  237. package/schemas/workspace-planning/schema.yaml +0 -72
  238. package/schemas/workspace-planning/templates/design.md +0 -33
  239. package/schemas/workspace-planning/templates/proposal.md +0 -28
  240. package/schemas/workspace-planning/templates/spec.md +0 -9
  241. package/schemas/workspace-planning/templates/tasks.md +0 -15
@@ -6,7 +6,7 @@ import { getNestedValue, setNestedValue, deleteNestedValue, coerceValue, formatV
6
6
  import { CORE_WORKFLOWS, ALL_WORKFLOWS, getProfileWorkflows } from '../core/profiles.js';
7
7
  import { OPENSPEC_DIR_NAME } from '../core/config.js';
8
8
  import { hasProjectConfigDrift } from '../core/profile-sync-drift.js';
9
- import { findWorkspaceRoot, hasWorkspaceSkillProfileDrift, readOptionalWorkspaceViewState, } from '../core/workspace/index.js';
9
+ import { isPromptCancellationError } from './shared-output.js';
10
10
  const WORKFLOW_PROMPT_META = {
11
11
  propose: {
12
12
  name: 'Propose change',
@@ -53,10 +53,6 @@ const WORKFLOW_PROMPT_META = {
53
53
  description: 'Guided onboarding flow for OpenSpec',
54
54
  },
55
55
  };
56
- function isPromptCancellationError(error) {
57
- return (error instanceof Error &&
58
- (error.name === 'ExitPromptError' || error.message.includes('force closed the prompt with SIGINT')));
59
- }
60
56
  /**
61
57
  * Resolve the effective current profile state from global config defaults.
62
58
  */
@@ -133,16 +129,6 @@ export function diffProfileState(before, after) {
133
129
  lines,
134
130
  };
135
131
  }
136
- async function resolveWorkspaceConfigProfileContext(cwd = process.cwd()) {
137
- const workspaceRoot = await findWorkspaceRoot(cwd);
138
- if (!workspaceRoot) {
139
- return null;
140
- }
141
- return {
142
- root: workspaceRoot,
143
- commandCwd: cwd,
144
- };
145
- }
146
132
  function maybeWarnProjectConfigDrift(projectDir, state, colorize) {
147
133
  const openspecDir = path.join(projectDir, OPENSPEC_DIR_NAME);
148
134
  if (!fs.existsSync(openspecDir)) {
@@ -153,28 +139,7 @@ function maybeWarnProjectConfigDrift(projectDir, state, colorize) {
153
139
  }
154
140
  console.log(colorize('Warning: Global config is not applied to this project. Run `openspec update` to sync.'));
155
141
  }
156
- async function maybeWarnConfigDrift(state, colorize) {
157
- const workspaceContext = await resolveWorkspaceConfigProfileContext();
158
- if (workspaceContext) {
159
- let viewState = null;
160
- try {
161
- viewState = await readOptionalWorkspaceViewState(workspaceContext.root);
162
- }
163
- catch {
164
- return;
165
- }
166
- if (hasWorkspaceSkillProfileDrift(viewState)) {
167
- console.log(colorize('Warning: Workspace-local agent skills are out of sync with the active global profile. Run `openspec workspace update` to sync.'));
168
- }
169
- return;
170
- }
171
- maybeWarnProjectConfigDrift(process.cwd(), state, colorize);
172
- }
173
- function printConfigProfileApplyGuidance(workspaceContext) {
174
- if (workspaceContext) {
175
- console.log('Config updated. Run `openspec workspace update` to apply it to workspace-local skills.');
176
- return;
177
- }
142
+ function printConfigProfileApplyGuidance() {
178
143
  console.log('Config updated. Run `openspec update` in your projects to apply.');
179
144
  }
180
145
  /**
@@ -418,8 +383,7 @@ export function registerConfigCommand(program) {
418
383
  config.workflows = [...CORE_WORKFLOWS];
419
384
  // Preserve delivery setting
420
385
  saveGlobalConfig(config);
421
- const workspaceContext = await resolveWorkspaceConfigProfileContext();
422
- printConfigProfileApplyGuidance(workspaceContext);
386
+ printConfigProfileApplyGuidance();
423
387
  return;
424
388
  }
425
389
  if (preset) {
@@ -472,7 +436,7 @@ export function registerConfigCommand(program) {
472
436
  });
473
437
  if (action === 'keep') {
474
438
  console.log('No config changes.');
475
- await maybeWarnConfigDrift(currentState, chalk.yellow);
439
+ maybeWarnProjectConfigDrift(process.cwd(), currentState, chalk.yellow);
476
440
  return;
477
441
  }
478
442
  const nextState = {
@@ -541,7 +505,7 @@ export function registerConfigCommand(program) {
541
505
  const diff = diffProfileState(currentState, nextState);
542
506
  if (!diff.hasChanges) {
543
507
  console.log('No config changes.');
544
- await maybeWarnConfigDrift(nextState, chalk.yellow);
508
+ maybeWarnProjectConfigDrift(process.cwd(), nextState, chalk.yellow);
545
509
  return;
546
510
  }
547
511
  console.log(chalk.bold('\nConfig changes:'));
@@ -553,29 +517,6 @@ export function registerConfigCommand(program) {
553
517
  config.delivery = nextState.delivery;
554
518
  config.workflows = nextState.workflows;
555
519
  saveGlobalConfig(config);
556
- const workspaceContext = await resolveWorkspaceConfigProfileContext();
557
- if (workspaceContext) {
558
- const applyNow = await confirm({
559
- message: 'Apply changes to this workspace now?',
560
- default: true,
561
- });
562
- if (applyNow) {
563
- try {
564
- execSync('npx openspec workspace update', {
565
- stdio: 'inherit',
566
- cwd: workspaceContext.commandCwd,
567
- });
568
- console.log('Run `openspec workspace update` in your other workspaces to apply.');
569
- }
570
- catch {
571
- console.error('`openspec workspace update` failed. Please run it manually to apply the profile changes.');
572
- process.exitCode = 1;
573
- }
574
- return;
575
- }
576
- printConfigProfileApplyGuidance(workspaceContext);
577
- return;
578
- }
579
520
  // Check if inside an OpenSpec project
580
521
  const projectDir = process.cwd();
581
522
  const openspecDir = path.join(projectDir, OPENSPEC_DIR_NAME);
@@ -596,7 +537,7 @@ export function registerConfigCommand(program) {
596
537
  return;
597
538
  }
598
539
  }
599
- printConfigProfileApplyGuidance(null);
540
+ printConfigProfileApplyGuidance();
600
541
  }
601
542
  catch (error) {
602
543
  if (isPromptCancellationError(error)) {
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerContextCommand(program: Command): void;
3
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1,155 @@
1
+ /**
2
+ * `openspec context` (slice 4.1): the working set a root's declarations
3
+ * describe, as an agent brief (JSON), a human listing, or an editor
4
+ * view (`--code-workspace`). Assembly is presentation over the Phase 3
5
+ * relationship data; doctor is the health surface. The only write this
6
+ * command can perform is the explicitly requested workspace file.
7
+ */
8
+ import * as fs from 'node:fs';
9
+ import * as path from 'node:path';
10
+ import { Option } from 'commander';
11
+ import { resolveRootForCommand, } from '../core/root-selection.js';
12
+ import { inspectRelationships } from '../core/relationship-health.js';
13
+ import { assembleWorkingSet, buildCodeWorkspaceJson, isAvailableMember, } from '../core/working-set.js';
14
+ import { StoreError } from '../core/store/errors.js';
15
+ import { COMMAND_REGISTRY } from '../core/completions/command-registry.js';
16
+ import { COMMON_FLAGS } from '../core/completions/shared-flags.js';
17
+ import { emitFailure, printJson } from './shared-output.js';
18
+ import { gatherRelationshipData } from './shared-gather.js';
19
+ const FAILURE_PAYLOAD = { root: null, members: [] };
20
+ async function gatherWorkingSet(root) {
21
+ const data = await gatherRelationshipData(root);
22
+ // Reuse the 3.6 composition for member classification; the
23
+ // doctor-only wrong-turn detections and store facts are deliberately
24
+ // absent — doctor is the health surface.
25
+ const health = inspectRelationships({
26
+ root,
27
+ rootHealthy: data.rootInspection.healthy,
28
+ rootStatus: data.rootInspection.diagnostics,
29
+ referenceEntries: data.referenceEntries,
30
+ registryUnreadable: data.registrySnapshot.unreadable,
31
+ });
32
+ return {
33
+ workingSet: assembleWorkingSet({
34
+ root,
35
+ referenceEntries: data.referenceEntries,
36
+ topLevelStatus: health.status,
37
+ }),
38
+ declaredReferenceCount: data.projectConfig?.references?.length ?? 0,
39
+ };
40
+ }
41
+ function memberLine(member) {
42
+ return ` ${member.id} ${member.path}`;
43
+ }
44
+ function printHumanWorkingSet(workingSet, declaredReferenceCount) {
45
+ const rootLabel = workingSet.root.store_id ?? path.basename(workingSet.root.path);
46
+ console.log(`Working context for ${rootLabel} (${workingSet.root.path})`);
47
+ console.log('');
48
+ console.log('OpenSpec root');
49
+ console.log(` ${rootLabel} ${workingSet.root.path}`);
50
+ const availableStores = workingSet.members.filter((member) => member.role === 'referenced_store' && isAvailableMember(member));
51
+ const unavailable = workingSet.members.filter((member) => !isAvailableMember(member));
52
+ if (availableStores.length > 0) {
53
+ console.log('');
54
+ console.log('Referenced stores');
55
+ for (const member of availableStores) {
56
+ console.log(memberLine(member));
57
+ if (member.fetch) {
58
+ console.log(` Fetch: ${member.fetch}`);
59
+ }
60
+ }
61
+ }
62
+ if (workingSet.members.length === 0) {
63
+ console.log('');
64
+ // Self-references are silently omitted from the index; an
65
+ // emptied-by-omission set must not claim nothing was declared.
66
+ console.log(declaredReferenceCount > 0
67
+ ? 'Declared references all resolve to this root; the working set is this root alone.'
68
+ : 'No references declared; the working set is this root alone.');
69
+ }
70
+ if (unavailable.length > 0 || workingSet.status.length > 0) {
71
+ console.log('');
72
+ console.log('Not available on this machine');
73
+ for (const member of unavailable) {
74
+ if (member.status.length === 0) {
75
+ console.log(` - ${member.id}`);
76
+ continue;
77
+ }
78
+ for (const diagnostic of member.status) {
79
+ console.log(` - ${member.id}: ${diagnostic.message}`);
80
+ if (diagnostic.fix) {
81
+ console.log(` Fix: ${diagnostic.fix}`);
82
+ }
83
+ }
84
+ }
85
+ for (const diagnostic of workingSet.status) {
86
+ console.log(` Note: ${diagnostic.message}`);
87
+ if (diagnostic.fix) {
88
+ console.log(` Fix: ${diagnostic.fix}`);
89
+ }
90
+ }
91
+ }
92
+ }
93
+ function writeCodeWorkspace(workingSet, outputPath, force) {
94
+ const resolved = path.resolve(outputPath);
95
+ if (fs.existsSync(resolved) && !force) {
96
+ throw new StoreError(`Refusing to overwrite ${resolved}.`, 'context_file_exists', {
97
+ target: 'context.output',
98
+ fix: `Pass --force to overwrite, or choose a different path.`,
99
+ });
100
+ }
101
+ const parent = path.dirname(resolved);
102
+ if (!fs.existsSync(parent)) {
103
+ throw new StoreError(`Output directory does not exist: ${parent}.`, 'context_output_dir_missing', { target: 'context.output', fix: 'Create the directory first, or choose another path.' });
104
+ }
105
+ const rootName = workingSet.root.store_id ?? path.basename(workingSet.root.path);
106
+ fs.writeFileSync(resolved, buildCodeWorkspaceJson(workingSet, rootName));
107
+ const available = workingSet.members.filter(isAvailableMember).length;
108
+ const skipped = workingSet.members
109
+ .filter((member) => !isAvailableMember(member))
110
+ .map((member) => member.id);
111
+ const summary = skipped.length > 0
112
+ ? `Wrote ${resolved} (${available + 1} folders; not available: ${skipped.join(', ')})`
113
+ : `Wrote ${resolved} (${available + 1} folders)`;
114
+ // stderr keeps JSON stdout pure; for humans it reads inline.
115
+ console.error(summary);
116
+ }
117
+ export function registerContextCommand(program) {
118
+ const description = COMMAND_REGISTRY.find((entry) => entry.name === 'context')?.description ??
119
+ 'Print the working context for the resolved OpenSpec root';
120
+ program
121
+ .command('context')
122
+ .description(description)
123
+ .option('--store <id>', COMMON_FLAGS.store.description)
124
+ .addOption(new Option('--store-path <path>', 'Removed; register the store and use --store').hideHelp())
125
+ .option('--json', 'Output the agent brief as JSON')
126
+ .option('--code-workspace <path>', 'Also write a VS Code workspace file for the set')
127
+ .option('--force', 'Overwrite an existing --code-workspace file')
128
+ .action(async (options) => {
129
+ try {
130
+ const root = await resolveRootForCommand({ store: options.store, storePath: options.storePath }, { json: options.json, failurePayload: FAILURE_PAYLOAD, allowImplicitRoot: false });
131
+ if (!root) {
132
+ return;
133
+ }
134
+ const { workingSet, declaredReferenceCount } = await gatherWorkingSet(root);
135
+ if (options.json) {
136
+ // The write runs FIRST: a write failure must leave stdout
137
+ // holding exactly one JSON document (the failure payload).
138
+ if (options.codeWorkspace) {
139
+ writeCodeWorkspace(workingSet, options.codeWorkspace, options.force === true);
140
+ }
141
+ printJson(workingSet);
142
+ }
143
+ else {
144
+ printHumanWorkingSet(workingSet, declaredReferenceCount);
145
+ if (options.codeWorkspace) {
146
+ writeCodeWorkspace(workingSet, options.codeWorkspace, options.force === true);
147
+ }
148
+ }
149
+ }
150
+ catch (error) {
151
+ emitFailure(options.json, FAILURE_PAYLOAD, error, 'context_failed');
152
+ }
153
+ });
154
+ }
155
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1,8 @@
1
+ /**
2
+ * `openspec doctor` (slice 3.6): the root-scoped relationship-health
3
+ * report. Read-only — it answers "are the roots this work relates to
4
+ * available on this machine?" and never clones, syncs, or repairs.
5
+ */
6
+ import { Command } from 'commander';
7
+ export declare function registerDoctorCommand(program: Command): void;
8
+ //# sourceMappingURL=doctor.d.ts.map
@@ -0,0 +1,163 @@
1
+ /**
2
+ * `openspec doctor` (slice 3.6): the root-scoped relationship-health
3
+ * report. Read-only — it answers "are the roots this work relates to
4
+ * available on this machine?" and never clones, syncs, or repairs.
5
+ */
6
+ import { Option } from 'commander';
7
+ import { resolveRootForCommand, } from '../core/root-selection.js';
8
+ import { readOptionalStoreMetadataState } from '../core/store/foundation.js';
9
+ import { gitOriginUrl, isGitRepositoryAtRoot } from '../core/store/git.js';
10
+ import { classifyOpenSpecDir, readProjectConfig, resolveConfigFilePath, } from '../core/project-config.js';
11
+ import { findRepoPlanningRootSync } from '../core/planning-home.js';
12
+ import { gatherRelationshipData } from './shared-gather.js';
13
+ import { inspectRelationships, } from '../core/relationship-health.js';
14
+ import { COMMAND_REGISTRY } from '../core/completions/command-registry.js';
15
+ import { COMMON_FLAGS } from '../core/completions/shared-flags.js';
16
+ import { emitFailure, printJson } from './shared-output.js';
17
+ import * as path from 'node:path';
18
+ const FAILURE_PAYLOAD = { root: null, store: null, references: [] };
19
+ async function gatherHealth(root) {
20
+ const data = await gatherRelationshipData(root);
21
+ const { registrySnapshot, projectConfig, referenceEntries, rootInspection, } = data;
22
+ const registryUnreadable = registrySnapshot.unreadable;
23
+ const input = {
24
+ root,
25
+ rootHealthy: rootInspection.healthy,
26
+ rootStatus: rootInspection.diagnostics,
27
+ referenceEntries,
28
+ registryUnreadable,
29
+ };
30
+ // Store facts for store-backed roots (explicit --store or declared).
31
+ // Missing/invalid metadata never reaches here: store resolution
32
+ // verifies identity first and fails with the existing taxonomy
33
+ // (recorded amendment - corrupt store.yaml is an exit-1 resolution
34
+ // failure, not a health finding).
35
+ if (root.storeId) {
36
+ const metadata = await readOptionalStoreMetadataState(root.path).catch(() => null);
37
+ // git -C walks UP the tree: probing a non-repo store nested inside
38
+ // another repo would record the ENCLOSING repo's origin.
39
+ const originUrl = (await isGitRepositoryAtRoot(root.path)) ? await gitOriginUrl(root.path) : null;
40
+ input.storeFacts = {
41
+ id: root.storeId,
42
+ metadataPresent: metadata !== null,
43
+ metadataValid: metadata !== null,
44
+ ...(metadata?.remote ? { canonicalRemote: metadata.remote } : {}),
45
+ ...(originUrl ? { originUrl } : {}),
46
+ };
47
+ }
48
+ // The 3.2 both-shapes wrong turn, structured — including a malformed
49
+ // pointer value, which the resolver is silent about on planning-shaped
50
+ // roots.
51
+ if (root.source === 'nearest') {
52
+ const { hasPlanningShape, pointer } = classifyOpenSpecDir(root.path);
53
+ if (hasPlanningShape && pointer.filePath) {
54
+ if (pointer.value !== undefined) {
55
+ input.bothShapesPointer = { value: pointer.value, filePath: pointer.filePath };
56
+ }
57
+ else if (pointer.malformed) {
58
+ input.malformedPointer = { filePath: pointer.filePath, reason: pointer.malformed };
59
+ }
60
+ }
61
+ }
62
+ // The 3.4-recorded inert-pointer wrong turn: the resolved root is the
63
+ // STORE; re-walk to the pointer directory and read ITS config.
64
+ if (root.source === 'declared') {
65
+ const pointerRoot = findRepoPlanningRootSync(process.cwd());
66
+ if (pointerRoot) {
67
+ const pointerConfig = readProjectConfig(pointerRoot);
68
+ const fields = [];
69
+ if (pointerConfig?.references?.length)
70
+ fields.push('references');
71
+ if (fields.length > 0) {
72
+ const filePath = resolveConfigFilePath(pointerRoot) ??
73
+ path.join(pointerRoot, 'openspec', 'config.yaml');
74
+ input.inertPointerDeclarations = { filePath, fields };
75
+ }
76
+ }
77
+ }
78
+ return {
79
+ health: inspectRelationships(input),
80
+ declaredReferenceCount: projectConfig?.references?.length ?? 0,
81
+ };
82
+ }
83
+ function printDiagnosticLines(prefix, status) {
84
+ for (const entry of status) {
85
+ console.log(`${prefix}- ${entry.message}`);
86
+ if (entry.fix) {
87
+ console.log(`${prefix} Fix: ${entry.fix}`);
88
+ }
89
+ }
90
+ }
91
+ function printEntrySection(title, entries, emptyLine, okLine, idOf) {
92
+ console.log('');
93
+ console.log(title);
94
+ if (entries.length === 0) {
95
+ console.log(` ${emptyLine}`);
96
+ return;
97
+ }
98
+ for (const entry of entries) {
99
+ if (entry.status.length === 0) {
100
+ console.log(` - ${okLine(entry)}`);
101
+ continue;
102
+ }
103
+ for (const diagnostic of entry.status) {
104
+ console.log(` - ${idOf(entry)}: ${diagnostic.message}`);
105
+ if (diagnostic.fix) {
106
+ console.log(` Fix: ${diagnostic.fix}`);
107
+ }
108
+ }
109
+ }
110
+ }
111
+ function printHumanHealth(health, declaredReferenceCount) {
112
+ console.log('Doctor');
113
+ console.log('');
114
+ console.log('Root');
115
+ console.log(` Location: ${health.root.path}`);
116
+ console.log(` OpenSpec root: ${health.root.healthy ? 'ok' : 'unhealthy'}`);
117
+ if (health.store) {
118
+ const metadataNote = health.store.metadata.valid ? 'metadata ok' : 'metadata invalid';
119
+ console.log(` Store: ${health.store.id} (${metadataNote})`);
120
+ }
121
+ printDiagnosticLines(' ', [...health.root.status, ...(health.store?.status ?? [])]);
122
+ // "(none declared)" must never lie: self-references are omitted from
123
+ // the index, so an emptied-by-omission list gets its own line.
124
+ const referencesEmptyLine = health.references.length === 0 && declaredReferenceCount > 0
125
+ ? '(declared references all resolve to this root)'
126
+ : '(none declared)';
127
+ printEntrySection('References', health.references, referencesEmptyLine, (entry) => `${entry.store_id}: ok${entry.root ? ` (${entry.root})` : ''}`, (entry) => entry.store_id);
128
+ for (const entry of health.status) {
129
+ console.log('');
130
+ console.log(`Note: ${entry.message}`);
131
+ if (entry.fix) {
132
+ console.log(`Fix: ${entry.fix}`);
133
+ }
134
+ }
135
+ }
136
+ export function registerDoctorCommand(program) {
137
+ const description = COMMAND_REGISTRY.find((entry) => entry.name === 'doctor')?.description ??
138
+ 'Report relationship health for the resolved OpenSpec root';
139
+ program
140
+ .command('doctor')
141
+ .description(description)
142
+ .option('--store <id>', COMMON_FLAGS.store.description)
143
+ .addOption(new Option('--store-path <path>', 'Removed; register the store and use --store').hideHelp())
144
+ .option('--json', 'Output as JSON')
145
+ .action(async (options) => {
146
+ try {
147
+ const root = await resolveRootForCommand({ store: options.store, storePath: options.storePath }, { json: options.json, failurePayload: FAILURE_PAYLOAD, allowImplicitRoot: false });
148
+ if (!root) {
149
+ return;
150
+ }
151
+ const { health, declaredReferenceCount } = await gatherHealth(root);
152
+ if (options.json) {
153
+ printJson(health);
154
+ return;
155
+ }
156
+ printHumanHealth(health, declaredReferenceCount);
157
+ }
158
+ catch (error) {
159
+ emitFailure(options.json, FAILURE_PAYLOAD, error, 'doctor_failed');
160
+ }
161
+ });
162
+ }
163
+ //# sourceMappingURL=doctor.js.map
@@ -0,0 +1,14 @@
1
+ import { type RegistrySnapshot } from '../core/store/registry.js';
2
+ import { type ProjectConfig } from '../core/project-config.js';
3
+ import { type ReferenceIndexEntry } from '../core/references.js';
4
+ import { type OpenSpecRootInspection } from '../core/openspec-root.js';
5
+ import type { ResolvedOpenSpecRoot } from '../core/root-selection.js';
6
+ export interface RelationshipData {
7
+ registrySnapshot: RegistrySnapshot;
8
+ projectConfig: ProjectConfig | null;
9
+ storeConfigPath: string;
10
+ referenceEntries: ReferenceIndexEntry[];
11
+ rootInspection: OpenSpecRootInspection;
12
+ }
13
+ export declare function gatherRelationshipData(root: ResolvedOpenSpecRoot): Promise<RelationshipData>;
14
+ //# sourceMappingURL=shared-gather.d.ts.map
@@ -0,0 +1,31 @@
1
+ /**
2
+ * The relationship-data gather shared by doctor and context (4.1): one
3
+ * registry snapshot, the health-mode reference index, and the root
4
+ * inspection. Doctor layers its health-only inputs (store facts,
5
+ * wrong-turn detection) on top.
6
+ */
7
+ import * as path from 'node:path';
8
+ import { readRegistrySnapshot } from '../core/store/registry.js';
9
+ import { readProjectConfig, resolveConfigFilePath, } from '../core/project-config.js';
10
+ import { assembleReferenceIndex } from '../core/references.js';
11
+ import { inspectOpenSpecRoot } from '../core/openspec-root.js';
12
+ export async function gatherRelationshipData(root) {
13
+ const registrySnapshot = await readRegistrySnapshot();
14
+ const projectConfig = readProjectConfig(root.path);
15
+ const storeConfigPath = resolveConfigFilePath(root.path) ?? path.join(root.path, 'openspec', 'config.yaml');
16
+ const referenceEntries = await assembleReferenceIndex({
17
+ references: projectConfig?.references ?? [],
18
+ resolvedRoot: root,
19
+ includeSpecs: false,
20
+ registryEntries: registrySnapshot.entries,
21
+ });
22
+ const rootInspection = await inspectOpenSpecRoot(root.path);
23
+ return {
24
+ registrySnapshot,
25
+ projectConfig,
26
+ storeConfigPath,
27
+ referenceEntries,
28
+ rootInspection,
29
+ };
30
+ }
31
+ //# sourceMappingURL=shared-gather.js.map
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Shared JSON/failure output plumbing for command groups whose errors
3
+ * carry the StoreDiagnostic envelope. One definition of the failure
4
+ * contract: exit code 1, Error:/Fix: lines in human mode, a status
5
+ * array in JSON mode.
6
+ */
7
+ import { type StoreDiagnostic } from '../core/store/errors.js';
8
+ export declare function printJson(payload: unknown): void;
9
+ export declare function asErrorMessage(error: unknown): string;
10
+ /**
11
+ * @inquirer prompts reject with ExitPromptError on Ctrl-C; commands
12
+ * translate that to `Cancelled.` + exit 130 (third caller extracted
13
+ * this here in slice 7.1).
14
+ */
15
+ export declare function isPromptCancellationError(error: unknown): boolean;
16
+ export declare function asStatus(error: unknown, fallbackCode: string): StoreDiagnostic;
17
+ export declare function emitFailure(json: boolean | undefined, payload: Record<string, unknown>, error: unknown, fallbackCode: string): void;
18
+ //# sourceMappingURL=shared-output.d.ts.map
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Shared JSON/failure output plumbing for command groups whose errors
3
+ * carry the StoreDiagnostic envelope. One definition of the failure
4
+ * contract: exit code 1, Error:/Fix: lines in human mode, a status
5
+ * array in JSON mode.
6
+ */
7
+ import { StoreError } from '../core/store/errors.js';
8
+ export function printJson(payload) {
9
+ console.log(JSON.stringify(payload, null, 2));
10
+ }
11
+ export function asErrorMessage(error) {
12
+ return error instanceof Error ? error.message : String(error);
13
+ }
14
+ /**
15
+ * @inquirer prompts reject with ExitPromptError on Ctrl-C; commands
16
+ * translate that to `Cancelled.` + exit 130 (third caller extracted
17
+ * this here in slice 7.1).
18
+ */
19
+ export function isPromptCancellationError(error) {
20
+ return (error instanceof Error &&
21
+ (error.name === 'ExitPromptError' ||
22
+ error.message.includes('force closed the prompt with SIGINT')));
23
+ }
24
+ export function asStatus(error, fallbackCode) {
25
+ if (error instanceof StoreError) {
26
+ return error.diagnostic;
27
+ }
28
+ // RootSelectionError (and siblings) carry the same envelope without
29
+ // sharing a class hierarchy; duck-type the diagnostic once, here.
30
+ const diagnostic = error.diagnostic;
31
+ if (diagnostic && typeof diagnostic.code === 'string') {
32
+ return diagnostic;
33
+ }
34
+ return {
35
+ severity: 'error',
36
+ code: fallbackCode,
37
+ message: asErrorMessage(error),
38
+ };
39
+ }
40
+ export function emitFailure(json, payload, error, fallbackCode) {
41
+ // Ctrl-C in a prompt is the user's choice, not an error: every
42
+ // command group gets the Cancelled./130 convention through here.
43
+ if (!json && isPromptCancellationError(error)) {
44
+ console.error('Cancelled.');
45
+ process.exitCode = 130;
46
+ return;
47
+ }
48
+ const status = asStatus(error, fallbackCode);
49
+ if (json) {
50
+ const prior = Array.isArray(payload.status) ? payload.status : [];
51
+ printJson({ ...payload, status: [...prior, status] });
52
+ process.exitCode = 1;
53
+ return;
54
+ }
55
+ console.error(`Error: ${status.message}`);
56
+ if (status.fix) {
57
+ console.error(`Fix: ${status.fix}`);
58
+ }
59
+ process.exitCode = 1;
60
+ }
61
+ //# sourceMappingURL=shared-output.js.map
@@ -1,14 +1,19 @@
1
+ interface ShowExecuteOptions {
2
+ json?: boolean;
3
+ type?: string;
4
+ noInteractive?: boolean;
5
+ store?: string;
6
+ storePath?: string;
7
+ [k: string]: any;
8
+ }
1
9
  export declare class ShowCommand {
2
- execute(itemName?: string, options?: {
3
- json?: boolean;
4
- type?: string;
5
- noInteractive?: boolean;
6
- [k: string]: any;
7
- }): Promise<void>;
10
+ execute(itemName?: string, options?: ShowExecuteOptions): Promise<void>;
8
11
  private normalizeType;
12
+ private delegateOptions;
9
13
  private runInteractiveByType;
10
14
  private showDirect;
11
15
  private printNonInteractiveHint;
12
16
  private warnIrrelevantFlags;
13
17
  }
18
+ export {};
14
19
  //# sourceMappingURL=show.d.ts.map