gmc-openspec 1.1.0 → 1.4.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.
- package/README.md +2 -2
- package/bin/openspec.js +3 -1
- package/dist/cli/index.d.ts +4 -1
- package/dist/cli/index.js +36 -2
- package/dist/commands/config.js +4 -4
- package/dist/commands/context-store.d.ts +3 -0
- package/dist/commands/context-store.js +475 -0
- package/dist/commands/initiative.d.ts +13 -0
- package/dist/commands/initiative.js +318 -0
- package/dist/commands/workflow/index.d.ts +2 -0
- package/dist/commands/workflow/index.js +1 -0
- package/dist/commands/workflow/initiative-link.d.ts +24 -0
- package/dist/commands/workflow/initiative-link.js +47 -0
- package/dist/commands/workflow/instructions.js +10 -2
- package/dist/commands/workflow/new-change.d.ts +4 -0
- package/dist/commands/workflow/new-change.js +72 -23
- package/dist/commands/workflow/set-change.d.ts +13 -0
- package/dist/commands/workflow/set-change.js +87 -0
- package/dist/commands/workflow/shared.d.ts +2 -0
- package/dist/commands/workflow/status.js +3 -0
- package/dist/commands/workspace/context-status.d.ts +4 -0
- package/dist/commands/workspace/context-status.js +59 -0
- package/dist/commands/workspace/open-target-selection.d.ts +13 -0
- package/dist/commands/workspace/open-target-selection.js +146 -0
- package/dist/commands/workspace/open-view.d.ts +62 -0
- package/dist/commands/workspace/open-view.js +249 -0
- package/dist/commands/workspace/open.d.ts +16 -8
- package/dist/commands/workspace/open.js +40 -14
- package/dist/commands/workspace/opener-selection.d.ts +11 -0
- package/dist/commands/workspace/opener-selection.js +98 -0
- package/dist/commands/workspace/operations.d.ts +14 -8
- package/dist/commands/workspace/operations.js +228 -160
- package/dist/commands/workspace/prompt-theme.d.ts +29 -0
- package/dist/commands/workspace/prompt-theme.js +24 -0
- package/dist/commands/workspace/registration.d.ts +13 -0
- package/dist/commands/workspace/registration.js +84 -0
- package/dist/commands/workspace/selection.d.ts +3 -0
- package/dist/commands/workspace/selection.js +42 -40
- package/dist/commands/workspace/setup-prompts.d.ts +13 -0
- package/dist/commands/workspace/setup-prompts.js +121 -0
- package/dist/commands/workspace/types.d.ts +15 -0
- package/dist/commands/workspace.js +59 -340
- package/dist/core/artifact-graph/index.d.ts +2 -1
- package/dist/core/artifact-graph/instruction-loader.d.ts +10 -23
- package/dist/core/artifact-graph/instruction-loader.js +28 -89
- package/dist/core/artifact-graph/types.d.ts +0 -7
- package/dist/core/artifact-graph/types.js +0 -19
- package/dist/core/change-metadata/index.d.ts +2 -0
- package/dist/core/change-metadata/index.js +2 -0
- package/dist/core/change-metadata/schema.d.ts +18 -0
- package/dist/core/change-metadata/schema.js +28 -0
- package/dist/core/change-status-policy.d.ts +50 -0
- package/dist/core/change-status-policy.js +70 -0
- package/dist/core/collections/index.d.ts +3 -0
- package/dist/core/collections/index.js +3 -0
- package/dist/core/collections/initiatives/collection.d.ts +4 -0
- package/dist/core/collections/initiatives/collection.js +17 -0
- package/dist/core/collections/initiatives/index.d.ts +6 -0
- package/dist/core/collections/initiatives/index.js +6 -0
- package/dist/core/collections/initiatives/operations.d.ts +49 -0
- package/dist/core/collections/initiatives/operations.js +175 -0
- package/dist/core/collections/initiatives/resolution.d.ts +87 -0
- package/dist/core/collections/initiatives/resolution.js +374 -0
- package/dist/core/collections/initiatives/schema.d.ts +41 -0
- package/dist/core/collections/initiatives/schema.js +134 -0
- package/dist/core/collections/initiatives/templates.d.ts +12 -0
- package/dist/core/collections/initiatives/templates.js +90 -0
- package/dist/core/collections/runtime.d.ts +46 -0
- package/dist/core/collections/runtime.js +194 -0
- package/dist/core/completions/command-registry.d.ts +1 -5
- package/dist/core/completions/command-registry.js +475 -70
- package/dist/core/completions/shared-flags.d.ts +12 -0
- package/dist/core/completions/shared-flags.js +28 -0
- package/dist/core/config.js +2 -1
- package/dist/core/context-store/binding.d.ts +53 -0
- package/dist/core/context-store/binding.js +197 -0
- package/dist/core/context-store/errors.d.ts +20 -0
- package/dist/core/context-store/errors.js +22 -0
- package/dist/core/context-store/foundation.d.ts +55 -0
- package/dist/core/context-store/foundation.js +321 -0
- package/dist/core/context-store/index.d.ts +6 -0
- package/dist/core/context-store/index.js +6 -0
- package/dist/core/context-store/operations.d.ts +85 -0
- package/dist/core/context-store/operations.js +528 -0
- package/dist/core/context-store/registry.d.ts +45 -0
- package/dist/core/context-store/registry.js +229 -0
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +2 -0
- package/dist/core/planning-home.js +5 -21
- package/dist/core/validation/validator.d.ts +11 -0
- package/dist/core/validation/validator.js +19 -2
- package/dist/core/workspace/foundation.d.ts +28 -48
- package/dist/core/workspace/foundation.js +130 -214
- package/dist/core/workspace/index.d.ts +2 -0
- package/dist/core/workspace/index.js +2 -0
- package/dist/core/workspace/legacy-state.d.ts +28 -0
- package/dist/core/workspace/legacy-state.js +200 -0
- package/dist/core/workspace/open-surface.d.ts +29 -8
- package/dist/core/workspace/open-surface.js +122 -44
- package/dist/core/workspace/openers.js +11 -6
- package/dist/core/workspace/registry.d.ts +24 -0
- package/dist/core/workspace/registry.js +146 -0
- package/dist/core/workspace/skills.d.ts +4 -2
- package/dist/core/workspace/skills.js +2 -2
- package/dist/core/workspace/state-io.d.ts +10 -0
- package/dist/core/workspace/state-io.js +119 -0
- package/dist/utils/change-metadata.d.ts +5 -2
- package/dist/utils/change-metadata.js +6 -12
- package/dist/utils/change-utils.d.ts +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -84,7 +84,7 @@ AI: Archived to openspec/changes/archive/2025-01-23-add-dark-mode/
|
|
|
84
84
|
Install OpenSpec globally:
|
|
85
85
|
|
|
86
86
|
```bash
|
|
87
|
-
npm install -g
|
|
87
|
+
npm install -g gmc-openspec@latest
|
|
88
88
|
```
|
|
89
89
|
|
|
90
90
|
Then navigate to your project directory and initialize:
|
|
@@ -145,7 +145,7 @@ AI coding assistants are powerful but unpredictable when requirements live only
|
|
|
145
145
|
**Upgrade the package**
|
|
146
146
|
|
|
147
147
|
```bash
|
|
148
|
-
npm install -g
|
|
148
|
+
npm install -g gmc-openspec@latest
|
|
149
149
|
```
|
|
150
150
|
|
|
151
151
|
**Refresh agent instructions**
|
package/bin/openspec.js
CHANGED
package/dist/cli/index.d.ts
CHANGED
package/dist/cli/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import { Command } from 'commander';
|
|
|
2
2
|
import { createRequire } from 'module';
|
|
3
3
|
import ora from 'ora';
|
|
4
4
|
import path from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
5
6
|
import { promises as fs } from 'fs';
|
|
6
7
|
import { AI_TOOLS } from '../core/config.js';
|
|
7
8
|
import { UpdateCommand } from '../core/update.js';
|
|
@@ -17,9 +18,11 @@ import { FeedbackCommand } from '../commands/feedback.js';
|
|
|
17
18
|
import { registerConfigCommand } from '../commands/config.js';
|
|
18
19
|
import { registerSchemaCommand } from '../commands/schema.js';
|
|
19
20
|
import { registerWorkspaceCommand, runWorkspaceUpdateForRoot, } from '../commands/workspace.js';
|
|
21
|
+
import { registerContextStoreCommand } from '../commands/context-store.js';
|
|
22
|
+
import { registerInitiativeCommand } from '../commands/initiative.js';
|
|
20
23
|
import { findWorkspaceRoot } from '../core/workspace/index.js';
|
|
21
24
|
import { registerJiraCommand } from '../commands/jira.js';
|
|
22
|
-
import { statusCommand, instructionsCommand, applyInstructionsCommand, templatesCommand, schemasCommand, newChangeCommand, DEFAULT_SCHEMA, } from '../commands/workflow/index.js';
|
|
25
|
+
import { statusCommand, instructionsCommand, applyInstructionsCommand, templatesCommand, schemasCommand, newChangeCommand, setChangeCommand, DEFAULT_SCHEMA, } from '../commands/workflow/index.js';
|
|
23
26
|
import { maybeShowTelemetryNotice, trackCommand, shutdown } from '../telemetry/index.js';
|
|
24
27
|
const program = new Command();
|
|
25
28
|
const require = createRequire(import.meta.url);
|
|
@@ -269,6 +272,8 @@ registerConfigCommand(program);
|
|
|
269
272
|
registerSchemaCommand(program);
|
|
270
273
|
registerWorkspaceCommand(program);
|
|
271
274
|
registerJiraCommand(program);
|
|
275
|
+
registerContextStoreCommand(program);
|
|
276
|
+
registerInitiativeCommand(program);
|
|
272
277
|
// Top-level validate command
|
|
273
278
|
program
|
|
274
279
|
.command('validate [item-name]')
|
|
@@ -479,7 +484,11 @@ newCmd
|
|
|
479
484
|
.option('--description <text>', 'Description to add to README.md')
|
|
480
485
|
.option('--goal <text>', 'Workspace product goal to store with the change')
|
|
481
486
|
.option('--areas <names>', 'Comma-separated affected workspace link names')
|
|
487
|
+
.option('--initiative <id>', 'Link the repo-local change to an initiative')
|
|
488
|
+
.option('--store <id>', 'Context store id for --initiative')
|
|
489
|
+
.option('--store-path <path>', 'Existing local context store root for --initiative')
|
|
482
490
|
.option('--schema <name>', `Workflow schema to use (default: ${DEFAULT_SCHEMA})`)
|
|
491
|
+
.option('--json', 'Output as JSON')
|
|
483
492
|
.action(async (name, options) => {
|
|
484
493
|
try {
|
|
485
494
|
await newChangeCommand(name, options);
|
|
@@ -490,5 +499,30 @@ newCmd
|
|
|
490
499
|
process.exit(1);
|
|
491
500
|
}
|
|
492
501
|
});
|
|
493
|
-
|
|
502
|
+
// Set command group
|
|
503
|
+
const setCmd = program.command('set').description('Set checked-in OpenSpec metadata');
|
|
504
|
+
setCmd
|
|
505
|
+
.command('change <name>')
|
|
506
|
+
.description('Set repo-local change metadata')
|
|
507
|
+
.option('--initiative <id>', 'Link the repo-local change to an initiative')
|
|
508
|
+
.option('--store <id>', 'Context store id for --initiative')
|
|
509
|
+
.option('--store-path <path>', 'Existing local context store root for --initiative')
|
|
510
|
+
.option('--json', 'Output as JSON')
|
|
511
|
+
.action(async (name, options) => {
|
|
512
|
+
try {
|
|
513
|
+
await setChangeCommand(name, options);
|
|
514
|
+
}
|
|
515
|
+
catch (error) {
|
|
516
|
+
console.log();
|
|
517
|
+
ora().fail(`Error: ${error.message}`);
|
|
518
|
+
process.exit(1);
|
|
519
|
+
}
|
|
520
|
+
});
|
|
521
|
+
export { program };
|
|
522
|
+
export function runCli(argv = process.argv) {
|
|
523
|
+
program.parse(argv);
|
|
524
|
+
}
|
|
525
|
+
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
526
|
+
runCli();
|
|
527
|
+
}
|
|
494
528
|
//# sourceMappingURL=index.js.map
|
package/dist/commands/config.js
CHANGED
|
@@ -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,
|
|
9
|
+
import { findWorkspaceRoot, hasWorkspaceSkillProfileDrift, readOptionalWorkspaceViewState, } from '../core/workspace/index.js';
|
|
10
10
|
const WORKFLOW_PROMPT_META = {
|
|
11
11
|
propose: {
|
|
12
12
|
name: 'Propose change',
|
|
@@ -156,14 +156,14 @@ function maybeWarnProjectConfigDrift(projectDir, state, colorize) {
|
|
|
156
156
|
async function maybeWarnConfigDrift(state, colorize) {
|
|
157
157
|
const workspaceContext = await resolveWorkspaceConfigProfileContext();
|
|
158
158
|
if (workspaceContext) {
|
|
159
|
-
let
|
|
159
|
+
let viewState = null;
|
|
160
160
|
try {
|
|
161
|
-
|
|
161
|
+
viewState = await readOptionalWorkspaceViewState(workspaceContext.root);
|
|
162
162
|
}
|
|
163
163
|
catch {
|
|
164
164
|
return;
|
|
165
165
|
}
|
|
166
|
-
if (hasWorkspaceSkillProfileDrift(
|
|
166
|
+
if (hasWorkspaceSkillProfileDrift(viewState)) {
|
|
167
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
168
|
}
|
|
169
169
|
return;
|
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
import * as os from 'node:os';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { ContextStoreError, doctorContextStores, getDefaultContextStoreRoot, listContextStores, prepareContextStoreSetup, prepareContextStoreCleanup, registerExistingContextStore, removeContextStore, setupPreparedContextStore, unregisterContextStore, validateContextStoreId, } from '../core/context-store/index.js';
|
|
4
|
+
import { isInteractive } from '../utils/interactive.js';
|
|
5
|
+
function printJson(payload) {
|
|
6
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
7
|
+
}
|
|
8
|
+
function asErrorMessage(error) {
|
|
9
|
+
return error instanceof Error ? error.message : String(error);
|
|
10
|
+
}
|
|
11
|
+
function appendStatus(payload, status) {
|
|
12
|
+
return {
|
|
13
|
+
...payload,
|
|
14
|
+
status: [...payload.status, status],
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function toStoreOutput(store) {
|
|
18
|
+
return {
|
|
19
|
+
id: store.id,
|
|
20
|
+
root: store.root,
|
|
21
|
+
...(store.metadataPath ? { metadata_path: store.metadataPath } : {}),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function toMutationOutput(result) {
|
|
25
|
+
return {
|
|
26
|
+
context_store: toStoreOutput(result.store),
|
|
27
|
+
registry: {
|
|
28
|
+
path: result.registryCommit.path,
|
|
29
|
+
registered: true,
|
|
30
|
+
},
|
|
31
|
+
git: {
|
|
32
|
+
is_repository: result.git.isRepository,
|
|
33
|
+
initialized: result.git.initialized,
|
|
34
|
+
},
|
|
35
|
+
created_files: result.createdArtifacts,
|
|
36
|
+
status: [],
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function toCleanupOutput(result) {
|
|
40
|
+
return {
|
|
41
|
+
context_store: toStoreOutput(result.store),
|
|
42
|
+
registry: {
|
|
43
|
+
path: result.registryCommit.path,
|
|
44
|
+
removed: result.registryCommit.removed,
|
|
45
|
+
},
|
|
46
|
+
files: {
|
|
47
|
+
deleted: result.files.deleted,
|
|
48
|
+
deleted_path: result.files.deletedPath ?? null,
|
|
49
|
+
left_on_disk: result.files.leftOnDisk ?? null,
|
|
50
|
+
},
|
|
51
|
+
status: result.diagnostics,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function toListOutput(result) {
|
|
55
|
+
return {
|
|
56
|
+
context_stores: result.stores.map(toStoreOutput),
|
|
57
|
+
status: [],
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function toDoctorStoreOutput(store) {
|
|
61
|
+
return {
|
|
62
|
+
...toStoreOutput(store),
|
|
63
|
+
metadata: store.metadata,
|
|
64
|
+
git: {
|
|
65
|
+
is_repository: store.git.isRepository,
|
|
66
|
+
},
|
|
67
|
+
status: store.diagnostics,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function toDoctorOutput(result) {
|
|
71
|
+
return {
|
|
72
|
+
context_stores: result.stores.map(toDoctorStoreOutput),
|
|
73
|
+
status: result.diagnostics,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function asStatus(error) {
|
|
77
|
+
if (error instanceof ContextStoreError) {
|
|
78
|
+
return error.diagnostic;
|
|
79
|
+
}
|
|
80
|
+
const message = asErrorMessage(error);
|
|
81
|
+
return {
|
|
82
|
+
severity: 'error',
|
|
83
|
+
code: 'context_store_error',
|
|
84
|
+
message,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function isPromptCancellationError(error) {
|
|
88
|
+
return (error instanceof Error &&
|
|
89
|
+
(error.name === 'ExitPromptError' || error.message.includes('force closed the prompt with SIGINT')));
|
|
90
|
+
}
|
|
91
|
+
async function shouldInitializeGit(options) {
|
|
92
|
+
if (options.initGit !== undefined) {
|
|
93
|
+
return options.initGit;
|
|
94
|
+
}
|
|
95
|
+
if (options.json || !isInteractive()) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
const { confirm } = await import('@inquirer/prompts');
|
|
99
|
+
return confirm({
|
|
100
|
+
message: 'Initialize Git in this context store?',
|
|
101
|
+
default: true,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
function formatPathForHuman(targetPath) {
|
|
105
|
+
const home = os.homedir();
|
|
106
|
+
const normalizedHome = path.resolve(home);
|
|
107
|
+
const normalizedTarget = path.resolve(targetPath);
|
|
108
|
+
if (normalizedTarget === normalizedHome)
|
|
109
|
+
return '~';
|
|
110
|
+
if (normalizedTarget.startsWith(`${normalizedHome}${path.sep}`)) {
|
|
111
|
+
return `~${path.sep}${path.relative(normalizedHome, normalizedTarget)}`;
|
|
112
|
+
}
|
|
113
|
+
return targetPath;
|
|
114
|
+
}
|
|
115
|
+
async function promptContextStoreId() {
|
|
116
|
+
const { input } = await import('@inquirer/prompts');
|
|
117
|
+
return input({
|
|
118
|
+
message: 'Context store name',
|
|
119
|
+
required: true,
|
|
120
|
+
validate(value) {
|
|
121
|
+
try {
|
|
122
|
+
validateContextStoreId(value);
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
return asErrorMessage(error);
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
async function promptContextStorePath(id) {
|
|
132
|
+
const { input } = await import('@inquirer/prompts');
|
|
133
|
+
const defaultPath = getDefaultContextStoreRoot(id);
|
|
134
|
+
return input({
|
|
135
|
+
message: 'Where should this context store live?',
|
|
136
|
+
default: defaultPath,
|
|
137
|
+
prefill: 'editable',
|
|
138
|
+
required: true,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
function isSetupInsideGitRepositoryError(error) {
|
|
142
|
+
return (error instanceof ContextStoreError &&
|
|
143
|
+
error.diagnostic.code === 'context_store_setup_inside_git_repo');
|
|
144
|
+
}
|
|
145
|
+
async function resolveSetupInput(id, options) {
|
|
146
|
+
const interactive = !options.json && isInteractive();
|
|
147
|
+
if (!id && !interactive) {
|
|
148
|
+
throw new ContextStoreError('Pass a context store name.', 'context_store_setup_id_required', {
|
|
149
|
+
target: 'context_store.id',
|
|
150
|
+
fix: 'openspec context-store setup <id> --path /path/to/context-store --json',
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
const resolvedId = id ? validateContextStoreId(id) : await promptContextStoreId();
|
|
154
|
+
const promptedPath = !id && options.path === undefined
|
|
155
|
+
? await promptContextStorePath(resolvedId)
|
|
156
|
+
: undefined;
|
|
157
|
+
return {
|
|
158
|
+
id: resolvedId,
|
|
159
|
+
path: options.path ?? promptedPath,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
async function prepareSetupInput(input, options) {
|
|
163
|
+
try {
|
|
164
|
+
return await prepareContextStoreSetup(input);
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
if (!isSetupInsideGitRepositoryError(error) || options.json || !isInteractive()) {
|
|
168
|
+
throw error;
|
|
169
|
+
}
|
|
170
|
+
const { confirm } = await import('@inquirer/prompts');
|
|
171
|
+
const shouldContinue = await confirm({
|
|
172
|
+
message: `${asErrorMessage(error)}. Use this location anyway?`,
|
|
173
|
+
default: false,
|
|
174
|
+
});
|
|
175
|
+
if (!shouldContinue) {
|
|
176
|
+
throw new ContextStoreError('Context store setup cancelled.', 'context_store_setup_cancelled', {
|
|
177
|
+
target: 'context_store.root',
|
|
178
|
+
fix: 'Choose another path or rerun setup later.',
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
return prepareContextStoreSetup({
|
|
182
|
+
...input,
|
|
183
|
+
allowInsideGitRepository: true,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
async function confirmSetup(prepared, initGit) {
|
|
188
|
+
const { confirm } = await import('@inquirer/prompts');
|
|
189
|
+
console.log('');
|
|
190
|
+
console.log('OpenSpec will create:');
|
|
191
|
+
console.log('');
|
|
192
|
+
console.log(` Context store: ${prepared.id}`);
|
|
193
|
+
console.log(` Location: ${formatPathForHuman(prepared.root)}`);
|
|
194
|
+
console.log(` Git: ${initGit ? 'initialized' : 'not initialized'}`);
|
|
195
|
+
console.log('');
|
|
196
|
+
const confirmed = await confirm({
|
|
197
|
+
message: 'Create this context store?',
|
|
198
|
+
default: true,
|
|
199
|
+
});
|
|
200
|
+
if (!confirmed) {
|
|
201
|
+
throw new ContextStoreError('Context store setup cancelled.', 'context_store_setup_cancelled', {
|
|
202
|
+
target: 'context_store.root',
|
|
203
|
+
fix: 'Rerun setup when you are ready.',
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
async function confirmRemove(id, root, options) {
|
|
208
|
+
if (options.yes)
|
|
209
|
+
return;
|
|
210
|
+
if (options.json || !isInteractive()) {
|
|
211
|
+
throw new ContextStoreError('Pass --yes to delete context-store files non-interactively.', 'context_store_remove_confirmation_required', {
|
|
212
|
+
target: 'context_store.root',
|
|
213
|
+
fix: `openspec context-store remove ${id} --yes`,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
const { confirm } = await import('@inquirer/prompts');
|
|
217
|
+
const confirmed = await confirm({
|
|
218
|
+
message: `Delete local context-store folder ${formatPathForHuman(root)}?`,
|
|
219
|
+
default: false,
|
|
220
|
+
});
|
|
221
|
+
if (!confirmed) {
|
|
222
|
+
throw new ContextStoreError('Context store remove cancelled.', 'context_store_remove_cancelled', {
|
|
223
|
+
target: 'context_store.root',
|
|
224
|
+
fix: 'Run context-store unregister if you only want to forget the local registration.',
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
function printMutationHuman(title, payload) {
|
|
229
|
+
if (!payload.context_store || !payload.registry || !payload.git) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
console.log(`${title}: ${payload.context_store.id}`);
|
|
233
|
+
console.log(`Location: ${formatPathForHuman(payload.context_store.root)}`);
|
|
234
|
+
console.log('');
|
|
235
|
+
console.log(`Next: ask your agent to create an initiative in ${payload.context_store.id}.`);
|
|
236
|
+
}
|
|
237
|
+
function printCleanupHuman(title, payload) {
|
|
238
|
+
if (!payload.context_store || !payload.registry || !payload.files) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
console.log(`${title}: ${payload.context_store.id}`);
|
|
242
|
+
if (payload.files.deleted_path) {
|
|
243
|
+
console.log(`Deleted: ${formatPathForHuman(payload.files.deleted_path)}`);
|
|
244
|
+
}
|
|
245
|
+
else if (payload.files.left_on_disk) {
|
|
246
|
+
console.log(`Files kept at: ${formatPathForHuman(payload.files.left_on_disk)}`);
|
|
247
|
+
}
|
|
248
|
+
else if (!payload.files.deleted) {
|
|
249
|
+
console.log(`Files were already missing: ${formatPathForHuman(payload.context_store.root)}`);
|
|
250
|
+
}
|
|
251
|
+
for (const status of payload.status) {
|
|
252
|
+
console.log(`${status.severity === 'warning' ? 'Note' : 'Issue'}: ${status.message}`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
function printListHuman(payload) {
|
|
256
|
+
if (payload.context_stores.length === 0) {
|
|
257
|
+
console.log('No context stores registered.');
|
|
258
|
+
console.log('');
|
|
259
|
+
console.log('Next:');
|
|
260
|
+
console.log(' openspec context-store setup team-context');
|
|
261
|
+
console.log(' openspec context-store register /path/to/context-store');
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
console.log(`OpenSpec context stores (${payload.context_stores.length})`);
|
|
265
|
+
console.log('');
|
|
266
|
+
console.log(`${'ID'.padEnd(16)}Location`);
|
|
267
|
+
for (const store of payload.context_stores) {
|
|
268
|
+
console.log(`${store.id.padEnd(16)}${store.root}`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
function formatMetadataHuman(store) {
|
|
272
|
+
if (store.metadata.valid)
|
|
273
|
+
return 'ok';
|
|
274
|
+
if (store.metadata.present === false)
|
|
275
|
+
return 'missing';
|
|
276
|
+
if (store.metadata.present === null)
|
|
277
|
+
return 'unknown';
|
|
278
|
+
return 'invalid';
|
|
279
|
+
}
|
|
280
|
+
function formatDoctorGitHuman(store) {
|
|
281
|
+
if (store.git.is_repository === null)
|
|
282
|
+
return 'unknown';
|
|
283
|
+
return store.git.is_repository ? 'repository detected' : 'not detected';
|
|
284
|
+
}
|
|
285
|
+
function printDoctorHuman(payload) {
|
|
286
|
+
if (payload.context_stores.length === 0) {
|
|
287
|
+
console.log('No context stores registered.');
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
console.log('Context store doctor');
|
|
291
|
+
for (const store of payload.context_stores) {
|
|
292
|
+
console.log('');
|
|
293
|
+
console.log(store.id);
|
|
294
|
+
console.log(` Location: ${store.root}`);
|
|
295
|
+
console.log(` Metadata: ${formatMetadataHuman(store)}`);
|
|
296
|
+
console.log(` Git: ${formatDoctorGitHuman(store)}`);
|
|
297
|
+
if (store.status.length === 0) {
|
|
298
|
+
console.log(' Issues: none');
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
console.log(' Issues:');
|
|
302
|
+
for (const status of store.status) {
|
|
303
|
+
console.log(` - ${status.message}`);
|
|
304
|
+
if (status.fix) {
|
|
305
|
+
console.log(` Fix: ${status.fix}`);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
class ContextStoreCommand {
|
|
311
|
+
async setup(id, options = {}) {
|
|
312
|
+
try {
|
|
313
|
+
const setupInput = await resolveSetupInput(id, options);
|
|
314
|
+
const prepared = await prepareSetupInput(setupInput, options);
|
|
315
|
+
const initGit = await shouldInitializeGit(options);
|
|
316
|
+
if (!options.json && isInteractive()) {
|
|
317
|
+
await confirmSetup(prepared, initGit);
|
|
318
|
+
}
|
|
319
|
+
const payload = toMutationOutput(await setupPreparedContextStore(prepared, {
|
|
320
|
+
initGit,
|
|
321
|
+
}));
|
|
322
|
+
if (options.json) {
|
|
323
|
+
printJson(payload);
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
printMutationHuman('Context store ready', payload);
|
|
327
|
+
}
|
|
328
|
+
catch (error) {
|
|
329
|
+
this.handleFailure(options.json, { context_store: null, registry: null, git: null, created_files: [], status: [] }, error);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
async register(inputPath, options = {}) {
|
|
333
|
+
try {
|
|
334
|
+
const payload = toMutationOutput(await registerExistingContextStore({
|
|
335
|
+
path: inputPath,
|
|
336
|
+
id: options.id,
|
|
337
|
+
}));
|
|
338
|
+
if (options.json) {
|
|
339
|
+
printJson(payload);
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
printMutationHuman('Context store registered', payload);
|
|
343
|
+
}
|
|
344
|
+
catch (error) {
|
|
345
|
+
this.handleFailure(options.json, { context_store: null, registry: null, git: null, created_files: [], status: [] }, error);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
async unregister(id, options = {}) {
|
|
349
|
+
try {
|
|
350
|
+
const payload = toCleanupOutput(await unregisterContextStore({ id }));
|
|
351
|
+
if (options.json) {
|
|
352
|
+
printJson(payload);
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
printCleanupHuman('Unregistered context store', payload);
|
|
356
|
+
}
|
|
357
|
+
catch (error) {
|
|
358
|
+
this.handleFailure(options.json, { context_store: null, registry: null, files: null, status: [] }, error);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
async remove(id, options = {}) {
|
|
362
|
+
try {
|
|
363
|
+
const target = await prepareContextStoreCleanup({ id });
|
|
364
|
+
await confirmRemove(target.id, target.root, options);
|
|
365
|
+
const payload = toCleanupOutput(await removeContextStore(target));
|
|
366
|
+
if (options.json) {
|
|
367
|
+
printJson(payload);
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
printCleanupHuman('Removed context store', payload);
|
|
371
|
+
}
|
|
372
|
+
catch (error) {
|
|
373
|
+
this.handleFailure(options.json, { context_store: null, registry: null, files: null, status: [] }, error);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
async list(options = {}) {
|
|
377
|
+
try {
|
|
378
|
+
const payload = toListOutput(await listContextStores());
|
|
379
|
+
if (options.json) {
|
|
380
|
+
printJson(payload);
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
printListHuman(payload);
|
|
384
|
+
}
|
|
385
|
+
catch (error) {
|
|
386
|
+
this.handleFailure(options.json, { context_stores: [], status: [] }, error);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
async doctor(id, options = {}) {
|
|
390
|
+
try {
|
|
391
|
+
const payload = toDoctorOutput(await doctorContextStores(id));
|
|
392
|
+
if (options.json) {
|
|
393
|
+
printJson(payload);
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
printDoctorHuman(payload);
|
|
397
|
+
}
|
|
398
|
+
catch (error) {
|
|
399
|
+
this.handleFailure(options.json, { context_stores: [], status: [] }, error);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
handleFailure(json, payload, error) {
|
|
403
|
+
if (!json && isPromptCancellationError(error)) {
|
|
404
|
+
console.error('Cancelled.');
|
|
405
|
+
process.exitCode = 130;
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
const status = asStatus(error);
|
|
409
|
+
if (json) {
|
|
410
|
+
printJson(appendStatus(payload, status));
|
|
411
|
+
process.exitCode = 1;
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
console.error(`Error: ${status.message}`);
|
|
415
|
+
if (status.fix) {
|
|
416
|
+
console.error(`Fix: ${status.fix}`);
|
|
417
|
+
}
|
|
418
|
+
process.exitCode = 1;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
export function registerContextStoreCommand(program) {
|
|
422
|
+
const contextStoreCommand = new ContextStoreCommand();
|
|
423
|
+
const contextStore = program
|
|
424
|
+
.command('context-store')
|
|
425
|
+
.description('Set up and inspect local context stores');
|
|
426
|
+
contextStore
|
|
427
|
+
.command('setup [id]')
|
|
428
|
+
.description('Create and register a local context store')
|
|
429
|
+
.option('--path <path>', 'Context store folder path; defaults to OpenSpec managed local data')
|
|
430
|
+
.option('--init-git', 'Initialize a Git repository in the context store')
|
|
431
|
+
.option('--no-init-git', 'Do not initialize a Git repository')
|
|
432
|
+
.option('--json', 'Output as JSON')
|
|
433
|
+
.action(async (id, options) => {
|
|
434
|
+
await contextStoreCommand.setup(id, options);
|
|
435
|
+
});
|
|
436
|
+
contextStore
|
|
437
|
+
.command('register [path]')
|
|
438
|
+
.description('Register an existing local context store')
|
|
439
|
+
.option('--id <id>', 'Context store id; defaults to metadata or folder name')
|
|
440
|
+
.option('--json', 'Output as JSON')
|
|
441
|
+
.action(async (inputPath, options) => {
|
|
442
|
+
await contextStoreCommand.register(inputPath, options);
|
|
443
|
+
});
|
|
444
|
+
contextStore
|
|
445
|
+
.command('unregister <id>')
|
|
446
|
+
.description('Forget a local context-store registration without deleting files')
|
|
447
|
+
.option('--json', 'Output as JSON')
|
|
448
|
+
.action(async (id, options) => {
|
|
449
|
+
await contextStoreCommand.unregister(id, options);
|
|
450
|
+
});
|
|
451
|
+
contextStore
|
|
452
|
+
.command('remove <id>')
|
|
453
|
+
.description('Forget a local context-store registration and delete its local folder')
|
|
454
|
+
.option('--yes', 'Confirm local context-store folder deletion')
|
|
455
|
+
.option('--json', 'Output as JSON')
|
|
456
|
+
.action(async (id, options) => {
|
|
457
|
+
await contextStoreCommand.remove(id, options);
|
|
458
|
+
});
|
|
459
|
+
contextStore
|
|
460
|
+
.command('list')
|
|
461
|
+
.alias('ls')
|
|
462
|
+
.description('List locally registered context stores')
|
|
463
|
+
.option('--json', 'Output as JSON')
|
|
464
|
+
.action(async (options) => {
|
|
465
|
+
await contextStoreCommand.list(options);
|
|
466
|
+
});
|
|
467
|
+
contextStore
|
|
468
|
+
.command('doctor [id]')
|
|
469
|
+
.description('Check local context-store registration and metadata')
|
|
470
|
+
.option('--json', 'Output as JSON')
|
|
471
|
+
.action(async (id, options) => {
|
|
472
|
+
await contextStoreCommand.doctor(id, options);
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
//# sourceMappingURL=context-store.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { type InitiativeResolutionDetails, type InitiativeDiagnostic } from '../core/collections/initiatives/index.js';
|
|
3
|
+
export declare class InitiativeCliError extends Error {
|
|
4
|
+
readonly diagnostic: InitiativeDiagnostic;
|
|
5
|
+
constructor(message: string, code: string, options?: {
|
|
6
|
+
target?: string;
|
|
7
|
+
fix?: string;
|
|
8
|
+
details?: InitiativeResolutionDetails;
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
export declare function initiativeDiagnosticFromError(error: unknown): InitiativeDiagnostic;
|
|
12
|
+
export declare function registerInitiativeCommand(program: Command): void;
|
|
13
|
+
//# sourceMappingURL=initiative.d.ts.map
|