@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
|
@@ -7,8 +7,11 @@
|
|
|
7
7
|
* - `--store <id>` selects a registered store's root.
|
|
8
8
|
* - Without `--store`, the nearest ancestor containing `openspec/` wins.
|
|
9
9
|
* Leftover workspace view state is never considered a root here.
|
|
10
|
-
* - With no nearest root,
|
|
11
|
-
*
|
|
10
|
+
* - With no nearest root, a global `defaultStore` (if set) is the last
|
|
11
|
+
* machine-level fallback before the selection hint error.
|
|
12
|
+
* - With no nearest root and no default, registered stores produce a
|
|
13
|
+
* selection hint error; otherwise commands may treat the current
|
|
14
|
+
* directory as an implicit root.
|
|
12
15
|
*
|
|
13
16
|
* Diagnostic codes reuse the store taxonomy where an error passes
|
|
14
17
|
* through unchanged (`invalid_store_id`, metadata parse failures);
|
|
@@ -26,6 +29,7 @@ import { getStoreRootForBackend } from './store/registry.js';
|
|
|
26
29
|
import { inspectOpenSpecRoot } from './openspec-root.js';
|
|
27
30
|
import { findRepoPlanningRootSync } from './planning-home.js';
|
|
28
31
|
import { classifyOpenSpecDir, storePointerProblem } from './project-config.js';
|
|
32
|
+
import { getGlobalConfig } from './global-config.js';
|
|
29
33
|
import { FileSystemUtils } from '../utils/file-system.js';
|
|
30
34
|
export class RootSelectionError extends Error {
|
|
31
35
|
diagnostic;
|
|
@@ -221,6 +225,32 @@ async function resolveNearestOrDeclaredRoot(nearestRoot, globalDataDir) {
|
|
|
221
225
|
throw error;
|
|
222
226
|
}
|
|
223
227
|
}
|
|
228
|
+
/**
|
|
229
|
+
* The machine-level fallback: the global `defaultStore` resolved as a root,
|
|
230
|
+
* with its own provenance (`global_default`) so JSON surfaces can tell a
|
|
231
|
+
* machine-wide default from a repo's `store:` pointer. Mirrors the
|
|
232
|
+
* declared-pointer catch — a stale or unregistered id degrades to the
|
|
233
|
+
* underlying error, reshaped to point at clearing the global default
|
|
234
|
+
* rather than passing --store.
|
|
235
|
+
*/
|
|
236
|
+
async function resolveDefaultStoreRoot(id, globalDataDir) {
|
|
237
|
+
try {
|
|
238
|
+
return await resolveStoreRoot(id, globalDataDir, 'global_default');
|
|
239
|
+
}
|
|
240
|
+
catch (error) {
|
|
241
|
+
if (error instanceof RootSelectionError) {
|
|
242
|
+
const staleFix = error.diagnostic.code === 'unknown_store' ||
|
|
243
|
+
error.diagnostic.code === 'no_registered_stores'
|
|
244
|
+
? `Register the store (openspec store register <path> --id ${id}) or clear the stale global default (openspec config unset defaultStore).`
|
|
245
|
+
: error.diagnostic.fix;
|
|
246
|
+
throw new RootSelectionError(`Global defaultStore '${id}': ${error.message}`, error.diagnostic.code, {
|
|
247
|
+
...(error.diagnostic.target ? { target: error.diagnostic.target } : {}),
|
|
248
|
+
...(staleFix ? { fix: staleFix } : {}),
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
throw error;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
224
254
|
export async function resolveOpenSpecRoot(options = {}) {
|
|
225
255
|
if (options.storePath !== undefined) {
|
|
226
256
|
throw new RootSelectionError('--store-path is not supported. Register the path with openspec store register <path>, then select it with --store <id>.', 'store_path_not_supported', {
|
|
@@ -236,6 +266,13 @@ export async function resolveOpenSpecRoot(options = {}) {
|
|
|
236
266
|
if (nearestRoot) {
|
|
237
267
|
return resolveNearestOrDeclaredRoot(nearestRoot, options.globalDataDir);
|
|
238
268
|
}
|
|
269
|
+
// Machine-level fallback: a global defaultStore is consulted only after
|
|
270
|
+
// --store, the nearest local root, and project-level pointers have all
|
|
271
|
+
// failed to resolve — it changes the failure path, never the precedence.
|
|
272
|
+
const defaultStore = getGlobalConfig().defaultStore;
|
|
273
|
+
if (defaultStore) {
|
|
274
|
+
return resolveDefaultStoreRoot(defaultStore, options.globalDataDir);
|
|
275
|
+
}
|
|
239
276
|
let registry;
|
|
240
277
|
try {
|
|
241
278
|
registry = await readStoreRegistryState(options.globalDataDir ? { globalDataDir: options.globalDataDir } : {});
|
|
@@ -265,9 +302,9 @@ export function toRootOutput(root) {
|
|
|
265
302
|
};
|
|
266
303
|
}
|
|
267
304
|
/**
|
|
268
|
-
* A store-selected root — explicit `--store
|
|
269
|
-
* Cross-root behavior (absolute paths, --store hints,
|
|
270
|
-
* noun-form suggestions) keys on this, never on `source` directly.
|
|
305
|
+
* A store-selected root — explicit `--store`, a declared pointer, or the
|
|
306
|
+
* global default. Cross-root behavior (absolute paths, --store hints,
|
|
307
|
+
* suppressed noun-form suggestions) keys on this, never on `source` directly.
|
|
271
308
|
*/
|
|
272
309
|
export function isStoreSelectedRoot(root) {
|
|
273
310
|
return root.storeId !== undefined;
|
|
@@ -4,9 +4,14 @@ export const ScenarioSchema = z.object({
|
|
|
4
4
|
rawText: z.string().min(1, VALIDATION_MESSAGES.SCENARIO_EMPTY),
|
|
5
5
|
});
|
|
6
6
|
export const RequirementSchema = z.object({
|
|
7
|
+
// SHALL/MUST body-keyword enforcement lives in the imperative validator
|
|
8
|
+
// (Validator.applySpecRules), not here: the parser collapses the requirement
|
|
9
|
+
// header into `text`, so a Zod refine on `text` cannot tell "keyword in header
|
|
10
|
+
// only" from "keyword in body" and emits a misleading generic error. The
|
|
11
|
+
// validator recovers the header and emits the targeted hint for both the
|
|
12
|
+
// main-spec and change-delta paths (#1156).
|
|
7
13
|
text: z.string()
|
|
8
|
-
.min(1, VALIDATION_MESSAGES.REQUIREMENT_EMPTY)
|
|
9
|
-
.refine((text) => text.includes('SHALL') || text.includes('MUST'), VALIDATION_MESSAGES.REQUIREMENT_NO_SHALL),
|
|
14
|
+
.min(1, VALIDATION_MESSAGES.REQUIREMENT_EMPTY),
|
|
10
15
|
scenarios: z.array(ScenarioSchema)
|
|
11
16
|
.min(1, VALIDATION_MESSAGES.REQUIREMENT_NO_SCENARIOS),
|
|
12
17
|
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-approved tools for generated skills and slash commands, emitted as the
|
|
3
|
+
* `allowed-tools` frontmatter field (Agent Skills standard for SKILL.md;
|
|
4
|
+
* same field for Claude Code slash commands). Scoped to the OpenSpec CLI so
|
|
5
|
+
* agents that honor it stop prompting on each `openspec` call; the field
|
|
6
|
+
* only pre-approves — it does not restrict — so any other tool a skill or
|
|
7
|
+
* command needs (Read, Write, arbitrary Bash for builds/tests) stays
|
|
8
|
+
* available under the user's normal permission settings. Tools that don't
|
|
9
|
+
* recognize the field ignore it.
|
|
10
|
+
*/
|
|
11
|
+
export declare const OPENSPEC_CLI_ALLOWED_TOOLS = "Bash(openspec:*)";
|
|
12
|
+
//# sourceMappingURL=allowed-tools.d.ts.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-approved tools for generated skills and slash commands, emitted as the
|
|
3
|
+
* `allowed-tools` frontmatter field (Agent Skills standard for SKILL.md;
|
|
4
|
+
* same field for Claude Code slash commands). Scoped to the OpenSpec CLI so
|
|
5
|
+
* agents that honor it stop prompting on each `openspec` call; the field
|
|
6
|
+
* only pre-approves — it does not restrict — so any other tool a skill or
|
|
7
|
+
* command needs (Read, Write, arbitrary Bash for builds/tests) stays
|
|
8
|
+
* available under the user's normal permission settings. Tools that don't
|
|
9
|
+
* recognize the field ignore it.
|
|
10
|
+
*/
|
|
11
|
+
export const OPENSPEC_CLI_ALLOWED_TOOLS = 'Bash(openspec:*)';
|
|
12
|
+
//# sourceMappingURL=allowed-tools.js.map
|
|
@@ -5,4 +5,5 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export { SKILL_NAMES, type SkillName, COMMAND_IDS, type CommandId, type ToolSkillStatus, type ToolVersionStatus, getToolsWithSkillsDir, getToolSkillStatus, getToolStates, extractGeneratedByVersion, getToolVersionStatus, getConfiguredTools, getAllToolVersionStatus, } from './tool-detection.js';
|
|
7
7
|
export { type SkillTemplateEntry, type CommandTemplateEntry, getSkillTemplates, getCommandTemplates, getCommandContents, generateSkillContent, } from './skill-generation.js';
|
|
8
|
+
export { type SkillCapableTool, toolSupportsSkills, getSkillCapableTools, hasGlobalSkillTarget, resolveToolSkillsDir, } from './skill-paths.js';
|
|
8
9
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -5,4 +5,5 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export { SKILL_NAMES, COMMAND_IDS, getToolsWithSkillsDir, getToolSkillStatus, getToolStates, extractGeneratedByVersion, getToolVersionStatus, getConfiguredTools, getAllToolVersionStatus, } from './tool-detection.js';
|
|
7
7
|
export { getSkillTemplates, getCommandTemplates, getCommandContents, generateSkillContent, } from './skill-generation.js';
|
|
8
|
+
export { toolSupportsSkills, getSkillCapableTools, hasGlobalSkillTarget, resolveToolSkillsDir, } from './skill-paths.js';
|
|
8
9
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns whether a legacy Codex skill differs from the current canonical
|
|
3
|
+
* replacement only by generated version, checkout line endings/BOM, or the
|
|
4
|
+
* known Codex/generic dual-reference expansion.
|
|
5
|
+
*/
|
|
6
|
+
export declare function isLegacyCodexSkillEquivalentToCurrent(legacyContent: string, currentContent: string): boolean;
|
|
7
|
+
//# sourceMappingURL=skill-content-equivalence.d.ts.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { OPENSPEC_SKILL_NAMES } from '../config.js';
|
|
2
|
+
const GENERATED_VERSION = /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-(?:(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*)(?:\.(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*))*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
3
|
+
const OPENSPEC_SKILL_NAME_SET = new Set(OPENSPEC_SKILL_NAMES);
|
|
4
|
+
/**
|
|
5
|
+
* Normalizes checkout line endings and a valid generated version inside the
|
|
6
|
+
* YAML frontmatter. Free-form `generatedBy` text in the instructions remains
|
|
7
|
+
* material.
|
|
8
|
+
*/
|
|
9
|
+
function normalizeGeneratedSkill(content) {
|
|
10
|
+
const normalized = content.replace(/^\uFEFF/, '').replace(/\r\n/g, '\n');
|
|
11
|
+
const frontmatter = normalized.match(/^---\n[\s\S]*?\n---(?:\n|$)/)?.[0];
|
|
12
|
+
if (!frontmatter)
|
|
13
|
+
return normalized;
|
|
14
|
+
const versionLine = /^(\s*generatedBy:\s*)(?:"([^"\n]+)"|'([^'\n]+)'|([^\s"'#]+))\s*$/m;
|
|
15
|
+
const normalizedFrontmatter = frontmatter.replace(versionLine, (line, prefix, doubleQuoted, singleQuoted, bare) => {
|
|
16
|
+
const version = doubleQuoted ?? singleQuoted ?? bare;
|
|
17
|
+
return version && GENERATED_VERSION.test(version)
|
|
18
|
+
? `${prefix}"<generated-version>"`
|
|
19
|
+
: line;
|
|
20
|
+
});
|
|
21
|
+
return normalizedFrontmatter + normalized.slice(frontmatter.length);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Converts only known generated dual references in current Codex content back
|
|
25
|
+
* to the direct syntax used by legacy `.codex` output.
|
|
26
|
+
*/
|
|
27
|
+
function toLegacyCodexReferences(content) {
|
|
28
|
+
return content.replace(/\$(openspec-[a-z0-9-]+) \(Codex\) or \/\1 \(other agents\)/g, (match, skillName) => OPENSPEC_SKILL_NAME_SET.has(skillName) ? `$${skillName}` : match);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Returns whether a legacy Codex skill differs from the current canonical
|
|
32
|
+
* replacement only by generated version, checkout line endings/BOM, or the
|
|
33
|
+
* known Codex/generic dual-reference expansion.
|
|
34
|
+
*/
|
|
35
|
+
export function isLegacyCodexSkillEquivalentToCurrent(legacyContent, currentContent) {
|
|
36
|
+
const normalizedLegacy = normalizeGeneratedSkill(legacyContent);
|
|
37
|
+
const normalizedCurrent = normalizeGeneratedSkill(currentContent);
|
|
38
|
+
return (normalizedLegacy === normalizedCurrent ||
|
|
39
|
+
normalizedLegacy === toLegacyCodexReferences(normalizedCurrent));
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=skill-content-equivalence.js.map
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Shared utilities for generating skill and command files.
|
|
5
5
|
*/
|
|
6
|
-
import { getExploreSkillTemplate, getNewChangeSkillTemplate, getContinueChangeSkillTemplate, getApplyChangeSkillTemplate, getFfChangeSkillTemplate, getSyncSpecsSkillTemplate, getArchiveChangeSkillTemplate, getBulkArchiveChangeSkillTemplate, getVerifyChangeSkillTemplate, getOnboardSkillTemplate, getOpsxProposeSkillTemplate,
|
|
6
|
+
import { getExploreSkillTemplate, getNewChangeSkillTemplate, getContinueChangeSkillTemplate, getApplyChangeSkillTemplate, getUpdateChangeSkillTemplate, getModifyChangeSkillTemplate, getFfChangeSkillTemplate, getSyncSpecsSkillTemplate, getArchiveChangeSkillTemplate, getBulkArchiveChangeSkillTemplate, getVerifyChangeSkillTemplate, getOnboardSkillTemplate, getOpsxProposeSkillTemplate, getOpsxExploreCommandTemplate, getOpsxNewCommandTemplate, getOpsxContinueCommandTemplate, getOpsxApplyCommandTemplate, getOpsxUpdateCommandTemplate, getOpsxModifyCommandTemplate, getOpsxFfCommandTemplate, getOpsxSyncCommandTemplate, getOpsxArchiveCommandTemplate, getOpsxBulkArchiveCommandTemplate, getOpsxVerifyCommandTemplate, getOpsxOnboardCommandTemplate, getOpsxProposeCommandTemplate, } from '../templates/skill-templates.js';
|
|
7
|
+
import { OPENSPEC_CLI_ALLOWED_TOOLS } from './allowed-tools.js';
|
|
7
8
|
/**
|
|
8
9
|
* Gets skill templates with their directory names, optionally filtered by workflow IDs.
|
|
9
10
|
*
|
|
@@ -15,6 +16,7 @@ export function getSkillTemplates(workflowFilter) {
|
|
|
15
16
|
{ template: getNewChangeSkillTemplate(), dirName: 'openspec-new-change', workflowId: 'new' },
|
|
16
17
|
{ template: getContinueChangeSkillTemplate(), dirName: 'openspec-continue-change', workflowId: 'continue' },
|
|
17
18
|
{ template: getApplyChangeSkillTemplate(), dirName: 'openspec-apply-change', workflowId: 'apply' },
|
|
19
|
+
{ template: getUpdateChangeSkillTemplate(), dirName: 'openspec-update-change', workflowId: 'update' },
|
|
18
20
|
{ template: getModifyChangeSkillTemplate(), dirName: 'openspec-modify-change', workflowId: 'modify' },
|
|
19
21
|
{ template: getFfChangeSkillTemplate(), dirName: 'openspec-ff-change', workflowId: 'ff' },
|
|
20
22
|
{ template: getSyncSpecsSkillTemplate(), dirName: 'openspec-sync-specs', workflowId: 'sync' },
|
|
@@ -40,6 +42,7 @@ export function getCommandTemplates(workflowFilter) {
|
|
|
40
42
|
{ template: getOpsxNewCommandTemplate(), id: 'new' },
|
|
41
43
|
{ template: getOpsxContinueCommandTemplate(), id: 'continue' },
|
|
42
44
|
{ template: getOpsxApplyCommandTemplate(), id: 'apply' },
|
|
45
|
+
{ template: getOpsxUpdateCommandTemplate(), id: 'update' },
|
|
43
46
|
{ template: getOpsxModifyCommandTemplate(), id: 'modify' },
|
|
44
47
|
{ template: getOpsxFfCommandTemplate(), id: 'ff' },
|
|
45
48
|
{ template: getOpsxSyncCommandTemplate(), id: 'sync' },
|
|
@@ -81,18 +84,19 @@ export function generateSkillContent(template, generatedByVersion, transformInst
|
|
|
81
84
|
const instructions = transformInstructions
|
|
82
85
|
? transformInstructions(template.instructions)
|
|
83
86
|
: template.instructions;
|
|
84
|
-
return `---
|
|
85
|
-
name: ${template.name}
|
|
86
|
-
description: ${template.description}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
87
|
+
return `---
|
|
88
|
+
name: ${template.name}
|
|
89
|
+
description: ${template.description}
|
|
90
|
+
allowed-tools: ${OPENSPEC_CLI_ALLOWED_TOOLS}
|
|
91
|
+
license: ${template.license || 'MIT'}
|
|
92
|
+
compatibility: ${template.compatibility || 'Requires openspec CLI.'}
|
|
93
|
+
metadata:
|
|
94
|
+
author: ${template.metadata?.author || 'openspec'}
|
|
95
|
+
version: "${template.metadata?.version || '1.0'}"
|
|
96
|
+
generatedBy: "${generatedByVersion}"
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
${instructions}
|
|
96
100
|
`;
|
|
97
101
|
}
|
|
98
102
|
//# sourceMappingURL=skill-generation.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type AIToolOption } from '../config.js';
|
|
2
|
+
export type SkillCapableTool = AIToolOption & ({
|
|
3
|
+
skillsDir: string;
|
|
4
|
+
} | {
|
|
5
|
+
globalSkillsDir: string;
|
|
6
|
+
});
|
|
7
|
+
export declare function toolSupportsSkills(tool: AIToolOption): tool is SkillCapableTool;
|
|
8
|
+
export declare function getSkillCapableTools(): SkillCapableTool[];
|
|
9
|
+
export declare function hasGlobalSkillTarget(tool: AIToolOption): boolean;
|
|
10
|
+
export declare function resolveToolSkillsDir(projectRoot: string, tool: SkillCapableTool, options?: {
|
|
11
|
+
homeDir?: string;
|
|
12
|
+
}): string;
|
|
13
|
+
//# sourceMappingURL=skill-paths.d.ts.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { AI_TOOLS } from '../config.js';
|
|
4
|
+
export function toolSupportsSkills(tool) {
|
|
5
|
+
return Boolean(tool.skillsDir || tool.globalSkillsDir);
|
|
6
|
+
}
|
|
7
|
+
export function getSkillCapableTools() {
|
|
8
|
+
return AI_TOOLS.filter(toolSupportsSkills);
|
|
9
|
+
}
|
|
10
|
+
export function hasGlobalSkillTarget(tool) {
|
|
11
|
+
return Boolean(tool.globalSkillsDir);
|
|
12
|
+
}
|
|
13
|
+
export function resolveToolSkillsDir(projectRoot, tool, options = {}) {
|
|
14
|
+
if (tool.globalSkillsDir) {
|
|
15
|
+
const homeDir = options.homeDir ?? process.env.USERPROFILE ?? process.env.HOME ?? os.homedir();
|
|
16
|
+
return path.join(homeDir, tool.globalSkillsDir, 'skills');
|
|
17
|
+
}
|
|
18
|
+
if (tool.skillsDir) {
|
|
19
|
+
return path.join(projectRoot, tool.skillsDir, 'skills');
|
|
20
|
+
}
|
|
21
|
+
throw new Error(`Tool '${tool.value}' does not support skill generation.`);
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=skill-paths.js.map
|
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
/**
|
|
7
7
|
* Names of skill directories created by openspec init.
|
|
8
8
|
*/
|
|
9
|
-
export declare const SKILL_NAMES: readonly ["openspec-explore", "openspec-new-change", "openspec-continue-change", "openspec-
|
|
9
|
+
export declare const SKILL_NAMES: readonly ["openspec-explore", "openspec-new-change", "openspec-continue-change", "openspec-apply-change", "openspec-update-change", "openspec-ff-change", "openspec-sync-specs", "openspec-archive-change", "openspec-bulk-archive-change", "openspec-verify-change", "openspec-onboard", "openspec-propose"];
|
|
10
10
|
export type SkillName = (typeof SKILL_NAMES)[number];
|
|
11
11
|
/**
|
|
12
12
|
* IDs of command templates created by openspec init.
|
|
13
13
|
*/
|
|
14
|
-
export declare const COMMAND_IDS: readonly ["explore", "new", "continue", "
|
|
14
|
+
export declare const COMMAND_IDS: readonly ["explore", "new", "continue", "apply", "update", "ff", "sync", "archive", "bulk-archive", "verify", "onboard", "propose"];
|
|
15
15
|
export type CommandId = (typeof COMMAND_IDS)[number];
|
|
16
16
|
/**
|
|
17
17
|
* Status of skill configuration for a tool.
|
|
@@ -32,9 +32,13 @@ export interface ToolVersionStatus {
|
|
|
32
32
|
toolId: string;
|
|
33
33
|
/** The tool's display name */
|
|
34
34
|
toolName: string;
|
|
35
|
-
/** Whether the tool has any skills configured */
|
|
35
|
+
/** Whether the tool has any skills or commands configured */
|
|
36
36
|
configured: boolean;
|
|
37
|
-
/**
|
|
37
|
+
/**
|
|
38
|
+
* The generatedBy version recorded in the tool's skill files. For a tool that
|
|
39
|
+
* has commands but no skills, the current version when the command files match
|
|
40
|
+
* what would be generated now. Null when neither says the files are current.
|
|
41
|
+
*/
|
|
38
42
|
generatedByVersion: string | null;
|
|
39
43
|
/** Whether the tool needs updating (version mismatch or missing) */
|
|
40
44
|
needsUpdate: boolean;
|
|
@@ -47,6 +51,19 @@ export declare function getToolsWithSkillsDir(): string[];
|
|
|
47
51
|
* Checks which skill files exist for a tool.
|
|
48
52
|
*/
|
|
49
53
|
export declare function getToolSkillStatus(projectRoot: string, toolId: string): ToolSkillStatus;
|
|
54
|
+
/**
|
|
55
|
+
* Checks whether a tool has at least one generated OpenSpec command file.
|
|
56
|
+
*/
|
|
57
|
+
export declare function toolHasAnyConfiguredCommand(projectPath: string, toolId: string): boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Checks whether command files for a tool on disk match current generated command contents.
|
|
60
|
+
*
|
|
61
|
+
* Command files carry no version stamp, so content equality is the only available
|
|
62
|
+
* "is this current?" signal for a commands-only install.
|
|
63
|
+
*/
|
|
64
|
+
export declare function areCommandFilesUpToDate(projectRoot: string, toolId: string, options?: {
|
|
65
|
+
workflows?: readonly string[];
|
|
66
|
+
}): boolean;
|
|
50
67
|
/**
|
|
51
68
|
* Gets the skill status for all tools with skillsDir configured.
|
|
52
69
|
*/
|
|
@@ -57,11 +74,14 @@ export declare function getToolStates(projectRoot: string): Map<string, ToolSkil
|
|
|
57
74
|
*/
|
|
58
75
|
export declare function extractGeneratedByVersion(skillFilePath: string): string | null;
|
|
59
76
|
/**
|
|
60
|
-
* Gets version status for a tool by reading
|
|
77
|
+
* Gets version status for a tool by reading its skill files, falling back to a
|
|
78
|
+
* command-content fingerprint for installs that have commands but no skills.
|
|
61
79
|
*/
|
|
62
|
-
export declare function getToolVersionStatus(projectRoot: string, toolId: string, currentVersion: string
|
|
80
|
+
export declare function getToolVersionStatus(projectRoot: string, toolId: string, currentVersion: string, options?: {
|
|
81
|
+
workflows?: readonly string[];
|
|
82
|
+
}): ToolVersionStatus;
|
|
63
83
|
/**
|
|
64
|
-
* Gets all configured tools in the project.
|
|
84
|
+
* Gets all configured tools in the project (configured via skills or commands).
|
|
65
85
|
*/
|
|
66
86
|
export declare function getConfiguredTools(projectRoot: string): string[];
|
|
67
87
|
/**
|
|
@@ -5,24 +5,18 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import path from 'path';
|
|
7
7
|
import * as fs from 'fs';
|
|
8
|
-
import { AI_TOOLS } from '../config.js';
|
|
8
|
+
import { AI_TOOLS, OPENSPEC_SKILL_NAMES } from '../config.js';
|
|
9
|
+
import { CommandAdapterRegistry, generateCommands } from '../command-generation/index.js';
|
|
10
|
+
import { getCommandContents } from './skill-generation.js';
|
|
11
|
+
import { getGlobalConfig } from '../global-config.js';
|
|
12
|
+
import { getProfileWorkflows, ALL_WORKFLOWS } from '../profiles.js';
|
|
13
|
+
import { isSharedSkillTargetActive, readSharedSkillTarget, reconcileSharedSkillTargets, } from '../shared-skill-target.js';
|
|
14
|
+
import { shouldGenerateCommandsForTool, shouldGenerateSkillsForTool, } from '../command-surface.js';
|
|
15
|
+
import { getSkillCapableTools, resolveToolSkillsDir, toolSupportsSkills, } from './skill-paths.js';
|
|
9
16
|
/**
|
|
10
17
|
* Names of skill directories created by openspec init.
|
|
11
18
|
*/
|
|
12
|
-
export const SKILL_NAMES =
|
|
13
|
-
'openspec-explore',
|
|
14
|
-
'openspec-new-change',
|
|
15
|
-
'openspec-continue-change',
|
|
16
|
-
'openspec-modify-change',
|
|
17
|
-
'openspec-apply-change',
|
|
18
|
-
'openspec-ff-change',
|
|
19
|
-
'openspec-sync-specs',
|
|
20
|
-
'openspec-archive-change',
|
|
21
|
-
'openspec-bulk-archive-change',
|
|
22
|
-
'openspec-verify-change',
|
|
23
|
-
'openspec-onboard',
|
|
24
|
-
'openspec-propose',
|
|
25
|
-
];
|
|
19
|
+
export const SKILL_NAMES = OPENSPEC_SKILL_NAMES;
|
|
26
20
|
/**
|
|
27
21
|
* IDs of command templates created by openspec init.
|
|
28
22
|
*/
|
|
@@ -30,8 +24,8 @@ export const COMMAND_IDS = [
|
|
|
30
24
|
'explore',
|
|
31
25
|
'new',
|
|
32
26
|
'continue',
|
|
33
|
-
'modify',
|
|
34
27
|
'apply',
|
|
28
|
+
'update',
|
|
35
29
|
'ff',
|
|
36
30
|
'sync',
|
|
37
31
|
'archive',
|
|
@@ -44,21 +38,26 @@ export const COMMAND_IDS = [
|
|
|
44
38
|
* Gets the list of tools with skillsDir configured.
|
|
45
39
|
*/
|
|
46
40
|
export function getToolsWithSkillsDir() {
|
|
47
|
-
return
|
|
41
|
+
return getSkillCapableTools().map((tool) => tool.value);
|
|
48
42
|
}
|
|
49
43
|
/**
|
|
50
44
|
* Checks which skill files exist for a tool.
|
|
51
45
|
*/
|
|
52
46
|
export function getToolSkillStatus(projectRoot, toolId) {
|
|
53
47
|
const tool = AI_TOOLS.find((t) => t.value === toolId);
|
|
54
|
-
if (!tool
|
|
48
|
+
if (!tool || !toolSupportsSkills(tool)) {
|
|
49
|
+
return { configured: false, fullyConfigured: false, skillCount: 0 };
|
|
50
|
+
}
|
|
51
|
+
if (tool.skillsDir && !isSharedSkillTargetActive(projectRoot, toolId)) {
|
|
55
52
|
return { configured: false, fullyConfigured: false, skillCount: 0 };
|
|
56
53
|
}
|
|
57
|
-
const
|
|
54
|
+
const skillsDirs = [
|
|
55
|
+
resolveToolSkillsDir(projectRoot, tool),
|
|
56
|
+
...(tool.legacySkillsDirs ?? []).map((root) => path.join(projectRoot, root, 'skills')),
|
|
57
|
+
];
|
|
58
58
|
let skillCount = 0;
|
|
59
59
|
for (const skillName of SKILL_NAMES) {
|
|
60
|
-
|
|
61
|
-
if (fs.existsSync(skillFile)) {
|
|
60
|
+
if (skillsDirs.some((skillsDir) => fs.existsSync(path.join(skillsDir, skillName, 'SKILL.md')))) {
|
|
62
61
|
skillCount++;
|
|
63
62
|
}
|
|
64
63
|
}
|
|
@@ -68,14 +67,108 @@ export function getToolSkillStatus(projectRoot, toolId) {
|
|
|
68
67
|
skillCount,
|
|
69
68
|
};
|
|
70
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* Checks whether a tool has at least one generated OpenSpec command file.
|
|
72
|
+
*/
|
|
73
|
+
export function toolHasAnyConfiguredCommand(projectPath, toolId) {
|
|
74
|
+
const adapter = CommandAdapterRegistry.get(toolId);
|
|
75
|
+
if (!adapter)
|
|
76
|
+
return false;
|
|
77
|
+
for (const commandId of COMMAND_IDS) {
|
|
78
|
+
const cmdPath = adapter.getFilePath(commandId);
|
|
79
|
+
const fullPath = path.isAbsolute(cmdPath) ? cmdPath : path.join(projectPath, cmdPath);
|
|
80
|
+
if (fs.existsSync(fullPath)) {
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Normalizes checkout artifacts that are not real content drift: a UTF-8 BOM and
|
|
88
|
+
* CRLF line endings, which a Windows clone with `core.autocrlf` reintroduces on
|
|
89
|
+
* every checkout of committed command files.
|
|
90
|
+
*/
|
|
91
|
+
function normalizeCommandContent(content) {
|
|
92
|
+
return content.replace(/^\uFEFF/, '').replace(/\r\n/g, '\n');
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Checks whether command files for a tool on disk match current generated command contents.
|
|
96
|
+
*
|
|
97
|
+
* Command files carry no version stamp, so content equality is the only available
|
|
98
|
+
* "is this current?" signal for a commands-only install.
|
|
99
|
+
*/
|
|
100
|
+
export function areCommandFilesUpToDate(projectRoot, toolId, options) {
|
|
101
|
+
const adapter = CommandAdapterRegistry.get(toolId);
|
|
102
|
+
if (!adapter)
|
|
103
|
+
return false;
|
|
104
|
+
let workflows;
|
|
105
|
+
if (options?.workflows) {
|
|
106
|
+
workflows = options.workflows;
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
try {
|
|
110
|
+
const globalCfg = getGlobalConfig();
|
|
111
|
+
const profile = globalCfg.profile ?? 'core';
|
|
112
|
+
workflows = getProfileWorkflows(profile, globalCfg.workflows);
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
workflows = ALL_WORKFLOWS;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const knownWorkflows = workflows.filter((w) => ALL_WORKFLOWS.includes(w));
|
|
119
|
+
const commandContents = getCommandContents(knownWorkflows);
|
|
120
|
+
const generatedCommands = generateCommands(commandContents, adapter);
|
|
121
|
+
if (generatedCommands.length === 0) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
for (const cmd of generatedCommands) {
|
|
125
|
+
const cmdPath = path.isAbsolute(cmd.path) ? cmd.path : path.join(projectRoot, cmd.path);
|
|
126
|
+
if (!fs.existsSync(cmdPath)) {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
const existingContent = fs.readFileSync(cmdPath, 'utf-8');
|
|
131
|
+
if (normalizeCommandContent(existingContent) !== normalizeCommandContent(cmd.fileContent)) {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// Also check no extra command files exist for deselected workflows
|
|
140
|
+
const desiredWorkflowSet = new Set(knownWorkflows);
|
|
141
|
+
for (const workflow of ALL_WORKFLOWS) {
|
|
142
|
+
if (desiredWorkflowSet.has(workflow))
|
|
143
|
+
continue;
|
|
144
|
+
const cmdPath = adapter.getFilePath(workflow);
|
|
145
|
+
const fullPath = path.isAbsolute(cmdPath) ? cmdPath : path.join(projectRoot, cmdPath);
|
|
146
|
+
if (fs.existsSync(fullPath)) {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
71
152
|
/**
|
|
72
153
|
* Gets the skill status for all tools with skillsDir configured.
|
|
73
154
|
*/
|
|
74
155
|
export function getToolStates(projectRoot) {
|
|
75
156
|
const states = new Map();
|
|
76
|
-
const
|
|
77
|
-
for (const
|
|
78
|
-
|
|
157
|
+
const tools = getSkillCapableTools();
|
|
158
|
+
for (const tool of tools) {
|
|
159
|
+
const skillStatus = getToolSkillStatus(projectRoot, tool.value);
|
|
160
|
+
const markerConfigured = Boolean(tool.skillsDir) &&
|
|
161
|
+
readSharedSkillTarget(projectRoot, tool.skillsDir) === tool.value;
|
|
162
|
+
states.set(tool.value, markerConfigured
|
|
163
|
+
? { ...skillStatus, configured: true }
|
|
164
|
+
: skillStatus);
|
|
165
|
+
}
|
|
166
|
+
const configuredTools = tools.filter((tool) => tool.skillsDir && states.get(tool.value)?.configured);
|
|
167
|
+
const activeSharedTargets = new Set(reconcileSharedSkillTargets(projectRoot, configuredTools).map((tool) => tool.value));
|
|
168
|
+
for (const tool of configuredTools) {
|
|
169
|
+
if (!activeSharedTargets.has(tool.value)) {
|
|
170
|
+
states.set(tool.value, { configured: false, fullyConfigured: false, skillCount: 0 });
|
|
171
|
+
}
|
|
79
172
|
}
|
|
80
173
|
return states;
|
|
81
174
|
}
|
|
@@ -109,11 +202,12 @@ export function extractGeneratedByVersion(skillFilePath) {
|
|
|
109
202
|
}
|
|
110
203
|
}
|
|
111
204
|
/**
|
|
112
|
-
* Gets version status for a tool by reading
|
|
205
|
+
* Gets version status for a tool by reading its skill files, falling back to a
|
|
206
|
+
* command-content fingerprint for installs that have commands but no skills.
|
|
113
207
|
*/
|
|
114
|
-
export function getToolVersionStatus(projectRoot, toolId, currentVersion) {
|
|
208
|
+
export function getToolVersionStatus(projectRoot, toolId, currentVersion, options) {
|
|
115
209
|
const tool = AI_TOOLS.find((t) => t.value === toolId);
|
|
116
|
-
if (!tool
|
|
210
|
+
if (!tool || !toolSupportsSkills(tool)) {
|
|
117
211
|
return {
|
|
118
212
|
toolId,
|
|
119
213
|
toolName: toolId,
|
|
@@ -122,17 +216,43 @@ export function getToolVersionStatus(projectRoot, toolId, currentVersion) {
|
|
|
122
216
|
needsUpdate: false,
|
|
123
217
|
};
|
|
124
218
|
}
|
|
125
|
-
const
|
|
219
|
+
const skillsDirs = [
|
|
220
|
+
resolveToolSkillsDir(projectRoot, tool),
|
|
221
|
+
...(tool.legacySkillsDirs ?? []).map((root) => path.join(projectRoot, root, 'skills')),
|
|
222
|
+
];
|
|
126
223
|
let generatedByVersion = null;
|
|
127
|
-
|
|
224
|
+
let foundSkill = false;
|
|
225
|
+
// 1. Find the first skill file that exists and read its version
|
|
128
226
|
for (const skillName of SKILL_NAMES) {
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
227
|
+
for (const skillsDir of skillsDirs) {
|
|
228
|
+
const skillFile = path.join(skillsDir, skillName, 'SKILL.md');
|
|
229
|
+
if (fs.existsSync(skillFile)) {
|
|
230
|
+
generatedByVersion = extractGeneratedByVersion(skillFile);
|
|
231
|
+
foundSkill = true;
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (foundSkill)
|
|
132
236
|
break;
|
|
237
|
+
}
|
|
238
|
+
const skillConfigured = getToolSkillStatus(projectRoot, toolId).configured;
|
|
239
|
+
const commandConfigured = toolHasAnyConfiguredCommand(projectRoot, toolId);
|
|
240
|
+
const markerConfigured = Boolean(tool.skillsDir) &&
|
|
241
|
+
readSharedSkillTarget(projectRoot, tool.skillsDir) === toolId;
|
|
242
|
+
const configured = skillConfigured || commandConfigured || markerConfigured;
|
|
243
|
+
// 2. Commands-only installs have no skill file to read a version from, so fall
|
|
244
|
+
// back to comparing the generated command content. Deliberately skipped when
|
|
245
|
+
// skill files exist: an unreadable version there must still force a rewrite.
|
|
246
|
+
if (!skillConfigured && commandConfigured && areCommandFilesUpToDate(projectRoot, toolId, options)) {
|
|
247
|
+
generatedByVersion = currentVersion;
|
|
248
|
+
}
|
|
249
|
+
if (!skillConfigured && !commandConfigured && markerConfigured) {
|
|
250
|
+
const delivery = getGlobalConfig().delivery ?? 'both';
|
|
251
|
+
if (!shouldGenerateSkillsForTool(toolId, delivery) &&
|
|
252
|
+
!shouldGenerateCommandsForTool(toolId, delivery)) {
|
|
253
|
+
generatedByVersion = currentVersion;
|
|
133
254
|
}
|
|
134
255
|
}
|
|
135
|
-
const configured = getToolSkillStatus(projectRoot, toolId).configured;
|
|
136
256
|
const needsUpdate = configured && (generatedByVersion === null || generatedByVersion !== currentVersion);
|
|
137
257
|
return {
|
|
138
258
|
toolId,
|
|
@@ -143,12 +263,22 @@ export function getToolVersionStatus(projectRoot, toolId, currentVersion) {
|
|
|
143
263
|
};
|
|
144
264
|
}
|
|
145
265
|
/**
|
|
146
|
-
* Gets all configured tools in the project.
|
|
266
|
+
* Gets all configured tools in the project (configured via skills or commands).
|
|
147
267
|
*/
|
|
148
268
|
export function getConfiguredTools(projectRoot) {
|
|
149
|
-
|
|
150
|
-
.filter((t) =>
|
|
151
|
-
|
|
269
|
+
const configured = AI_TOOLS
|
|
270
|
+
.filter((t) => {
|
|
271
|
+
if (!toolSupportsSkills(t))
|
|
272
|
+
return false;
|
|
273
|
+
return (getToolSkillStatus(projectRoot, t.value).configured ||
|
|
274
|
+
toolHasAnyConfiguredCommand(projectRoot, t.value) ||
|
|
275
|
+
(Boolean(t.skillsDir) &&
|
|
276
|
+
readSharedSkillTarget(projectRoot, t.skillsDir) === t.value));
|
|
277
|
+
});
|
|
278
|
+
const activeProjectTools = new Set(reconcileSharedSkillTargets(projectRoot, configured.filter((tool) => tool.skillsDir)).map((tool) => tool.value));
|
|
279
|
+
return configured
|
|
280
|
+
.filter((tool) => tool.globalSkillsDir || activeProjectTools.has(tool.value))
|
|
281
|
+
.map((tool) => tool.value);
|
|
152
282
|
}
|
|
153
283
|
/**
|
|
154
284
|
* Gets version status for all configured tools.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type AIToolOption } from './config.js';
|
|
2
|
+
/** Reads a valid-looking marker value without letting linked roots escape. */
|
|
3
|
+
export declare function readSharedSkillTarget(projectPath: string, skillsDir: string): string | undefined;
|
|
4
|
+
/**
|
|
5
|
+
* A shared skill root can only hold one rendered variant of each skill.
|
|
6
|
+
* Keep the writer recorded so later updates do not infer every tool that
|
|
7
|
+
* happens to use the same directory.
|
|
8
|
+
*/
|
|
9
|
+
export declare function reconcileSharedSkillTargets(projectPath: string, tools: AIToolOption[]): AIToolOption[];
|
|
10
|
+
/**
|
|
11
|
+
* Returns whether a tool is the active writer for its physical skills root.
|
|
12
|
+
* Non-shared roots are always active.
|
|
13
|
+
*/
|
|
14
|
+
export declare function isSharedSkillTargetActive(projectPath: string, toolId: string): boolean;
|
|
15
|
+
export declare function writeSharedSkillTarget(projectPath: string, toolId: string): void;
|
|
16
|
+
//# sourceMappingURL=shared-skill-target.d.ts.map
|