@codewalla_india/openspec 1.3.1 → 1.3.2
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/LICENSE +22 -22
- package/README.md +272 -191
- package/bin/openspec.js +5 -5
- package/dist/cli/index.js +99 -62
- package/dist/commands/change.d.ts +0 -2
- package/dist/commands/change.js +107 -86
- package/dist/commands/config.js +14 -16
- package/dist/commands/doctor.js +9 -4
- package/dist/commands/feedback.js +79 -30
- package/dist/commands/modify.d.ts +26 -0
- package/dist/commands/modify.js +147 -0
- package/dist/commands/schema.js +170 -91
- package/dist/commands/show.js +5 -2
- package/dist/commands/spec.js +49 -29
- package/dist/commands/store.js +15 -21
- package/dist/commands/validate.d.ts +8 -0
- package/dist/commands/validate.js +43 -20
- package/dist/commands/workflow/index.d.ts +2 -2
- package/dist/commands/workflow/index.js +1 -1
- package/dist/commands/workflow/instructions.d.ts +6 -26
- package/dist/commands/workflow/instructions.js +183 -376
- package/dist/commands/workflow/new-change.d.ts +0 -4
- package/dist/commands/workflow/new-change.js +10 -28
- package/dist/commands/workflow/shared.d.ts +34 -19
- package/dist/commands/workflow/shared.js +34 -4
- package/dist/commands/workflow/status.js +10 -32
- package/dist/commands/workflow/templates.js +15 -5
- package/dist/commands/workset.d.ts +2 -2
- package/dist/commands/workset.js +19 -22
- package/dist/comprehension-quiz/fingerprint.d.ts +39 -0
- package/dist/comprehension-quiz/fingerprint.js +84 -0
- package/dist/comprehension-quiz/index.d.ts +42 -0
- package/dist/comprehension-quiz/index.js +47 -0
- package/dist/comprehension-quiz/pass-record.d.ts +44 -0
- package/dist/comprehension-quiz/pass-record.js +98 -0
- package/dist/comprehension-quiz/providers/anthropic.d.ts +34 -0
- package/dist/comprehension-quiz/providers/anthropic.js +53 -0
- package/dist/comprehension-quiz/providers/factory.d.ts +44 -0
- package/dist/comprehension-quiz/providers/factory.js +174 -0
- package/dist/comprehension-quiz/providers/interface.d.ts +59 -0
- package/dist/comprehension-quiz/providers/interface.js +63 -0
- package/dist/comprehension-quiz/providers/local.d.ts +34 -0
- package/dist/comprehension-quiz/providers/local.js +53 -0
- package/dist/comprehension-quiz/providers/ollama.d.ts +34 -0
- package/dist/comprehension-quiz/providers/ollama.js +53 -0
- package/dist/comprehension-quiz/providers/openai.d.ts +34 -0
- package/dist/comprehension-quiz/providers/openai.js +53 -0
- package/dist/comprehension-quiz/question-generator.d.ts +69 -0
- package/dist/comprehension-quiz/question-generator.js +141 -0
- package/dist/comprehension-quiz/quiz-executor.d.ts +70 -0
- package/dist/comprehension-quiz/quiz-executor.js +165 -0
- package/dist/comprehension-quiz/types.d.ts +115 -0
- package/dist/comprehension-quiz/types.js +11 -0
- package/dist/core/archive.d.ts +14 -1
- package/dist/core/archive.js +1350 -190
- package/dist/core/artifact-graph/graph.d.ts +28 -9
- package/dist/core/artifact-graph/graph.js +94 -43
- package/dist/core/artifact-graph/index.d.ts +1 -1
- package/dist/core/artifact-graph/index.js +1 -1
- package/dist/core/artifact-graph/instruction-loader.d.ts +30 -3
- package/dist/core/artifact-graph/instruction-loader.js +70 -12
- package/dist/core/artifact-graph/outputs.d.ts +1 -0
- package/dist/core/artifact-graph/outputs.js +67 -5
- package/dist/core/artifact-graph/resolver.d.ts +14 -0
- package/dist/core/artifact-graph/resolver.js +69 -15
- package/dist/core/artifact-graph/types.js +21 -3
- package/dist/core/available-tools.d.ts +2 -2
- package/dist/core/available-tools.js +14 -3
- package/dist/core/change-metadata/schema.d.ts +2 -0
- package/dist/core/change-metadata/schema.js +13 -0
- package/dist/core/change-status-policy.d.ts +10 -1
- package/dist/core/change-status-policy.js +30 -2
- package/dist/core/command-generation/adapters/amazon-q.d.ts +4 -0
- package/dist/core/command-generation/adapters/amazon-q.js +11 -5
- package/dist/core/command-generation/adapters/antigravity.js +6 -5
- package/dist/core/command-generation/adapters/auggie.js +7 -6
- package/dist/core/command-generation/adapters/bob.d.ts +5 -1
- package/dist/core/command-generation/adapters/bob.js +11 -10
- package/dist/core/command-generation/adapters/claude.d.ts +1 -1
- package/dist/core/command-generation/adapters/claude.js +12 -17
- package/dist/core/command-generation/adapters/cline.js +5 -5
- package/dist/core/command-generation/adapters/codebuddy.js +8 -7
- package/dist/core/command-generation/adapters/continue.js +8 -7
- package/dist/core/command-generation/adapters/costrict.js +7 -6
- package/dist/core/command-generation/adapters/crush.js +9 -9
- package/dist/core/command-generation/adapters/cursor.js +8 -8
- package/dist/core/command-generation/adapters/devin.d.ts +19 -0
- package/dist/core/command-generation/adapters/devin.js +36 -0
- package/dist/core/command-generation/adapters/factory.js +7 -6
- package/dist/core/command-generation/adapters/gemini.js +40 -5
- package/dist/core/command-generation/adapters/github-copilot.js +6 -5
- package/dist/core/command-generation/adapters/iflow.js +9 -8
- package/dist/core/command-generation/adapters/index.d.ts +4 -2
- package/dist/core/command-generation/adapters/index.js +4 -2
- package/dist/core/command-generation/adapters/junie.js +6 -5
- package/dist/core/command-generation/adapters/kilocode.js +1 -1
- package/dist/core/command-generation/adapters/kiro.js +6 -5
- package/dist/core/command-generation/adapters/lingma.js +9 -9
- package/dist/core/command-generation/adapters/oh-my-pi.d.ts +21 -0
- package/dist/core/command-generation/adapters/oh-my-pi.js +42 -0
- package/dist/core/command-generation/adapters/opencode.js +6 -8
- package/dist/core/command-generation/adapters/pi.d.ts +2 -2
- package/dist/core/command-generation/adapters/pi.js +7 -10
- package/dist/core/command-generation/adapters/qoder.js +9 -9
- package/dist/core/command-generation/adapters/qwen.d.ts +7 -3
- package/dist/core/command-generation/adapters/qwen.js +14 -9
- package/dist/core/command-generation/adapters/roocode.d.ts +4 -4
- package/dist/core/command-generation/adapters/roocode.js +9 -9
- package/dist/core/command-generation/adapters/trae.d.ts +13 -0
- package/dist/core/command-generation/adapters/trae.js +28 -0
- package/dist/core/command-generation/adapters/zcode.d.ts +16 -0
- package/dist/core/command-generation/adapters/zcode.js +33 -0
- package/dist/core/command-generation/generator.d.ts +8 -0
- package/dist/core/command-generation/generator.js +15 -1
- package/dist/core/command-generation/index.d.ts +1 -1
- package/dist/core/command-generation/index.js +1 -1
- package/dist/core/command-generation/invocation.d.ts +73 -0
- package/dist/core/command-generation/invocation.js +77 -0
- package/dist/core/command-generation/registry.js +8 -4
- package/dist/core/command-generation/types.d.ts +9 -2
- package/dist/core/command-generation/yaml.d.ts +20 -6
- package/dist/core/command-generation/yaml.js +31 -21
- package/dist/core/command-surface.d.ts +15 -0
- package/dist/core/command-surface.js +33 -0
- package/dist/core/completions/command-registry.js +16 -61
- package/dist/core/completions/generators/bash-generator.js +41 -41
- package/dist/core/completions/generators/fish-generator.js +7 -7
- package/dist/core/completions/generators/powershell-generator.js +44 -37
- package/dist/core/completions/generators/zsh-generator.js +33 -33
- package/dist/core/completions/installers/zsh-installer.d.ts +9 -0
- package/dist/core/completions/installers/zsh-installer.js +20 -4
- package/dist/core/completions/templates/bash-templates.js +24 -24
- package/dist/core/completions/templates/fish-templates.js +38 -38
- package/dist/core/completions/templates/powershell-templates.js +28 -28
- package/dist/core/completions/templates/zsh-templates.js +39 -39
- package/dist/core/config-prompts.d.ts +1 -1
- package/dist/core/config-prompts.js +14 -1
- package/dist/core/config-schema.d.ts +10 -1
- package/dist/core/config-schema.js +67 -1
- package/dist/core/config.d.ts +15 -0
- package/dist/core/config.js +46 -5
- package/dist/core/file-state.js +56 -24
- package/dist/core/github-copilot/cloud-agent.d.ts +95 -0
- package/dist/core/github-copilot/cloud-agent.js +497 -0
- package/dist/core/global-config.d.ts +16 -0
- package/dist/core/init.d.ts +58 -0
- package/dist/core/init.js +474 -73
- package/dist/core/legacy-cleanup.d.ts +59 -0
- package/dist/core/legacy-cleanup.js +253 -24
- package/dist/core/list.js +23 -16
- package/dist/core/migration.d.ts +73 -1
- package/dist/core/migration.js +360 -11
- package/dist/core/modify/conflict-detector.d.ts +26 -0
- package/dist/core/modify/conflict-detector.js +62 -0
- package/dist/core/modify/dependency-propagator.d.ts +38 -0
- package/dist/core/modify/dependency-propagator.js +103 -0
- package/dist/core/modify/history-tracker.d.ts +34 -0
- package/dist/core/modify/history-tracker.js +114 -0
- package/dist/core/modify/index.d.ts +7 -0
- package/dist/core/modify/index.js +7 -0
- package/dist/core/onboarding-commands.d.ts +30 -0
- package/dist/core/onboarding-commands.js +38 -0
- package/dist/core/openspec-root.js +18 -15
- package/dist/core/parsers/change-parser.d.ts +17 -2
- package/dist/core/parsers/change-parser.js +35 -21
- package/dist/core/parsers/code-fence.d.ts +15 -0
- package/dist/core/parsers/code-fence.js +49 -0
- package/dist/core/parsers/markdown-parser.d.ts +0 -3
- package/dist/core/parsers/markdown-parser.js +7 -60
- package/dist/core/parsers/requirement-blocks.d.ts +29 -0
- package/dist/core/parsers/requirement-blocks.js +150 -31
- package/dist/core/parsers/requirement-text.d.ts +48 -0
- package/dist/core/parsers/requirement-text.js +98 -0
- package/dist/core/parsers/spec-structure.d.ts +1 -1
- package/dist/core/parsers/spec-structure.js +20 -30
- package/dist/core/profile-sync-drift.d.ts +0 -8
- package/dist/core/profile-sync-drift.js +49 -50
- package/dist/core/profiles.d.ts +2 -2
- package/dist/core/profiles.js +2 -2
- package/dist/core/project-config.d.ts +27 -18
- package/dist/core/project-config.js +101 -34
- package/dist/core/references.js +42 -4
- package/dist/core/relationship-health.d.ts +8 -0
- package/dist/core/relationship-health.js +13 -0
- package/dist/core/root-selection.d.ts +9 -6
- package/dist/core/root-selection.js +42 -5
- package/dist/core/schemas/base.schema.js +7 -2
- package/dist/core/shared/allowed-tools.d.ts +12 -0
- package/dist/core/shared/allowed-tools.js +12 -0
- package/dist/core/shared/index.d.ts +1 -0
- package/dist/core/shared/index.js +1 -0
- package/dist/core/shared/skill-content-equivalence.d.ts +7 -0
- package/dist/core/shared/skill-content-equivalence.js +41 -0
- package/dist/core/shared/skill-generation.js +17 -13
- package/dist/core/shared/skill-paths.d.ts +13 -0
- package/dist/core/shared/skill-paths.js +23 -0
- package/dist/core/shared/tool-detection.d.ts +27 -7
- package/dist/core/shared/tool-detection.js +167 -37
- package/dist/core/shared-skill-target.d.ts +16 -0
- package/dist/core/shared-skill-target.js +156 -0
- package/dist/core/specs-apply.d.ts +80 -33
- package/dist/core/specs-apply.js +667 -145
- package/dist/core/store/git.d.ts +13 -0
- package/dist/core/store/git.js +24 -0
- package/dist/core/store/operations.js +20 -0
- package/dist/core/templates/skill-templates.d.ts +3 -2
- package/dist/core/templates/skill-templates.js +3 -2
- package/dist/core/templates/workflows/apply-change.d.ts +10 -0
- package/dist/core/templates/workflows/apply-change.js +186 -224
- package/dist/core/templates/workflows/archive-change.js +387 -260
- package/dist/core/templates/workflows/bulk-archive-change.js +655 -475
- package/dist/core/templates/workflows/continue-change.js +210 -219
- package/dist/core/templates/workflows/explore.js +479 -443
- package/dist/core/templates/workflows/feedback.js +97 -97
- package/dist/core/templates/workflows/ff-change.js +200 -191
- package/dist/core/templates/workflows/modify-change.d.ts +4 -1
- package/dist/core/templates/workflows/modify-change.js +237 -115
- package/dist/core/templates/workflows/new-change.js +127 -134
- package/dist/core/templates/workflows/onboard.js +548 -542
- package/dist/core/templates/workflows/propose.js +273 -217
- package/dist/core/templates/workflows/store-selection.d.ts +1 -1
- package/dist/core/templates/workflows/store-selection.js +1 -1
- package/dist/core/templates/workflows/sync-specs.js +500 -273
- package/dist/core/templates/workflows/update-change.d.ts +10 -0
- package/dist/core/templates/workflows/update-change.js +177 -0
- package/dist/core/templates/workflows/verify-change.js +324 -326
- package/dist/core/update.d.ts +40 -5
- package/dist/core/update.js +411 -96
- package/dist/core/validation/constants.d.ts +4 -1
- package/dist/core/validation/constants.js +4 -1
- package/dist/core/validation/plan-validator.d.ts +8 -0
- package/dist/core/validation/plan-validator.js +45 -0
- package/dist/core/validation/task-numbering.d.ts +16 -0
- package/dist/core/validation/task-numbering.js +57 -0
- package/dist/core/validation/validator.d.ts +27 -3
- package/dist/core/validation/validator.js +337 -48
- package/dist/core/version-check.d.ts +152 -0
- package/dist/core/version-check.js +731 -0
- package/dist/core/view.js +13 -18
- package/dist/prompts/searchable-multi-select.js +1 -1
- package/dist/telemetry/caller-detection.d.ts +27 -0
- package/dist/telemetry/caller-detection.js +85 -0
- package/dist/telemetry/caller-detection.test.d.ts +5 -0
- package/dist/telemetry/config.d.ts +48 -2
- package/dist/telemetry/config.js +113 -11
- package/dist/telemetry/config.test.d.ts +5 -0
- package/dist/telemetry/deduplication.d.ts +35 -0
- package/dist/telemetry/deduplication.js +82 -0
- package/dist/telemetry/deduplication.test.d.ts +5 -0
- package/dist/telemetry/identity.d.ts +62 -22
- package/dist/telemetry/identity.js +147 -102
- package/dist/telemetry/index.d.ts +126 -15
- package/dist/telemetry/index.js +569 -37
- package/dist/telemetry/sanitization.d.ts +40 -0
- package/dist/telemetry/sanitization.js +114 -0
- package/dist/telemetry/sanitization.test.d.ts +5 -0
- package/dist/telemetry/throttling.d.ts +20 -0
- package/dist/telemetry/throttling.js +70 -0
- package/dist/ui/welcome-screen.d.ts +9 -1
- package/dist/ui/welcome-screen.js +91 -38
- package/dist/utils/change-metadata.d.ts +43 -0
- package/dist/utils/change-metadata.js +123 -7
- package/dist/utils/change-utils.d.ts +10 -4
- package/dist/utils/change-utils.js +20 -13
- package/dist/utils/ci.d.ts +12 -0
- package/dist/utils/ci.js +16 -0
- package/dist/utils/command-references.d.ts +92 -6
- package/dist/utils/command-references.js +180 -7
- package/dist/utils/date.d.ts +8 -0
- package/dist/utils/date.js +13 -0
- package/dist/utils/file-system.d.ts +10 -0
- package/dist/utils/file-system.js +70 -0
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/utils/interactive.d.ts +23 -0
- package/dist/utils/interactive.js +32 -0
- package/dist/utils/item-discovery.d.ts +19 -0
- package/dist/utils/item-discovery.js +30 -48
- package/dist/utils/shell-detection.d.ts +1 -1
- package/dist/utils/shell-detection.js +64 -11
- package/dist/utils/spec-discovery.d.ts +40 -0
- package/dist/utils/spec-discovery.js +115 -0
- package/dist/utils/task-progress.d.ts +29 -1
- package/dist/utils/task-progress.js +109 -15
- package/package.json +96 -84
- package/schemas/spec-driven/schema.yaml +252 -184
- package/schemas/spec-driven/templates/design.md +19 -19
- package/schemas/spec-driven/templates/plan-template.md +91 -0
- package/schemas/spec-driven/templates/proposal.md +29 -24
- package/schemas/spec-driven/templates/spec.md +11 -8
- package/schemas/spec-driven/templates/tasks.md +9 -9
- package/scripts/postinstall.js +83 -83
- package/dist/core/command-generation/adapters/codex.d.ts +0 -16
- package/dist/core/command-generation/adapters/codex.js +0 -39
- package/dist/core/command-generation/adapters/windsurf.d.ts +0 -14
- package/dist/core/command-generation/adapters/windsurf.js +0 -38
- package/dist/core/comprehension/config.d.ts +0 -20
- package/dist/core/comprehension/config.js +0 -23
- package/dist/core/comprehension/fingerprint.d.ts +0 -14
- package/dist/core/comprehension/fingerprint.js +0 -33
- package/dist/core/comprehension/index.d.ts +0 -57
- package/dist/core/comprehension/index.js +0 -87
- package/dist/core/comprehension/pass-record.d.ts +0 -29
- package/dist/core/comprehension/pass-record.js +0 -64
- package/dist/core/comprehension/stats.d.ts +0 -35
- package/dist/core/comprehension/stats.js +0 -133
- package/dist/core/templates/workflows/comprehension-guidance.d.ts +0 -9
- package/dist/core/templates/workflows/comprehension-guidance.js +0 -73
- package/dist/core/templates/workflows/mcp-guidance.d.ts +0 -13
- package/dist/core/templates/workflows/mcp-guidance.js +0 -131
- package/dist/core/templates/workflows/user-prompt-guidance.d.ts +0 -16
- package/dist/core/templates/workflows/user-prompt-guidance.js +0 -48
- package/dist/telemetry/caller.d.ts +0 -5
- package/dist/telemetry/caller.js +0 -29
- package/dist/telemetry/client.d.ts +0 -27
- package/dist/telemetry/client.js +0 -127
- package/dist/telemetry/command-context.d.ts +0 -13
- package/dist/telemetry/command-context.js +0 -59
- package/dist/telemetry/comprehension.d.ts +0 -44
- package/dist/telemetry/comprehension.js +0 -105
- package/dist/telemetry/content.d.ts +0 -10
- package/dist/telemetry/content.js +0 -56
- package/dist/telemetry/git-stats.d.ts +0 -12
- package/dist/telemetry/git-stats.js +0 -69
- package/dist/telemetry/identify-cache.d.ts +0 -7
- package/dist/telemetry/identify-cache.js +0 -47
- package/dist/telemetry/input.d.ts +0 -17
- package/dist/telemetry/input.js +0 -68
- package/dist/telemetry/marker.d.ts +0 -37
- package/dist/telemetry/marker.js +0 -67
- package/dist/telemetry/workflow.d.ts +0 -85
- package/dist/telemetry/workflow.js +0 -318
- package/schemas/spec-driven/templates/plan.md +0 -19
package/dist/commands/schema.js
CHANGED
|
@@ -2,8 +2,9 @@ import * as fs from 'node:fs';
|
|
|
2
2
|
import * as path from 'node:path';
|
|
3
3
|
import ora from 'ora';
|
|
4
4
|
import { stringify as stringifyYaml } from 'yaml';
|
|
5
|
-
import { getSchemaDir, getProjectSchemasDir, getUserSchemasDir, getPackageSchemasDir, listSchemas, } from '../core/artifact-graph/resolver.js';
|
|
5
|
+
import { getSchemaDir, getProjectSchemasDir, getUserSchemasDir, getPackageSchemasDir, isSchemaDir, listSchemas, } from '../core/artifact-graph/resolver.js';
|
|
6
6
|
import { parseSchema, SchemaValidationError } from '../core/artifact-graph/schema.js';
|
|
7
|
+
import { FileSystemUtils } from '../utils/file-system.js';
|
|
7
8
|
/**
|
|
8
9
|
* Check all three locations for a schema and return which ones exist.
|
|
9
10
|
*/
|
|
@@ -129,21 +130,30 @@ function validateSchema(schemaDir, verbose = false) {
|
|
|
129
130
|
}
|
|
130
131
|
return { valid: false, issues };
|
|
131
132
|
}
|
|
132
|
-
// Check template files exist
|
|
133
|
-
// Templates can be in schemaDir directly or in a templates/ subdirectory
|
|
133
|
+
// Check template files exist in the same directory used at runtime.
|
|
134
134
|
if (verbose) {
|
|
135
135
|
console.log(' Checking template files...');
|
|
136
136
|
}
|
|
137
137
|
for (const artifact of schema.artifacts) {
|
|
138
|
-
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
if (!fs.existsSync(templatePathInTemplates) && !fs.existsSync(templatePathInRoot)) {
|
|
138
|
+
const templatesDir = path.join(schemaDir, 'templates');
|
|
139
|
+
const existingTemplatePath = path.join(templatesDir, artifact.template);
|
|
140
|
+
if (!fs.existsSync(existingTemplatePath)) {
|
|
142
141
|
issues.push({
|
|
143
142
|
level: 'error',
|
|
144
143
|
path: `artifacts.${artifact.id}.template`,
|
|
145
144
|
message: `Template file '${artifact.template}' not found for artifact '${artifact.id}'`,
|
|
146
145
|
});
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
try {
|
|
149
|
+
FileSystemUtils.assertPathWithin(templatesDir, existingTemplatePath);
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
issues.push({
|
|
153
|
+
level: 'error',
|
|
154
|
+
path: `artifacts.${artifact.id}.template`,
|
|
155
|
+
message: `Template file '${artifact.template}' points outside the schema templates directory`,
|
|
156
|
+
});
|
|
147
157
|
}
|
|
148
158
|
}
|
|
149
159
|
// Dependency graph validation is already done by parseSchema
|
|
@@ -162,19 +172,73 @@ function isValidSchemaName(name) {
|
|
|
162
172
|
/**
|
|
163
173
|
* Copy a directory recursively.
|
|
164
174
|
*/
|
|
165
|
-
function
|
|
175
|
+
function resolveSchemaCopyPath(allowedRoot, sourcePath) {
|
|
176
|
+
try {
|
|
177
|
+
const canonicalRoot = fs.realpathSync(allowedRoot);
|
|
178
|
+
const canonicalPath = fs.realpathSync(sourcePath);
|
|
179
|
+
FileSystemUtils.assertPathWithin(canonicalRoot, canonicalPath);
|
|
180
|
+
return canonicalPath;
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
184
|
+
throw new Error(`Cannot fork schema with linked or unsupported entry: ${sourcePath}: ${detail}`, { cause: error });
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
function copyDirRecursive(src, dest, allowedRoot = src, ancestors = new Set()) {
|
|
188
|
+
const canonicalSrc = resolveSchemaCopyPath(allowedRoot, src);
|
|
189
|
+
if (ancestors.has(canonicalSrc)) {
|
|
190
|
+
throw new Error(`Cannot fork schema with a linked directory cycle: ${src}`);
|
|
191
|
+
}
|
|
192
|
+
ancestors.add(canonicalSrc);
|
|
166
193
|
fs.mkdirSync(dest, { recursive: true });
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
194
|
+
try {
|
|
195
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
196
|
+
for (const entry of entries) {
|
|
197
|
+
const srcPath = path.join(src, entry.name);
|
|
198
|
+
const destPath = path.join(dest, entry.name);
|
|
199
|
+
const canonicalEntry = resolveSchemaCopyPath(allowedRoot, srcPath);
|
|
200
|
+
const stats = fs.statSync(canonicalEntry);
|
|
201
|
+
if (stats.isDirectory()) {
|
|
202
|
+
copyDirRecursive(canonicalEntry, destPath, allowedRoot, ancestors);
|
|
203
|
+
}
|
|
204
|
+
else if (stats.isFile()) {
|
|
205
|
+
// Dereference confined links so the fork is an independent schema.
|
|
206
|
+
fs.copyFileSync(canonicalEntry, destPath);
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
throw new Error(`Cannot fork schema with linked or unsupported entry: ${srcPath}`);
|
|
210
|
+
}
|
|
173
211
|
}
|
|
174
|
-
|
|
175
|
-
|
|
212
|
+
}
|
|
213
|
+
finally {
|
|
214
|
+
ancestors.delete(canonicalSrc);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Verifies a schema tree before replacing or creating the fork destination.
|
|
219
|
+
*/
|
|
220
|
+
function assertSchemaTreeCanBeCopied(src, allowedRoot = src, ancestors = new Set()) {
|
|
221
|
+
const canonicalSrc = resolveSchemaCopyPath(allowedRoot, src);
|
|
222
|
+
if (ancestors.has(canonicalSrc)) {
|
|
223
|
+
throw new Error(`Cannot fork schema with a linked directory cycle: ${src}`);
|
|
224
|
+
}
|
|
225
|
+
ancestors.add(canonicalSrc);
|
|
226
|
+
try {
|
|
227
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
228
|
+
const entryPath = path.join(src, entry.name);
|
|
229
|
+
const canonicalEntry = resolveSchemaCopyPath(allowedRoot, entryPath);
|
|
230
|
+
const stats = fs.statSync(canonicalEntry);
|
|
231
|
+
if (stats.isDirectory()) {
|
|
232
|
+
assertSchemaTreeCanBeCopied(canonicalEntry, allowedRoot, ancestors);
|
|
233
|
+
}
|
|
234
|
+
else if (!stats.isFile()) {
|
|
235
|
+
throw new Error(`Cannot fork schema with linked or unsupported entry: ${entryPath}`);
|
|
236
|
+
}
|
|
176
237
|
}
|
|
177
238
|
}
|
|
239
|
+
finally {
|
|
240
|
+
ancestors.delete(canonicalSrc);
|
|
241
|
+
}
|
|
178
242
|
}
|
|
179
243
|
/**
|
|
180
244
|
* Default artifacts with descriptions for schema init.
|
|
@@ -339,7 +403,7 @@ export function registerSchemaCommand(program) {
|
|
|
339
403
|
const schemaResults = [];
|
|
340
404
|
let anyInvalid = false;
|
|
341
405
|
for (const entry of entries) {
|
|
342
|
-
if (!entry
|
|
406
|
+
if (!isSchemaDir(projectSchemasDir, entry))
|
|
343
407
|
continue;
|
|
344
408
|
const schemaDir = path.join(projectSchemasDir, entry.name);
|
|
345
409
|
const schemaPath = path.join(schemaDir, 'schema.yaml');
|
|
@@ -378,9 +442,9 @@ export function registerSchemaCommand(program) {
|
|
|
378
442
|
console.log(` ${issue.level}: ${issue.message}`);
|
|
379
443
|
}
|
|
380
444
|
}
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
445
|
+
}
|
|
446
|
+
if (anyInvalid) {
|
|
447
|
+
process.exitCode = 1;
|
|
384
448
|
}
|
|
385
449
|
return;
|
|
386
450
|
}
|
|
@@ -423,9 +487,11 @@ export function registerSchemaCommand(program) {
|
|
|
423
487
|
for (const issue of result.issues) {
|
|
424
488
|
console.log(` ${issue.level}: ${issue.message}`);
|
|
425
489
|
}
|
|
426
|
-
process.exitCode = 1;
|
|
427
490
|
}
|
|
428
491
|
}
|
|
492
|
+
if (!result.valid) {
|
|
493
|
+
process.exitCode = 1;
|
|
494
|
+
}
|
|
429
495
|
}
|
|
430
496
|
catch (error) {
|
|
431
497
|
if (options?.json) {
|
|
@@ -487,6 +553,9 @@ export function registerSchemaCommand(program) {
|
|
|
487
553
|
// Determine source location
|
|
488
554
|
const sourceResolution = getSchemaResolution(source, projectRoot);
|
|
489
555
|
const sourceLocation = sourceResolution?.source || 'package';
|
|
556
|
+
// Validate the complete source before a forced fork removes anything.
|
|
557
|
+
const trustedSourceDir = fs.realpathSync(sourceDir);
|
|
558
|
+
assertSchemaTreeCanBeCopied(trustedSourceDir);
|
|
490
559
|
// Check destination
|
|
491
560
|
const destinationDir = path.join(getProjectSchemasDir(projectRoot), destinationName);
|
|
492
561
|
if (fs.existsSync(destinationDir)) {
|
|
@@ -513,7 +582,7 @@ export function registerSchemaCommand(program) {
|
|
|
513
582
|
// Copy schema
|
|
514
583
|
if (spinner)
|
|
515
584
|
spinner.start(`Forking '${source}' to '${destinationName}'...`);
|
|
516
|
-
copyDirRecursive(
|
|
585
|
+
copyDirRecursive(trustedSourceDir, destinationDir);
|
|
517
586
|
// Update name in schema.yaml
|
|
518
587
|
const destSchemaPath = path.join(destinationDir, 'schema.yaml');
|
|
519
588
|
const schemaContent = fs.readFileSync(destSchemaPath, 'utf-8');
|
|
@@ -584,8 +653,9 @@ export function registerSchemaCommand(program) {
|
|
|
584
653
|
return;
|
|
585
654
|
}
|
|
586
655
|
const schemaDir = path.join(getProjectSchemasDir(projectRoot), name);
|
|
587
|
-
// Check
|
|
588
|
-
|
|
656
|
+
// Check overwrite permission without mutating the destination
|
|
657
|
+
const schemaExists = fs.existsSync(schemaDir);
|
|
658
|
+
if (schemaExists) {
|
|
589
659
|
if (!options?.force) {
|
|
590
660
|
if (options?.json) {
|
|
591
661
|
console.log(JSON.stringify({
|
|
@@ -601,9 +671,6 @@ export function registerSchemaCommand(program) {
|
|
|
601
671
|
process.exitCode = 1;
|
|
602
672
|
return;
|
|
603
673
|
}
|
|
604
|
-
if (spinner)
|
|
605
|
-
spinner.start(`Removing existing schema '${name}'...`);
|
|
606
|
-
fs.rmSync(schemaDir, { recursive: true });
|
|
607
674
|
}
|
|
608
675
|
// Determine artifacts and description
|
|
609
676
|
let description;
|
|
@@ -625,6 +692,12 @@ export function registerSchemaCommand(program) {
|
|
|
625
692
|
}));
|
|
626
693
|
selectedArtifactIds = await checkbox({
|
|
627
694
|
message: 'Select artifacts to include:',
|
|
695
|
+
theme: {
|
|
696
|
+
icon: {
|
|
697
|
+
checked: '[x]',
|
|
698
|
+
unchecked: '[ ]',
|
|
699
|
+
},
|
|
700
|
+
},
|
|
628
701
|
choices: artifactChoices,
|
|
629
702
|
});
|
|
630
703
|
if (selectedArtifactIds.length === 0) {
|
|
@@ -673,10 +746,6 @@ export function registerSchemaCommand(program) {
|
|
|
673
746
|
selectedArtifactIds = DEFAULT_ARTIFACTS.map((a) => a.id);
|
|
674
747
|
}
|
|
675
748
|
}
|
|
676
|
-
// Create schema directory
|
|
677
|
-
if (spinner)
|
|
678
|
-
spinner.start(`Creating schema '${name}'...`);
|
|
679
|
-
fs.mkdirSync(schemaDir, { recursive: true });
|
|
680
749
|
// Build artifacts array with proper dependencies
|
|
681
750
|
const selectedArtifacts = selectedArtifactIds.map((id) => {
|
|
682
751
|
const template = DEFAULT_ARTIFACTS.find((a) => a.id === id);
|
|
@@ -718,6 +787,16 @@ export function registerSchemaCommand(program) {
|
|
|
718
787
|
tracks: 'tasks.md',
|
|
719
788
|
};
|
|
720
789
|
}
|
|
790
|
+
// Replace only after all inputs have been collected and validated
|
|
791
|
+
if (schemaExists) {
|
|
792
|
+
if (spinner)
|
|
793
|
+
spinner.start(`Removing existing schema '${name}'...`);
|
|
794
|
+
fs.rmSync(schemaDir, { recursive: true });
|
|
795
|
+
}
|
|
796
|
+
// Create schema directory
|
|
797
|
+
if (spinner)
|
|
798
|
+
spinner.start(`Creating schema '${name}'...`);
|
|
799
|
+
fs.mkdirSync(schemaDir, { recursive: true });
|
|
721
800
|
fs.writeFileSync(path.join(schemaDir, 'schema.yaml'), stringifyYaml(schema));
|
|
722
801
|
// Create template files in templates/ subdirectory (standard location)
|
|
723
802
|
const templatesDir = path.join(schemaDir, 'templates');
|
|
@@ -795,74 +874,74 @@ export function registerSchemaCommand(program) {
|
|
|
795
874
|
function createDefaultTemplate(artifactId) {
|
|
796
875
|
switch (artifactId) {
|
|
797
876
|
case 'proposal':
|
|
798
|
-
return `## Why
|
|
799
|
-
|
|
800
|
-
<!-- Describe the motivation for this change -->
|
|
801
|
-
|
|
802
|
-
## What Changes
|
|
803
|
-
|
|
804
|
-
<!-- Describe what will change -->
|
|
805
|
-
|
|
806
|
-
## Capabilities
|
|
807
|
-
|
|
808
|
-
### New Capabilities
|
|
809
|
-
<!-- List new capabilities -->
|
|
810
|
-
|
|
811
|
-
### Modified Capabilities
|
|
812
|
-
<!-- List modified capabilities -->
|
|
813
|
-
|
|
814
|
-
## Impact
|
|
815
|
-
|
|
816
|
-
<!-- Describe the impact on existing functionality -->
|
|
877
|
+
return `## Why
|
|
878
|
+
|
|
879
|
+
<!-- Describe the motivation for this change -->
|
|
880
|
+
|
|
881
|
+
## What Changes
|
|
882
|
+
|
|
883
|
+
<!-- Describe what will change -->
|
|
884
|
+
|
|
885
|
+
## Capabilities
|
|
886
|
+
|
|
887
|
+
### New Capabilities
|
|
888
|
+
<!-- List new capabilities -->
|
|
889
|
+
|
|
890
|
+
### Modified Capabilities
|
|
891
|
+
<!-- List modified capabilities -->
|
|
892
|
+
|
|
893
|
+
## Impact
|
|
894
|
+
|
|
895
|
+
<!-- Describe the impact on existing functionality -->
|
|
817
896
|
`;
|
|
818
897
|
case 'specs':
|
|
819
|
-
return `## ADDED Requirements
|
|
820
|
-
|
|
821
|
-
### Requirement: Example requirement
|
|
822
|
-
|
|
823
|
-
Description of the requirement.
|
|
824
|
-
|
|
825
|
-
#### Scenario: Example scenario
|
|
826
|
-
- **WHEN** some condition
|
|
827
|
-
- **THEN** some outcome
|
|
898
|
+
return `## ADDED Requirements
|
|
899
|
+
|
|
900
|
+
### Requirement: Example requirement
|
|
901
|
+
|
|
902
|
+
Description of the requirement.
|
|
903
|
+
|
|
904
|
+
#### Scenario: Example scenario
|
|
905
|
+
- **WHEN** some condition
|
|
906
|
+
- **THEN** some outcome
|
|
828
907
|
`;
|
|
829
908
|
case 'design':
|
|
830
|
-
return `## Context
|
|
831
|
-
|
|
832
|
-
<!-- Background and context -->
|
|
833
|
-
|
|
834
|
-
## Goals / Non-Goals
|
|
835
|
-
|
|
836
|
-
**Goals:**
|
|
837
|
-
<!-- List goals -->
|
|
838
|
-
|
|
839
|
-
**Non-Goals:**
|
|
840
|
-
<!-- List non-goals -->
|
|
841
|
-
|
|
842
|
-
## Decisions
|
|
843
|
-
|
|
844
|
-
### 1. Decision Name
|
|
845
|
-
|
|
846
|
-
Description and rationale.
|
|
847
|
-
|
|
848
|
-
**Alternatives considered:**
|
|
849
|
-
- Alternative 1: Rejected because...
|
|
850
|
-
|
|
851
|
-
## Risks / Trade-offs
|
|
852
|
-
|
|
853
|
-
<!-- List risks and trade-offs -->
|
|
909
|
+
return `## Context
|
|
910
|
+
|
|
911
|
+
<!-- Background and context -->
|
|
912
|
+
|
|
913
|
+
## Goals / Non-Goals
|
|
914
|
+
|
|
915
|
+
**Goals:**
|
|
916
|
+
<!-- List goals -->
|
|
917
|
+
|
|
918
|
+
**Non-Goals:**
|
|
919
|
+
<!-- List non-goals -->
|
|
920
|
+
|
|
921
|
+
## Decisions
|
|
922
|
+
|
|
923
|
+
### 1. Decision Name
|
|
924
|
+
|
|
925
|
+
Description and rationale.
|
|
926
|
+
|
|
927
|
+
**Alternatives considered:**
|
|
928
|
+
- Alternative 1: Rejected because...
|
|
929
|
+
|
|
930
|
+
## Risks / Trade-offs
|
|
931
|
+
|
|
932
|
+
<!-- List risks and trade-offs -->
|
|
854
933
|
`;
|
|
855
934
|
case 'tasks':
|
|
856
|
-
return `## Implementation Tasks
|
|
857
|
-
|
|
858
|
-
- [ ] Task 1
|
|
859
|
-
- [ ] Task 2
|
|
860
|
-
- [ ] Task 3
|
|
935
|
+
return `## Implementation Tasks
|
|
936
|
+
|
|
937
|
+
- [ ] Task 1
|
|
938
|
+
- [ ] Task 2
|
|
939
|
+
- [ ] Task 3
|
|
861
940
|
`;
|
|
862
941
|
default:
|
|
863
|
-
return `## ${artifactId}
|
|
864
|
-
|
|
865
|
-
<!-- Add content here -->
|
|
942
|
+
return `## ${artifactId}
|
|
943
|
+
|
|
944
|
+
<!-- Add content here -->
|
|
866
945
|
`;
|
|
867
946
|
}
|
|
868
947
|
}
|
package/dist/commands/show.js
CHANGED
|
@@ -157,14 +157,17 @@ export class ShowCommand {
|
|
|
157
157
|
}
|
|
158
158
|
warnIrrelevantFlags(type, options) {
|
|
159
159
|
const irrelevant = [];
|
|
160
|
+
// --no-scenarios makes commander default `scenarios` to true, so its
|
|
161
|
+
// presence alone does not mean the user passed it — only false does.
|
|
162
|
+
const isUserProvided = (k) => k === 'scenarios' ? options[k] === false : k in options;
|
|
160
163
|
if (type === 'change') {
|
|
161
164
|
for (const k of SPEC_FLAG_KEYS)
|
|
162
|
-
if (k
|
|
165
|
+
if (isUserProvided(k))
|
|
163
166
|
irrelevant.push(k);
|
|
164
167
|
}
|
|
165
168
|
else {
|
|
166
169
|
for (const k of CHANGE_FLAG_KEYS)
|
|
167
|
-
if (k
|
|
170
|
+
if (isUserProvided(k))
|
|
168
171
|
irrelevant.push(k);
|
|
169
172
|
}
|
|
170
173
|
if (irrelevant.length > 0) {
|
package/dist/commands/spec.js
CHANGED
|
@@ -1,11 +1,31 @@
|
|
|
1
|
-
import { existsSync,
|
|
2
|
-
import { join } from 'path';
|
|
1
|
+
import { existsSync, readFileSync } from 'fs';
|
|
2
|
+
import path, { join } from 'path';
|
|
3
3
|
import { MarkdownParser } from '../core/parsers/markdown-parser.js';
|
|
4
4
|
import { Validator } from '../core/validation/validator.js';
|
|
5
5
|
import { isInteractive } from '../utils/interactive.js';
|
|
6
6
|
import { getSpecIds } from '../utils/item-discovery.js';
|
|
7
|
+
import { discoverSpecFiles } from '../utils/spec-discovery.js';
|
|
8
|
+
import { FileSystemUtils } from '../utils/file-system.js';
|
|
7
9
|
const SPECS_DIR = 'openspec/specs';
|
|
8
|
-
function
|
|
10
|
+
function assertSpecPath(specsDir, specPath) {
|
|
11
|
+
const relativePath = path.relative(path.resolve(specsDir), path.resolve(specPath));
|
|
12
|
+
if (relativePath === '..' ||
|
|
13
|
+
relativePath.startsWith(`..${path.sep}`) ||
|
|
14
|
+
path.isAbsolute(relativePath)) {
|
|
15
|
+
throw new Error(`Path is outside the allowed directory: ${specPath}`);
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
// Preserve confined spec.md links, including links to a sibling capability.
|
|
19
|
+
FileSystemUtils.assertPathWithin(specsDir, specPath);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
// A capability directory may intentionally be a monorepo symlink. Treat it
|
|
23
|
+
// as the trust root while still rejecting a link outside that capability.
|
|
24
|
+
FileSystemUtils.assertPathWithin(path.dirname(specPath), specPath);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function parseSpecFromFile(specsDir, specPath, specId) {
|
|
28
|
+
assertSpecPath(specsDir, specPath);
|
|
9
29
|
const content = readFileSync(specPath, 'utf-8');
|
|
10
30
|
const parser = new MarkdownParser(content);
|
|
11
31
|
return parser.parseSpec(specId);
|
|
@@ -40,7 +60,8 @@ function filterSpec(spec, options) {
|
|
|
40
60
|
* Print the raw markdown content for a spec file without any formatting.
|
|
41
61
|
* Raw-first behavior ensures text mode is a passthrough for deterministic output.
|
|
42
62
|
*/
|
|
43
|
-
function printSpecTextRaw(specPath) {
|
|
63
|
+
function printSpecTextRaw(specsDir, specPath) {
|
|
64
|
+
assertSpecPath(specsDir, specPath);
|
|
44
65
|
const content = readFileSync(specPath, 'utf-8');
|
|
45
66
|
console.log(content);
|
|
46
67
|
}
|
|
@@ -69,6 +90,7 @@ export class SpecCommand {
|
|
|
69
90
|
}
|
|
70
91
|
}
|
|
71
92
|
const specPath = join(this.specsDir, specId, 'spec.md');
|
|
93
|
+
assertSpecPath(this.specsDir, specPath);
|
|
72
94
|
if (!existsSync(specPath)) {
|
|
73
95
|
// Root-aware callers get the absolute path; the cwd-based noun form
|
|
74
96
|
// keeps its historical forward-slash relative message on all platforms.
|
|
@@ -79,7 +101,7 @@ export class SpecCommand {
|
|
|
79
101
|
if (options.requirements && options.requirement) {
|
|
80
102
|
throw new Error('Options --requirements and --requirement cannot be used together');
|
|
81
103
|
}
|
|
82
|
-
const parsed = parseSpecFromFile(specPath, specId);
|
|
104
|
+
const parsed = parseSpecFromFile(this.specsDir, specPath, specId);
|
|
83
105
|
const filtered = filterSpec(parsed, options);
|
|
84
106
|
const output = {
|
|
85
107
|
id: specId,
|
|
@@ -93,7 +115,7 @@ export class SpecCommand {
|
|
|
93
115
|
console.log(JSON.stringify(output, null, 2));
|
|
94
116
|
return;
|
|
95
117
|
}
|
|
96
|
-
printSpecTextRaw(specPath);
|
|
118
|
+
printSpecTextRaw(this.specsDir, specPath);
|
|
97
119
|
}
|
|
98
120
|
}
|
|
99
121
|
export function registerSpecCommand(rootProgram) {
|
|
@@ -127,36 +149,32 @@ export function registerSpecCommand(rootProgram) {
|
|
|
127
149
|
.description('List all available specifications')
|
|
128
150
|
.option('--json', 'Output as JSON')
|
|
129
151
|
.option('--long', 'Show id and title with counts')
|
|
130
|
-
.action((options) => {
|
|
152
|
+
.action(async (options) => {
|
|
131
153
|
try {
|
|
132
154
|
if (!existsSync(SPECS_DIR)) {
|
|
133
155
|
console.log('No items found');
|
|
134
156
|
return;
|
|
135
157
|
}
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
.map(
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
};
|
|
155
|
-
}
|
|
158
|
+
const discovered = await discoverSpecFiles(SPECS_DIR);
|
|
159
|
+
const specs = discovered
|
|
160
|
+
.map(({ id, specFile }) => {
|
|
161
|
+
try {
|
|
162
|
+
assertSpecPath(SPECS_DIR, specFile);
|
|
163
|
+
const spec = parseSpecFromFile(SPECS_DIR, specFile, id);
|
|
164
|
+
return {
|
|
165
|
+
id,
|
|
166
|
+
title: spec.name,
|
|
167
|
+
requirementCount: spec.requirements.length
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
return {
|
|
172
|
+
id,
|
|
173
|
+
title: id,
|
|
174
|
+
requirementCount: 0
|
|
175
|
+
};
|
|
156
176
|
}
|
|
157
|
-
return null;
|
|
158
177
|
})
|
|
159
|
-
.filter((spec) => spec !== null)
|
|
160
178
|
.sort((a, b) => a.id.localeCompare(b.id));
|
|
161
179
|
if (options.json) {
|
|
162
180
|
console.log(JSON.stringify(specs, null, 2));
|
|
@@ -203,10 +221,12 @@ export function registerSpecCommand(rootProgram) {
|
|
|
203
221
|
}
|
|
204
222
|
}
|
|
205
223
|
const specPath = join(SPECS_DIR, specId, 'spec.md');
|
|
224
|
+
assertSpecPath(SPECS_DIR, specPath);
|
|
206
225
|
if (!existsSync(specPath)) {
|
|
207
226
|
throw new Error(`Spec '${specId}' not found at openspec/specs/${specId}/spec.md`);
|
|
208
227
|
}
|
|
209
228
|
const validator = new Validator(options.strict);
|
|
229
|
+
assertSpecPath(SPECS_DIR, specPath);
|
|
210
230
|
const report = await validator.validateSpec(specPath);
|
|
211
231
|
if (options.json) {
|
|
212
232
|
console.log(JSON.stringify(report, null, 2));
|
package/dist/commands/store.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import * as os from 'node:os';
|
|
2
2
|
import { asErrorMessage, emitFailure, printJson } from './shared-output.js';
|
|
3
3
|
import * as path from 'node:path';
|
|
4
|
-
import { trackEvent, trackCommandFailed } from '../telemetry/index.js';
|
|
5
4
|
import { COMMAND_REGISTRY } from '../core/completions/command-registry.js';
|
|
6
5
|
import { StoreError, doctorStores, listStores, prepareStoreSetup, prepareStoreCleanup, registerExistingStore, removeStore, resolveSetupGitEnabled, setupPreparedStore, unregisterStore, validateStoreId, } from '../core/store/index.js';
|
|
7
6
|
import { isInteractive } from '../utils/interactive.js';
|
|
7
|
+
import { trackStoreOperation } from '../telemetry/index.js';
|
|
8
8
|
function toStoreOutput(store) {
|
|
9
9
|
return {
|
|
10
10
|
id: store.id,
|
|
@@ -327,19 +327,15 @@ class StoreCommand {
|
|
|
327
327
|
}
|
|
328
328
|
const result = await setupPreparedStore(prepared, { initGit });
|
|
329
329
|
const payload = toMutationOutput(result);
|
|
330
|
-
trackEvent('store_setup', {
|
|
331
|
-
git_initialized: result.git.initialized,
|
|
332
|
-
already_registered: result.registryCommit.alreadyRegistered,
|
|
333
|
-
has_remote: Boolean(options.remote),
|
|
334
|
-
});
|
|
335
330
|
if (options.json) {
|
|
336
331
|
printJson(payload);
|
|
337
332
|
return;
|
|
338
333
|
}
|
|
339
334
|
printMutationHuman('Store ready', payload, result.remotes);
|
|
335
|
+
// Track store setup operation
|
|
336
|
+
await trackStoreOperation('register', result.store?.id);
|
|
340
337
|
}
|
|
341
338
|
catch (error) {
|
|
342
|
-
await trackCommandFailed('store_setup', error);
|
|
343
339
|
this.handleFailure(options.json, { store: null, registry: null, git: null, created_files: [], status: [] }, error);
|
|
344
340
|
}
|
|
345
341
|
}
|
|
@@ -365,18 +361,15 @@ class StoreCommand {
|
|
|
365
361
|
});
|
|
366
362
|
}
|
|
367
363
|
const payload = toMutationOutput(result);
|
|
368
|
-
trackEvent('store_registered', {
|
|
369
|
-
already_registered: result.registryCommit.alreadyRegistered,
|
|
370
|
-
git_is_repository: result.git.isRepository,
|
|
371
|
-
});
|
|
372
364
|
if (options.json) {
|
|
373
365
|
printJson(payload);
|
|
374
366
|
return;
|
|
375
367
|
}
|
|
376
368
|
printMutationHuman('Store registered', payload, result.remotes);
|
|
369
|
+
// Track store register operation
|
|
370
|
+
await trackStoreOperation('register', result.store?.id);
|
|
377
371
|
}
|
|
378
372
|
catch (error) {
|
|
379
|
-
await trackCommandFailed('store_register', error);
|
|
380
373
|
this.handleFailure(options.json, { store: null, registry: null, git: null, created_files: [], status: [] }, error);
|
|
381
374
|
}
|
|
382
375
|
}
|
|
@@ -388,6 +381,8 @@ class StoreCommand {
|
|
|
388
381
|
return;
|
|
389
382
|
}
|
|
390
383
|
printCleanupHuman('Unregistered store', payload);
|
|
384
|
+
// Track store unregister operation
|
|
385
|
+
await trackStoreOperation('unregister', id);
|
|
391
386
|
}
|
|
392
387
|
catch (error) {
|
|
393
388
|
this.handleFailure(options.json, { store: null, registry: null, files: null, status: [] }, error);
|
|
@@ -397,30 +392,29 @@ class StoreCommand {
|
|
|
397
392
|
try {
|
|
398
393
|
const target = await prepareStoreCleanup({ id });
|
|
399
394
|
await confirmRemove(target.id, target.root, options);
|
|
400
|
-
const
|
|
401
|
-
const payload = toCleanupOutput(result);
|
|
402
|
-
trackEvent('store_removed', {
|
|
403
|
-
files_deleted: result.files.deleted,
|
|
404
|
-
});
|
|
395
|
+
const payload = toCleanupOutput(await removeStore(target));
|
|
405
396
|
if (options.json) {
|
|
406
397
|
printJson(payload);
|
|
407
398
|
return;
|
|
408
399
|
}
|
|
409
400
|
printCleanupHuman('Removed store', payload);
|
|
401
|
+
// Track store remove operation
|
|
402
|
+
await trackStoreOperation('remove', id);
|
|
410
403
|
}
|
|
411
404
|
catch (error) {
|
|
412
|
-
await trackCommandFailed('store_remove', error);
|
|
413
405
|
this.handleFailure(options.json, { store: null, registry: null, files: null, status: [] }, error);
|
|
414
406
|
}
|
|
415
407
|
}
|
|
416
408
|
async list(options = {}) {
|
|
417
409
|
try {
|
|
418
|
-
const
|
|
410
|
+
const result = await listStores();
|
|
419
411
|
if (options.json) {
|
|
420
|
-
printJson(
|
|
412
|
+
printJson({ stores: result.stores, status: [] });
|
|
421
413
|
return;
|
|
422
414
|
}
|
|
423
|
-
printListHuman(
|
|
415
|
+
printListHuman({ stores: result.stores, status: [] });
|
|
416
|
+
// Track store list operation
|
|
417
|
+
await trackStoreOperation('list');
|
|
424
418
|
}
|
|
425
419
|
catch (error) {
|
|
426
420
|
this.handleFailure(options.json, { stores: [], status: [] }, error);
|
|
@@ -14,6 +14,14 @@ interface ExecuteOptions {
|
|
|
14
14
|
export declare class ValidateCommand {
|
|
15
15
|
execute(itemName: string | undefined, options?: ExecuteOptions): Promise<void>;
|
|
16
16
|
private normalizeType;
|
|
17
|
+
/**
|
|
18
|
+
* Resolve change IDs by directory existence within the resolved root — the
|
|
19
|
+
* same rule `openspec status`/`instructions` use (`getAvailableChanges`) —
|
|
20
|
+
* rather than requiring `proposal.md`. This lets `validate` resolve a
|
|
21
|
+
* scaffolded or still-authoring change that the sibling commands already
|
|
22
|
+
* resolve (#1182). Sorted to preserve the prior `getActiveChangeIds` ordering.
|
|
23
|
+
*/
|
|
24
|
+
private listChangeIds;
|
|
17
25
|
private runInteractiveSelector;
|
|
18
26
|
private printNonInteractiveHint;
|
|
19
27
|
private validateDirectItem;
|