@fission-ai/openspec 1.4.1 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -3
- package/dist/cli/index.d.ts +5 -0
- package/dist/cli/index.js +95 -95
- package/dist/commands/change.d.ts +5 -1
- package/dist/commands/change.js +17 -15
- package/dist/commands/config.js +6 -65
- package/dist/commands/context.d.ts +3 -0
- package/dist/commands/context.js +155 -0
- package/dist/commands/doctor.d.ts +8 -0
- package/dist/commands/doctor.js +163 -0
- package/dist/commands/shared-gather.d.ts +14 -0
- package/dist/commands/shared-gather.js +31 -0
- package/dist/commands/shared-output.d.ts +18 -0
- package/dist/commands/shared-output.js +61 -0
- package/dist/commands/show.d.ts +11 -6
- package/dist/commands/show.js +70 -25
- package/dist/commands/spec.d.ts +5 -1
- package/dist/commands/spec.js +15 -4
- package/dist/commands/store.d.ts +3 -0
- package/dist/commands/store.js +547 -0
- package/dist/commands/validate.d.ts +2 -0
- package/dist/commands/validate.js +75 -39
- package/dist/commands/workflow/index.d.ts +0 -2
- package/dist/commands/workflow/index.js +0 -1
- package/dist/commands/workflow/instructions.d.ts +11 -1
- package/dist/commands/workflow/instructions.js +71 -22
- package/dist/commands/workflow/new-change.d.ts +6 -3
- package/dist/commands/workflow/new-change.js +42 -77
- package/dist/commands/workflow/shared.d.ts +15 -3
- package/dist/commands/workflow/shared.js +20 -3
- package/dist/commands/workflow/status.d.ts +2 -0
- package/dist/commands/workflow/status.js +19 -17
- package/dist/commands/workset-input.d.ts +19 -0
- package/dist/commands/workset-input.js +112 -0
- package/dist/commands/workset-prompts.d.ts +12 -0
- package/dist/commands/workset-prompts.js +143 -0
- package/dist/commands/workset.d.ts +25 -0
- package/dist/commands/workset.js +446 -0
- package/dist/core/archive.d.ts +17 -6
- package/dist/core/archive.js +209 -56
- package/dist/core/artifact-graph/index.d.ts +1 -1
- package/dist/core/artifact-graph/instruction-loader.d.ts +18 -13
- package/dist/core/artifact-graph/instruction-loader.js +8 -31
- package/dist/core/change-metadata/schema.d.ts +1 -0
- package/dist/core/change-metadata/schema.js +4 -2
- package/dist/core/change-status-policy.d.ts +5 -18
- package/dist/core/change-status-policy.js +2 -37
- package/dist/core/command-generation/adapters/bob.js +1 -14
- package/dist/core/command-generation/adapters/claude.js +1 -14
- package/dist/core/command-generation/adapters/cursor.js +1 -14
- package/dist/core/command-generation/adapters/pi.js +1 -14
- package/dist/core/command-generation/adapters/windsurf.js +1 -14
- package/dist/core/command-generation/yaml.d.ts +22 -0
- package/dist/core/command-generation/yaml.js +38 -0
- package/dist/core/completions/command-registry.js +87 -289
- package/dist/core/completions/generators/zsh-generator.js +3 -1
- package/dist/core/completions/installers/bash-installer.js +3 -0
- package/dist/core/completions/installers/fish-installer.js +8 -0
- package/dist/core/completions/installers/powershell-installer.js +30 -2
- package/dist/core/completions/installers/zsh-installer.js +3 -0
- package/dist/core/completions/shared-flags.d.ts +1 -0
- package/dist/core/completions/shared-flags.js +5 -0
- package/dist/core/config-schema.d.ts +2 -1
- package/dist/core/config-schema.js +26 -0
- package/dist/core/file-state.d.ts +36 -0
- package/dist/core/file-state.js +112 -0
- package/dist/core/global-config.d.ts +2 -0
- package/dist/core/id.d.ts +17 -0
- package/dist/core/id.js +30 -0
- package/dist/core/index.d.ts +3 -3
- package/dist/core/index.js +3 -3
- package/dist/core/init.js +24 -4
- package/dist/core/list.d.ts +2 -0
- package/dist/core/list.js +19 -5
- package/dist/core/openers.d.ts +77 -0
- package/dist/core/openers.js +251 -0
- package/dist/core/openspec-root.d.ts +45 -0
- package/dist/core/openspec-root.js +192 -0
- package/dist/core/planning-home.d.ts +1 -6
- package/dist/core/planning-home.js +2 -43
- package/dist/core/project-config.d.ts +44 -1
- package/dist/core/project-config.js +160 -10
- package/dist/core/references.d.ts +63 -0
- package/dist/core/references.js +310 -0
- package/dist/core/relationship-health.d.ts +65 -0
- package/dist/core/relationship-health.js +64 -0
- package/dist/core/root-selection.d.ts +122 -0
- package/dist/core/root-selection.js +337 -0
- package/dist/core/specs-apply.d.ts +6 -1
- package/dist/core/specs-apply.js +6 -4
- package/dist/core/store/errors.d.ts +20 -0
- package/dist/core/{context-store → store}/errors.js +3 -3
- package/dist/core/store/foundation.d.ts +56 -0
- package/dist/core/store/foundation.js +251 -0
- package/dist/core/store/git.d.ts +23 -0
- package/dist/core/store/git.js +137 -0
- package/dist/core/{context-store → store}/index.d.ts +0 -1
- package/dist/core/{context-store → store}/index.js +0 -1
- package/dist/core/store/operations.d.ts +114 -0
- package/dist/core/store/operations.js +783 -0
- package/dist/core/store/registry.d.ts +58 -0
- package/dist/core/store/registry.js +275 -0
- package/dist/core/templates/workflows/apply-change.js +5 -4
- package/dist/core/templates/workflows/archive-change.js +5 -4
- package/dist/core/templates/workflows/bulk-archive-change.js +5 -4
- package/dist/core/templates/workflows/continue-change.js +7 -2
- package/dist/core/templates/workflows/explore.js +5 -0
- package/dist/core/templates/workflows/ff-change.js +5 -0
- package/dist/core/templates/workflows/new-change.js +5 -0
- package/dist/core/templates/workflows/onboard.js +4 -1
- package/dist/core/templates/workflows/propose.js +5 -0
- package/dist/core/templates/workflows/store-selection.d.ts +8 -0
- package/dist/core/templates/workflows/store-selection.js +8 -0
- package/dist/core/templates/workflows/sync-specs.js +5 -4
- package/dist/core/templates/workflows/verify-change.js +5 -4
- package/dist/core/working-set.d.ts +47 -0
- package/dist/core/working-set.js +43 -0
- package/dist/core/worksets.d.ts +75 -0
- package/dist/core/worksets.js +245 -0
- package/dist/core/zod-issues.d.ts +4 -0
- package/dist/core/zod-issues.js +10 -0
- package/dist/utils/change-metadata.d.ts +1 -0
- package/dist/utils/change-metadata.js +1 -1
- package/dist/utils/change-utils.js +15 -0
- package/dist/utils/file-system.js +36 -17
- package/package.json +6 -6
- package/dist/commands/context-store.d.ts +0 -3
- package/dist/commands/context-store.js +0 -475
- package/dist/commands/initiative.d.ts +0 -13
- package/dist/commands/initiative.js +0 -318
- package/dist/commands/workflow/initiative-link.d.ts +0 -24
- package/dist/commands/workflow/initiative-link.js +0 -47
- package/dist/commands/workflow/set-change.d.ts +0 -13
- package/dist/commands/workflow/set-change.js +0 -87
- package/dist/commands/workspace/context-status.d.ts +0 -4
- package/dist/commands/workspace/context-status.js +0 -59
- package/dist/commands/workspace/open-target-selection.d.ts +0 -13
- package/dist/commands/workspace/open-target-selection.js +0 -146
- package/dist/commands/workspace/open-view.d.ts +0 -62
- package/dist/commands/workspace/open-view.js +0 -249
- package/dist/commands/workspace/open.d.ts +0 -37
- package/dist/commands/workspace/open.js +0 -110
- package/dist/commands/workspace/opener-selection.d.ts +0 -11
- package/dist/commands/workspace/opener-selection.js +0 -98
- package/dist/commands/workspace/operations.d.ts +0 -29
- package/dist/commands/workspace/operations.js +0 -543
- package/dist/commands/workspace/prompt-theme.d.ts +0 -29
- package/dist/commands/workspace/prompt-theme.js +0 -24
- package/dist/commands/workspace/registration.d.ts +0 -13
- package/dist/commands/workspace/registration.js +0 -84
- package/dist/commands/workspace/selection.d.ts +0 -8
- package/dist/commands/workspace/selection.js +0 -104
- package/dist/commands/workspace/setup-prompts.d.ts +0 -13
- package/dist/commands/workspace/setup-prompts.js +0 -121
- package/dist/commands/workspace/types.d.ts +0 -103
- package/dist/commands/workspace/types.js +0 -36
- package/dist/commands/workspace.d.ts +0 -5
- package/dist/commands/workspace.js +0 -574
- package/dist/core/collections/index.d.ts +0 -3
- package/dist/core/collections/index.js +0 -3
- package/dist/core/collections/initiatives/collection.d.ts +0 -4
- package/dist/core/collections/initiatives/collection.js +0 -17
- package/dist/core/collections/initiatives/index.d.ts +0 -6
- package/dist/core/collections/initiatives/index.js +0 -6
- package/dist/core/collections/initiatives/operations.d.ts +0 -49
- package/dist/core/collections/initiatives/operations.js +0 -175
- package/dist/core/collections/initiatives/resolution.d.ts +0 -87
- package/dist/core/collections/initiatives/resolution.js +0 -374
- package/dist/core/collections/initiatives/schema.d.ts +0 -41
- package/dist/core/collections/initiatives/schema.js +0 -134
- package/dist/core/collections/initiatives/templates.d.ts +0 -12
- package/dist/core/collections/initiatives/templates.js +0 -90
- package/dist/core/collections/runtime.d.ts +0 -46
- package/dist/core/collections/runtime.js +0 -194
- package/dist/core/context-store/binding.d.ts +0 -53
- package/dist/core/context-store/binding.js +0 -197
- package/dist/core/context-store/errors.d.ts +0 -20
- package/dist/core/context-store/foundation.d.ts +0 -55
- package/dist/core/context-store/foundation.js +0 -321
- package/dist/core/context-store/operations.d.ts +0 -85
- package/dist/core/context-store/operations.js +0 -528
- package/dist/core/context-store/registry.d.ts +0 -45
- package/dist/core/context-store/registry.js +0 -229
- package/dist/core/workspace/foundation.d.ts +0 -67
- package/dist/core/workspace/foundation.js +0 -295
- package/dist/core/workspace/index.d.ts +0 -8
- package/dist/core/workspace/index.js +0 -8
- package/dist/core/workspace/legacy-state.d.ts +0 -28
- package/dist/core/workspace/legacy-state.js +0 -200
- package/dist/core/workspace/link-input.d.ts +0 -9
- package/dist/core/workspace/link-input.js +0 -32
- package/dist/core/workspace/open-surface.d.ts +0 -45
- package/dist/core/workspace/open-surface.js +0 -215
- package/dist/core/workspace/openers.d.ts +0 -21
- package/dist/core/workspace/openers.js +0 -124
- package/dist/core/workspace/registry.d.ts +0 -24
- package/dist/core/workspace/registry.js +0 -146
- package/dist/core/workspace/skills.d.ts +0 -57
- package/dist/core/workspace/skills.js +0 -334
- package/dist/core/workspace/state-io.d.ts +0 -10
- package/dist/core/workspace/state-io.js +0 -121
- package/schemas/workspace-planning/schema.yaml +0 -72
- package/schemas/workspace-planning/templates/design.md +0 -33
- package/schemas/workspace-planning/templates/proposal.md +0 -28
- package/schemas/workspace-planning/templates/spec.md +0 -9
- package/schemas/workspace-planning/templates/tasks.md +0 -15
|
@@ -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
|
package/dist/commands/show.d.ts
CHANGED
|
@@ -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
|
package/dist/commands/show.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { isInteractive } from '../utils/interactive.js';
|
|
2
2
|
import { getActiveChangeIds, getSpecIds } from '../utils/item-discovery.js';
|
|
3
|
+
import { resolveRootForCommand, toRootOutput, withStoreFlag, isStoreSelectedRoot, } from '../core/root-selection.js';
|
|
3
4
|
import { ChangeCommand } from './change.js';
|
|
4
5
|
import { SpecCommand } from './spec.js';
|
|
5
6
|
import { nearestMatches } from '../utils/match.js';
|
|
@@ -7,6 +8,10 @@ const CHANGE_FLAG_KEYS = new Set(['deltasOnly', 'requirementsOnly']);
|
|
|
7
8
|
const SPEC_FLAG_KEYS = new Set(['requirements', 'scenarios', 'requirement']);
|
|
8
9
|
export class ShowCommand {
|
|
9
10
|
async execute(itemName, options = {}) {
|
|
11
|
+
const root = await resolveRootForCommand(options, { json: options.json });
|
|
12
|
+
if (!root) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
10
15
|
const interactive = isInteractive(options);
|
|
11
16
|
const typeOverride = this.normalizeType(options.type);
|
|
12
17
|
if (!itemName) {
|
|
@@ -19,14 +24,14 @@ export class ShowCommand {
|
|
|
19
24
|
{ name: 'Spec', value: 'spec' },
|
|
20
25
|
],
|
|
21
26
|
});
|
|
22
|
-
await this.runInteractiveByType(type, options);
|
|
27
|
+
await this.runInteractiveByType(type, options, root);
|
|
23
28
|
return;
|
|
24
29
|
}
|
|
25
|
-
this.printNonInteractiveHint();
|
|
30
|
+
this.printNonInteractiveHint(root);
|
|
26
31
|
process.exitCode = 1;
|
|
27
32
|
return;
|
|
28
33
|
}
|
|
29
|
-
await this.showDirect(itemName, { typeOverride, options });
|
|
34
|
+
await this.showDirect(itemName, { typeOverride, options, root });
|
|
30
35
|
}
|
|
31
36
|
normalizeType(value) {
|
|
32
37
|
if (!value)
|
|
@@ -36,78 +41,118 @@ export class ShowCommand {
|
|
|
36
41
|
return v;
|
|
37
42
|
return undefined;
|
|
38
43
|
}
|
|
39
|
-
|
|
44
|
+
delegateOptions(root, options) {
|
|
45
|
+
return {
|
|
46
|
+
...options,
|
|
47
|
+
...(options.json ? { rootOutput: toRootOutput(root) } : {}),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
async runInteractiveByType(type, options, root) {
|
|
40
51
|
const { select } = await import('@inquirer/prompts');
|
|
41
52
|
if (type === 'change') {
|
|
42
|
-
const changes = await getActiveChangeIds();
|
|
53
|
+
const changes = await getActiveChangeIds(root.path);
|
|
43
54
|
if (changes.length === 0) {
|
|
44
55
|
console.error('No changes found.');
|
|
45
56
|
process.exitCode = 1;
|
|
46
57
|
return;
|
|
47
58
|
}
|
|
48
59
|
const picked = await select({ message: 'Pick a change', choices: changes.map(id => ({ name: id, value: id })) });
|
|
49
|
-
const cmd = new ChangeCommand();
|
|
50
|
-
await cmd.show(picked, options);
|
|
60
|
+
const cmd = new ChangeCommand(root.path);
|
|
61
|
+
await cmd.show(picked, this.delegateOptions(root, options));
|
|
51
62
|
return;
|
|
52
63
|
}
|
|
53
|
-
const specs = await getSpecIds();
|
|
64
|
+
const specs = await getSpecIds(root.path);
|
|
54
65
|
if (specs.length === 0) {
|
|
55
66
|
console.error('No specs found.');
|
|
56
67
|
process.exitCode = 1;
|
|
57
68
|
return;
|
|
58
69
|
}
|
|
59
70
|
const picked = await select({ message: 'Pick a spec', choices: specs.map(id => ({ name: id, value: id })) });
|
|
60
|
-
const cmd = new SpecCommand();
|
|
61
|
-
await cmd.show(picked, options);
|
|
71
|
+
const cmd = new SpecCommand(root.path);
|
|
72
|
+
await cmd.show(picked, this.delegateOptions(root, options));
|
|
62
73
|
}
|
|
63
74
|
async showDirect(itemName, params) {
|
|
75
|
+
const root = params.root;
|
|
64
76
|
// Optimize lookups when type is pre-specified
|
|
65
77
|
let isChange = false;
|
|
66
78
|
let isSpec = false;
|
|
67
79
|
let changes = [];
|
|
68
80
|
let specs = [];
|
|
69
81
|
if (params.typeOverride === 'change') {
|
|
70
|
-
changes = await getActiveChangeIds();
|
|
82
|
+
changes = await getActiveChangeIds(root.path);
|
|
71
83
|
isChange = changes.includes(itemName);
|
|
72
84
|
}
|
|
73
85
|
else if (params.typeOverride === 'spec') {
|
|
74
|
-
specs = await getSpecIds();
|
|
86
|
+
specs = await getSpecIds(root.path);
|
|
75
87
|
isSpec = specs.includes(itemName);
|
|
76
88
|
}
|
|
77
89
|
else {
|
|
78
|
-
[changes, specs] = await Promise.all([getActiveChangeIds(), getSpecIds()]);
|
|
90
|
+
[changes, specs] = await Promise.all([getActiveChangeIds(root.path), getSpecIds(root.path)]);
|
|
79
91
|
isChange = changes.includes(itemName);
|
|
80
92
|
isSpec = specs.includes(itemName);
|
|
81
93
|
}
|
|
82
94
|
const resolvedType = params.typeOverride ?? (isChange ? 'change' : isSpec ? 'spec' : undefined);
|
|
83
95
|
if (!resolvedType) {
|
|
84
|
-
console.error(`Unknown item '${itemName}'`);
|
|
85
96
|
const suggestions = nearestMatches(itemName, [...changes, ...specs]);
|
|
86
|
-
|
|
87
|
-
|
|
97
|
+
const message = suggestions.length
|
|
98
|
+
? `Unknown item '${itemName}'. Did you mean: ${suggestions.join(', ')}?`
|
|
99
|
+
: `Unknown item '${itemName}'.`;
|
|
100
|
+
if (params.options.json) {
|
|
101
|
+
console.log(JSON.stringify({ status: [{ severity: 'error', code: 'unknown_item', message }] }, null, 2));
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
console.error(message);
|
|
105
|
+
}
|
|
88
106
|
process.exitCode = 1;
|
|
89
107
|
return;
|
|
90
108
|
}
|
|
91
109
|
if (!params.typeOverride && isChange && isSpec) {
|
|
110
|
+
if (params.options.json) {
|
|
111
|
+
console.log(JSON.stringify({
|
|
112
|
+
status: [
|
|
113
|
+
{
|
|
114
|
+
severity: 'error',
|
|
115
|
+
code: 'ambiguous_item',
|
|
116
|
+
message: `Ambiguous item '${itemName}' matches both a change and a spec.`,
|
|
117
|
+
fix: 'Pass --type change|spec.',
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
}, null, 2));
|
|
121
|
+
process.exitCode = 1;
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
92
124
|
console.error(`Ambiguous item '${itemName}' matches both a change and a spec.`);
|
|
93
|
-
|
|
125
|
+
// The noun-form commands are cwd-based and cannot reach a selected store.
|
|
126
|
+
if (isStoreSelectedRoot(root)) {
|
|
127
|
+
console.error('Pass --type change|spec.');
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
console.error('Pass --type change|spec, or use: openspec change show / openspec spec show');
|
|
131
|
+
}
|
|
94
132
|
process.exitCode = 1;
|
|
95
133
|
return;
|
|
96
134
|
}
|
|
97
135
|
this.warnIrrelevantFlags(resolvedType, params.options);
|
|
98
136
|
if (resolvedType === 'change') {
|
|
99
|
-
const cmd = new ChangeCommand();
|
|
100
|
-
await cmd.show(itemName, params.options);
|
|
137
|
+
const cmd = new ChangeCommand(root.path);
|
|
138
|
+
await cmd.show(itemName, this.delegateOptions(root, params.options));
|
|
101
139
|
return;
|
|
102
140
|
}
|
|
103
|
-
const cmd = new SpecCommand();
|
|
104
|
-
await cmd.show(itemName, params.options);
|
|
141
|
+
const cmd = new SpecCommand(root.path);
|
|
142
|
+
await cmd.show(itemName, this.delegateOptions(root, params.options));
|
|
105
143
|
}
|
|
106
|
-
printNonInteractiveHint() {
|
|
144
|
+
printNonInteractiveHint(root) {
|
|
107
145
|
console.error('Nothing to show. Try one of:');
|
|
108
|
-
console.error('
|
|
109
|
-
|
|
110
|
-
|
|
146
|
+
console.error(` ${withStoreFlag(root, 'openspec show <item>')}`);
|
|
147
|
+
if (isStoreSelectedRoot(root)) {
|
|
148
|
+
// The noun-form commands are cwd-based and cannot reach a selected store.
|
|
149
|
+
console.error(` ${withStoreFlag(root, 'openspec show <item> --type change')}`);
|
|
150
|
+
console.error(` ${withStoreFlag(root, 'openspec show <item> --type spec')}`);
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
console.error(' openspec change show');
|
|
154
|
+
console.error(' openspec spec show');
|
|
155
|
+
}
|
|
111
156
|
console.error('Or run in an interactive terminal.');
|
|
112
157
|
}
|
|
113
158
|
warnIrrelevantFlags(type, options) {
|