@fission-ai/openspec 1.6.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -2
- package/dist/cli/index.js +79 -8
- package/dist/commands/change.d.ts +0 -1
- package/dist/commands/change.js +105 -51
- package/dist/commands/config.js +14 -8
- package/dist/commands/doctor.js +9 -4
- package/dist/commands/feedback.js +74 -23
- package/dist/commands/schema.js +110 -31
- package/dist/commands/show.js +5 -2
- package/dist/commands/spec.js +49 -29
- package/dist/commands/validate.js +20 -5
- package/dist/commands/workflow/index.d.ts +1 -1
- package/dist/commands/workflow/index.js +1 -1
- package/dist/commands/workflow/instructions.d.ts +7 -1
- package/dist/commands/workflow/instructions.js +121 -33
- package/dist/commands/workflow/shared.d.ts +13 -2
- package/dist/commands/workflow/shared.js +34 -4
- package/dist/commands/workflow/status.js +9 -4
- package/dist/commands/workflow/templates.js +15 -5
- package/dist/core/archive.d.ts +14 -1
- package/dist/core/archive.js +1332 -155
- package/dist/core/artifact-graph/graph.d.ts +16 -0
- package/dist/core/artifact-graph/graph.js +30 -6
- 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 +1 -1
- package/dist/core/change-status-policy.js +2 -2
- package/dist/core/command-generation/adapters/amazon-q.d.ts +4 -0
- package/dist/core/command-generation/adapters/amazon-q.js +7 -1
- package/dist/core/command-generation/adapters/antigravity.js +2 -1
- package/dist/core/command-generation/adapters/auggie.js +2 -1
- package/dist/core/command-generation/adapters/bob.d.ts +5 -1
- package/dist/core/command-generation/adapters/bob.js +6 -5
- package/dist/core/command-generation/adapters/claude.js +1 -8
- package/dist/core/command-generation/adapters/codebuddy.js +3 -2
- package/dist/core/command-generation/adapters/continue.js +3 -2
- package/dist/core/command-generation/adapters/costrict.js +2 -1
- package/dist/core/command-generation/adapters/crush.js +5 -5
- package/dist/core/command-generation/adapters/cursor.js +2 -2
- 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 +2 -1
- package/dist/core/command-generation/adapters/gemini.js +37 -2
- package/dist/core/command-generation/adapters/github-copilot.js +2 -1
- package/dist/core/command-generation/adapters/iflow.js +5 -4
- package/dist/core/command-generation/adapters/index.d.ts +2 -2
- package/dist/core/command-generation/adapters/index.js +2 -2
- package/dist/core/command-generation/adapters/junie.js +2 -1
- package/dist/core/command-generation/adapters/kiro.js +2 -1
- package/dist/core/command-generation/adapters/lingma.js +5 -5
- package/dist/core/command-generation/adapters/oh-my-pi.d.ts +2 -2
- package/dist/core/command-generation/adapters/oh-my-pi.js +3 -5
- package/dist/core/command-generation/adapters/opencode.js +3 -5
- package/dist/core/command-generation/adapters/pi.d.ts +2 -2
- package/dist/core/command-generation/adapters/pi.js +3 -6
- package/dist/core/command-generation/adapters/qoder.js +5 -5
- package/dist/core/command-generation/adapters/qwen.d.ts +7 -3
- package/dist/core/command-generation/adapters/qwen.js +12 -7
- package/dist/core/command-generation/adapters/roocode.d.ts +4 -4
- package/dist/core/command-generation/adapters/roocode.js +4 -4
- package/dist/core/command-generation/adapters/trae.js +1 -21
- 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 +4 -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 -2
- package/dist/core/completions/generators/powershell-generator.js +15 -8
- package/dist/core/completions/installers/zsh-installer.d.ts +9 -0
- package/dist/core/completions/installers/zsh-installer.js +20 -4
- 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 +45 -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 +450 -69
- package/dist/core/legacy-cleanup.d.ts +59 -0
- package/dist/core/legacy-cleanup.js +253 -24
- package/dist/core/list.js +5 -7
- package/dist/core/migration.d.ts +73 -1
- package/dist/core/migration.js +360 -11
- package/dist/core/onboarding-commands.d.ts +30 -0
- package/dist/core/onboarding-commands.js +38 -0
- package/dist/core/parsers/change-parser.d.ts +17 -2
- package/dist/core/parsers/change-parser.js +33 -20
- 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.js +2 -1
- package/dist/core/parsers/requirement-blocks.d.ts +18 -0
- package/dist/core/parsers/requirement-blocks.js +120 -35
- package/dist/core/parsers/requirement-text.d.ts +1 -8
- package/dist/core/parsers/requirement-text.js +4 -42
- 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 +48 -49
- package/dist/core/project-config.d.ts +28 -6
- package/dist/core/project-config.js +108 -8
- 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/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-paths.d.ts +13 -0
- package/dist/core/shared/skill-paths.js +23 -0
- package/dist/core/shared/tool-detection.d.ts +25 -5
- package/dist/core/shared/tool-detection.js +166 -36
- 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 +659 -171
- package/dist/core/store/git.d.ts +13 -0
- package/dist/core/store/git.js +24 -0
- package/dist/core/templates/skill-templates.d.ts +1 -1
- package/dist/core/templates/skill-templates.js +1 -1
- package/dist/core/templates/workflows/apply-change.d.ts +10 -0
- package/dist/core/templates/workflows/apply-change.js +48 -158
- package/dist/core/templates/workflows/archive-change.js +170 -42
- package/dist/core/templates/workflows/bulk-archive-change.js +256 -75
- package/dist/core/templates/workflows/continue-change.js +38 -46
- package/dist/core/templates/workflows/explore.js +54 -18
- package/dist/core/templates/workflows/ff-change.js +46 -30
- package/dist/core/templates/workflows/new-change.js +2 -2
- package/dist/core/templates/workflows/onboard.js +13 -7
- package/dist/core/templates/workflows/propose.js +116 -46
- 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 +260 -32
- package/dist/core/templates/workflows/update-change.js +26 -16
- package/dist/core/templates/workflows/verify-change.js +16 -10
- package/dist/core/update.d.ts +40 -5
- package/dist/core/update.js +389 -93
- package/dist/core/validation/constants.d.ts +4 -1
- package/dist/core/validation/constants.js +4 -1
- package/dist/core/validation/validator.d.ts +22 -8
- package/dist/core/validation/validator.js +222 -50
- package/dist/core/version-check.d.ts +152 -0
- package/dist/core/version-check.js +731 -0
- package/dist/core/view.js +12 -17
- package/dist/prompts/searchable-multi-select.js +1 -1
- package/dist/telemetry/config.d.ts +3 -4
- package/dist/telemetry/index.d.ts +10 -5
- package/dist/telemetry/index.js +75 -41
- 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 +17 -0
- package/dist/utils/task-progress.js +41 -13
- package/package.json +14 -12
- package/schemas/spec-driven/schema.yaml +67 -10
- package/schemas/spec-driven/templates/design.md +2 -2
- package/schemas/spec-driven/templates/proposal.md +10 -4
- package/schemas/spec-driven/templates/spec.md +3 -0
- 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
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command Invocation
|
|
3
|
+
*
|
|
4
|
+
* How a tool spells an OpenSpec command has two parts, and only one of them
|
|
5
|
+
* can be read off the file the adapter writes:
|
|
6
|
+
*
|
|
7
|
+
* - The *name* comes from the file. `.../commands/opsx/<id>.md` is namespaced
|
|
8
|
+
* by its directory, so the tool registers `opsx:<id>` (Claude Code, Gemini,
|
|
9
|
+
* Crush, ...). `.../commands/opsx-<id>.md` names the command with the
|
|
10
|
+
* filename, so the tool registers `opsx-<id>` (Cursor, GitHub Copilot,
|
|
11
|
+
* OpenCode, ...).
|
|
12
|
+
* - The *prefix* is the tool's own and cannot be derived. Almost every tool
|
|
13
|
+
* uses `/`; Amazon Q loads these files into its prompt library, which is
|
|
14
|
+
* invoked with `@` (`@opsx-propose`), so its adapter declares that prefix.
|
|
15
|
+
*
|
|
16
|
+
* Deriving the name from `getFilePath` keeps generated cross-references and
|
|
17
|
+
* onboarding hints in step with the files OpenSpec actually writes. A
|
|
18
|
+
* hand-maintained list drifted before: only OpenCode was rewritten when the
|
|
19
|
+
* hyphen form was introduced (#727), and Cursor still advertised `/opsx:`
|
|
20
|
+
* commands its palette never registered (#1307). Carrying the prefix as
|
|
21
|
+
* adapter metadata rather than inferring it keeps the one tool that does not
|
|
22
|
+
* use a slash from being advertised as if it did.
|
|
23
|
+
*/
|
|
24
|
+
import type { ToolCommandAdapter } from './types.js';
|
|
25
|
+
export type CommandInvocationStyle = 'namespaced' | 'flat';
|
|
26
|
+
/**
|
|
27
|
+
* Everything needed to spell one of a tool's OpenSpec commands.
|
|
28
|
+
*/
|
|
29
|
+
export interface CommandInvocation {
|
|
30
|
+
/** How the command file names the command. */
|
|
31
|
+
style: CommandInvocationStyle;
|
|
32
|
+
/** What the user types before the name, e.g. `/` or Amazon Q's `@`. */
|
|
33
|
+
prefix: string;
|
|
34
|
+
}
|
|
35
|
+
/** The form these docs, command bodies, and skill templates are authored in. */
|
|
36
|
+
export declare const CANONICAL_INVOCATION: CommandInvocation;
|
|
37
|
+
/**
|
|
38
|
+
* Classifies a generated command file by the name the tool will answer to.
|
|
39
|
+
*
|
|
40
|
+
* The test is the filename, not the directory: an `opsx-` prefix means the
|
|
41
|
+
* filename is the command. Every other shape is treated as namespaced, which
|
|
42
|
+
* is what all seven `opsx/<id>.*` adapters need. An adapter that neither
|
|
43
|
+
* prefixes the filename nor nests under `opsx/` would land here too — none
|
|
44
|
+
* does, and the registry-wide test in invocation.test.ts fails if one appears.
|
|
45
|
+
*
|
|
46
|
+
* @param commandFilePath - Path returned by an adapter's `getFilePath`
|
|
47
|
+
* @returns 'flat' when the filename carries the `opsx-` prefix, otherwise
|
|
48
|
+
* 'namespaced'
|
|
49
|
+
*/
|
|
50
|
+
export declare function getInvocationStyleForPath(commandFilePath: string): CommandInvocationStyle;
|
|
51
|
+
/**
|
|
52
|
+
* Resolves how a tool's generated commands are invoked: the name from the
|
|
53
|
+
* files its adapter writes, the prefix from the adapter's own declaration.
|
|
54
|
+
*
|
|
55
|
+
* @param adapter - The tool-specific command adapter
|
|
56
|
+
* @returns The invocation shared by every command that adapter generates
|
|
57
|
+
*/
|
|
58
|
+
export declare function getInvocationForAdapter(adapter: ToolCommandAdapter): CommandInvocation;
|
|
59
|
+
/**
|
|
60
|
+
* Spells one command the way the tool registers it.
|
|
61
|
+
*
|
|
62
|
+
* @param invocation - The tool's invocation, from getInvocationForAdapter()
|
|
63
|
+
* @param commandId - The command identifier (e.g. 'apply')
|
|
64
|
+
* @returns What the user types, e.g. `/opsx:apply`, `/opsx-apply`, `@opsx-apply`
|
|
65
|
+
*/
|
|
66
|
+
export declare function formatCommandInvocation(invocation: CommandInvocation, commandId: string): string;
|
|
67
|
+
/**
|
|
68
|
+
* Whether a tool's invocation differs from the canonical `/opsx:<id>` that
|
|
69
|
+
* command bodies and skill templates are authored in — that is, whether
|
|
70
|
+
* generated text has to be rewritten for that tool at all.
|
|
71
|
+
*/
|
|
72
|
+
export declare function needsInvocationRewrite(invocation: CommandInvocation): boolean;
|
|
73
|
+
//# sourceMappingURL=invocation.d.ts.map
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command Invocation
|
|
3
|
+
*
|
|
4
|
+
* How a tool spells an OpenSpec command has two parts, and only one of them
|
|
5
|
+
* can be read off the file the adapter writes:
|
|
6
|
+
*
|
|
7
|
+
* - The *name* comes from the file. `.../commands/opsx/<id>.md` is namespaced
|
|
8
|
+
* by its directory, so the tool registers `opsx:<id>` (Claude Code, Gemini,
|
|
9
|
+
* Crush, ...). `.../commands/opsx-<id>.md` names the command with the
|
|
10
|
+
* filename, so the tool registers `opsx-<id>` (Cursor, GitHub Copilot,
|
|
11
|
+
* OpenCode, ...).
|
|
12
|
+
* - The *prefix* is the tool's own and cannot be derived. Almost every tool
|
|
13
|
+
* uses `/`; Amazon Q loads these files into its prompt library, which is
|
|
14
|
+
* invoked with `@` (`@opsx-propose`), so its adapter declares that prefix.
|
|
15
|
+
*
|
|
16
|
+
* Deriving the name from `getFilePath` keeps generated cross-references and
|
|
17
|
+
* onboarding hints in step with the files OpenSpec actually writes. A
|
|
18
|
+
* hand-maintained list drifted before: only OpenCode was rewritten when the
|
|
19
|
+
* hyphen form was introduced (#727), and Cursor still advertised `/opsx:`
|
|
20
|
+
* commands its palette never registered (#1307). Carrying the prefix as
|
|
21
|
+
* adapter metadata rather than inferring it keeps the one tool that does not
|
|
22
|
+
* use a slash from being advertised as if it did.
|
|
23
|
+
*/
|
|
24
|
+
import path from 'path';
|
|
25
|
+
/** The form these docs, command bodies, and skill templates are authored in. */
|
|
26
|
+
export const CANONICAL_INVOCATION = { style: 'namespaced', prefix: '/' };
|
|
27
|
+
/**
|
|
28
|
+
* Classifies a generated command file by the name the tool will answer to.
|
|
29
|
+
*
|
|
30
|
+
* The test is the filename, not the directory: an `opsx-` prefix means the
|
|
31
|
+
* filename is the command. Every other shape is treated as namespaced, which
|
|
32
|
+
* is what all seven `opsx/<id>.*` adapters need. An adapter that neither
|
|
33
|
+
* prefixes the filename nor nests under `opsx/` would land here too — none
|
|
34
|
+
* does, and the registry-wide test in invocation.test.ts fails if one appears.
|
|
35
|
+
*
|
|
36
|
+
* @param commandFilePath - Path returned by an adapter's `getFilePath`
|
|
37
|
+
* @returns 'flat' when the filename carries the `opsx-` prefix, otherwise
|
|
38
|
+
* 'namespaced'
|
|
39
|
+
*/
|
|
40
|
+
export function getInvocationStyleForPath(commandFilePath) {
|
|
41
|
+
return path.basename(commandFilePath).startsWith('opsx-') ? 'flat' : 'namespaced';
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Resolves how a tool's generated commands are invoked: the name from the
|
|
45
|
+
* files its adapter writes, the prefix from the adapter's own declaration.
|
|
46
|
+
*
|
|
47
|
+
* @param adapter - The tool-specific command adapter
|
|
48
|
+
* @returns The invocation shared by every command that adapter generates
|
|
49
|
+
*/
|
|
50
|
+
export function getInvocationForAdapter(adapter) {
|
|
51
|
+
return {
|
|
52
|
+
// Any command id works: every adapter applies one naming rule to all of them.
|
|
53
|
+
style: getInvocationStyleForPath(adapter.getFilePath('explore')),
|
|
54
|
+
prefix: adapter.invocationPrefix ?? CANONICAL_INVOCATION.prefix,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Spells one command the way the tool registers it.
|
|
59
|
+
*
|
|
60
|
+
* @param invocation - The tool's invocation, from getInvocationForAdapter()
|
|
61
|
+
* @param commandId - The command identifier (e.g. 'apply')
|
|
62
|
+
* @returns What the user types, e.g. `/opsx:apply`, `/opsx-apply`, `@opsx-apply`
|
|
63
|
+
*/
|
|
64
|
+
export function formatCommandInvocation(invocation, commandId) {
|
|
65
|
+
const separator = invocation.style === 'namespaced' ? ':' : '-';
|
|
66
|
+
return `${invocation.prefix}opsx${separator}${commandId}`;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Whether a tool's invocation differs from the canonical `/opsx:<id>` that
|
|
70
|
+
* command bodies and skill templates are authored in — that is, whether
|
|
71
|
+
* generated text has to be rewritten for that tool at all.
|
|
72
|
+
*/
|
|
73
|
+
export function needsInvocationRewrite(invocation) {
|
|
74
|
+
return (invocation.style !== CANONICAL_INVOCATION.style ||
|
|
75
|
+
invocation.prefix !== CANONICAL_INVOCATION.prefix);
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=invocation.js.map
|
|
@@ -10,7 +10,7 @@ import { auggieAdapter } from './adapters/auggie.js';
|
|
|
10
10
|
import { bobAdapter } from './adapters/bob.js';
|
|
11
11
|
import { claudeAdapter } from './adapters/claude.js';
|
|
12
12
|
import { clineAdapter } from './adapters/cline.js';
|
|
13
|
-
import {
|
|
13
|
+
import { devinAdapter } from './adapters/devin.js';
|
|
14
14
|
import { codebuddyAdapter } from './adapters/codebuddy.js';
|
|
15
15
|
import { continueAdapter } from './adapters/continue.js';
|
|
16
16
|
import { costrictAdapter } from './adapters/costrict.js';
|
|
@@ -31,7 +31,7 @@ import { lingmaAdapter } from './adapters/lingma.js';
|
|
|
31
31
|
import { qwenAdapter } from './adapters/qwen.js';
|
|
32
32
|
import { roocodeAdapter } from './adapters/roocode.js';
|
|
33
33
|
import { traeAdapter } from './adapters/trae.js';
|
|
34
|
-
import {
|
|
34
|
+
import { zcodeAdapter } from './adapters/zcode.js';
|
|
35
35
|
/**
|
|
36
36
|
* Registry for looking up tool command adapters.
|
|
37
37
|
*/
|
|
@@ -45,7 +45,7 @@ export class CommandAdapterRegistry {
|
|
|
45
45
|
CommandAdapterRegistry.register(bobAdapter);
|
|
46
46
|
CommandAdapterRegistry.register(claudeAdapter);
|
|
47
47
|
CommandAdapterRegistry.register(clineAdapter);
|
|
48
|
-
CommandAdapterRegistry.register(
|
|
48
|
+
CommandAdapterRegistry.register(devinAdapter);
|
|
49
49
|
CommandAdapterRegistry.register(codebuddyAdapter);
|
|
50
50
|
CommandAdapterRegistry.register(continueAdapter);
|
|
51
51
|
CommandAdapterRegistry.register(costrictAdapter);
|
|
@@ -66,7 +66,7 @@ export class CommandAdapterRegistry {
|
|
|
66
66
|
CommandAdapterRegistry.register(qwenAdapter);
|
|
67
67
|
CommandAdapterRegistry.register(roocodeAdapter);
|
|
68
68
|
CommandAdapterRegistry.register(traeAdapter);
|
|
69
|
-
CommandAdapterRegistry.register(
|
|
69
|
+
CommandAdapterRegistry.register(zcodeAdapter);
|
|
70
70
|
}
|
|
71
71
|
/**
|
|
72
72
|
* Register a tool command adapter.
|
|
@@ -34,9 +34,16 @@ export interface ToolCommandAdapter {
|
|
|
34
34
|
* Returns the file path for a command.
|
|
35
35
|
* @param commandId - The command identifier (e.g., 'explore')
|
|
36
36
|
* @returns Path from project root (e.g., '.claude/commands/opsx/explore.md').
|
|
37
|
-
* May be absolute for tools with global-scoped
|
|
37
|
+
* May be absolute for tools with global-scoped command files.
|
|
38
38
|
*/
|
|
39
39
|
getFilePath(commandId: string): string;
|
|
40
|
+
/**
|
|
41
|
+
* What the user types before the command name, when it is not the default
|
|
42
|
+
* `/`. Amazon Q loads these files into its prompt library, which is invoked
|
|
43
|
+
* with `@` (`@opsx-propose`), so its adapter sets '@'. The name itself is
|
|
44
|
+
* still derived from getFilePath — see invocation.ts.
|
|
45
|
+
*/
|
|
46
|
+
invocationPrefix?: string;
|
|
40
47
|
/**
|
|
41
48
|
* Formats the complete file content including frontmatter.
|
|
42
49
|
* @param content - The tool-agnostic command content
|
|
@@ -48,7 +55,7 @@ export interface ToolCommandAdapter {
|
|
|
48
55
|
* Result of generating a command file.
|
|
49
56
|
*/
|
|
50
57
|
export interface GeneratedCommand {
|
|
51
|
-
/** File path from project root, or absolute for global-scoped
|
|
58
|
+
/** File path from project root, or absolute for global-scoped command files */
|
|
52
59
|
path: string;
|
|
53
60
|
/** Complete file content (frontmatter + body) */
|
|
54
61
|
fileContent: string;
|
|
@@ -9,14 +9,28 @@
|
|
|
9
9
|
/**
|
|
10
10
|
* Escapes a string value for safe YAML output.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
12
|
+
* Always emits a double-quoted scalar. Quoting unconditionally keeps the
|
|
13
|
+
* value a string no matter what it holds: an unquoted `true`, `null` or
|
|
14
|
+
* `123` would round-trip as a boolean, null or number, and an unquoted
|
|
15
|
+
* value opening with a block indicator (`|`, `>`) or containing `: `
|
|
16
|
+
* is not valid YAML at all.
|
|
17
|
+
*
|
|
18
|
+
* Inside the quotes it escapes everything that cannot appear verbatim in a
|
|
19
|
+
* double-quoted scalar: backslash, double quote, line feed, carriage
|
|
20
|
+
* return, and the non-printable characters YAML's `c-printable` production
|
|
21
|
+
* excludes (C0 controls, DEL and C1 controls). Lenient parsers accept a raw
|
|
22
|
+
* control byte, but strict ones reject the document outright, so escaping
|
|
23
|
+
* them here keeps the generated file portable across every tool's parser.
|
|
17
24
|
*
|
|
18
25
|
* @param value - The raw string to embed in YAML frontmatter.
|
|
19
|
-
* @returns The value, double-quoted
|
|
26
|
+
* @returns The value as an escaped, double-quoted YAML scalar.
|
|
20
27
|
*/
|
|
21
28
|
export declare function escapeYamlValue(value: string): string;
|
|
29
|
+
/**
|
|
30
|
+
* Formats a tags array as a YAML array with proper escaping.
|
|
31
|
+
*
|
|
32
|
+
* @param tags - Array of tag strings.
|
|
33
|
+
* @returns Formatted YAML array string, e.g. '[tag1, tag2]'.
|
|
34
|
+
*/
|
|
35
|
+
export declare function formatTagsArray(tags: string[]): string;
|
|
22
36
|
//# sourceMappingURL=yaml.d.ts.map
|
|
@@ -9,30 +9,40 @@
|
|
|
9
9
|
/**
|
|
10
10
|
* Escapes a string value for safe YAML output.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
12
|
+
* Always emits a double-quoted scalar. Quoting unconditionally keeps the
|
|
13
|
+
* value a string no matter what it holds: an unquoted `true`, `null` or
|
|
14
|
+
* `123` would round-trip as a boolean, null or number, and an unquoted
|
|
15
|
+
* value opening with a block indicator (`|`, `>`) or containing `: `
|
|
16
|
+
* is not valid YAML at all.
|
|
17
|
+
*
|
|
18
|
+
* Inside the quotes it escapes everything that cannot appear verbatim in a
|
|
19
|
+
* double-quoted scalar: backslash, double quote, line feed, carriage
|
|
20
|
+
* return, and the non-printable characters YAML's `c-printable` production
|
|
21
|
+
* excludes (C0 controls, DEL and C1 controls). Lenient parsers accept a raw
|
|
22
|
+
* control byte, but strict ones reject the document outright, so escaping
|
|
23
|
+
* them here keeps the generated file portable across every tool's parser.
|
|
17
24
|
*
|
|
18
25
|
* @param value - The raw string to embed in YAML frontmatter.
|
|
19
|
-
* @returns The value, double-quoted
|
|
26
|
+
* @returns The value as an escaped, double-quoted YAML scalar.
|
|
20
27
|
*/
|
|
21
28
|
export function escapeYamlValue(value) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
//
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
29
|
+
const escaped = value
|
|
30
|
+
.replace(/\\/g, '\\\\')
|
|
31
|
+
.replace(/"/g, '\\"')
|
|
32
|
+
.replace(/\n/g, '\\n')
|
|
33
|
+
.replace(/\r/g, '\\r')
|
|
34
|
+
// Remaining non-printables have no dedicated escape; emit them as \xHH.
|
|
35
|
+
.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f-\x9f]/g, (char) => `\\x${char.charCodeAt(0).toString(16).padStart(2, '0')}`);
|
|
36
|
+
return `"${escaped}"`;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Formats a tags array as a YAML array with proper escaping.
|
|
40
|
+
*
|
|
41
|
+
* @param tags - Array of tag strings.
|
|
42
|
+
* @returns Formatted YAML array string, e.g. '[tag1, tag2]'.
|
|
43
|
+
*/
|
|
44
|
+
export function formatTagsArray(tags) {
|
|
45
|
+
const escapedTags = tags.map((tag) => escapeYamlValue(tag));
|
|
46
|
+
return `[${escapedTags.join(', ')}]`;
|
|
37
47
|
}
|
|
38
48
|
//# sourceMappingURL=yaml.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type CommandInvocation } from './command-generation/invocation.js';
|
|
2
|
+
import type { Delivery } from './global-config.js';
|
|
3
|
+
export type CommandSurfaceCapability = 'adapter-backed' | 'skills-invocable' | 'none';
|
|
4
|
+
/**
|
|
5
|
+
* How the tool spells its OpenSpec commands: the name from the command files
|
|
6
|
+
* its adapter writes, the prefix the adapter declares. Returns undefined for
|
|
7
|
+
* tools with no command adapter, which have no command names to spell.
|
|
8
|
+
*/
|
|
9
|
+
export declare function resolveCommandInvocation(toolId: string): CommandInvocation | undefined;
|
|
10
|
+
export declare function resolveCommandSurfaceCapability(toolId: string): CommandSurfaceCapability;
|
|
11
|
+
export declare function shouldGenerateSkillsForTool(toolId: string, delivery: Delivery): boolean;
|
|
12
|
+
export declare function shouldRemoveSkillsForTool(toolId: string, delivery: Delivery): boolean;
|
|
13
|
+
export declare function shouldGenerateCommandsForTool(toolId: string, delivery: Delivery): boolean;
|
|
14
|
+
export declare function shouldReconcileCommandFilesForTool(toolId: string, delivery: Delivery): boolean;
|
|
15
|
+
//# sourceMappingURL=command-surface.d.ts.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { CommandAdapterRegistry } from './command-generation/index.js';
|
|
2
|
+
import { getInvocationForAdapter } from './command-generation/invocation.js';
|
|
3
|
+
/**
|
|
4
|
+
* How the tool spells its OpenSpec commands: the name from the command files
|
|
5
|
+
* its adapter writes, the prefix the adapter declares. Returns undefined for
|
|
6
|
+
* tools with no command adapter, which have no command names to spell.
|
|
7
|
+
*/
|
|
8
|
+
export function resolveCommandInvocation(toolId) {
|
|
9
|
+
const adapter = CommandAdapterRegistry.get(toolId);
|
|
10
|
+
return adapter ? getInvocationForAdapter(adapter) : undefined;
|
|
11
|
+
}
|
|
12
|
+
export function resolveCommandSurfaceCapability(toolId) {
|
|
13
|
+
if (CommandAdapterRegistry.has(toolId)) {
|
|
14
|
+
return 'adapter-backed';
|
|
15
|
+
}
|
|
16
|
+
if (toolId === 'codex') {
|
|
17
|
+
return 'skills-invocable';
|
|
18
|
+
}
|
|
19
|
+
return 'none';
|
|
20
|
+
}
|
|
21
|
+
export function shouldGenerateSkillsForTool(toolId, delivery) {
|
|
22
|
+
return delivery !== 'commands' || resolveCommandSurfaceCapability(toolId) === 'skills-invocable';
|
|
23
|
+
}
|
|
24
|
+
export function shouldRemoveSkillsForTool(toolId, delivery) {
|
|
25
|
+
return delivery === 'commands' && resolveCommandSurfaceCapability(toolId) !== 'skills-invocable';
|
|
26
|
+
}
|
|
27
|
+
export function shouldGenerateCommandsForTool(toolId, delivery) {
|
|
28
|
+
return delivery !== 'skills' && resolveCommandSurfaceCapability(toolId) === 'adapter-backed';
|
|
29
|
+
}
|
|
30
|
+
export function shouldReconcileCommandFilesForTool(toolId, delivery) {
|
|
31
|
+
return delivery === 'skills' && resolveCommandSurfaceCapability(toolId) === 'adapter-backed';
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=command-surface.js.map
|
|
@@ -22,6 +22,18 @@ export const COMMAND_REGISTRY = [
|
|
|
22
22
|
takesValue: true,
|
|
23
23
|
values: ['core', 'custom'],
|
|
24
24
|
},
|
|
25
|
+
{
|
|
26
|
+
name: 'no-animation',
|
|
27
|
+
description: 'Show a static welcome screen instead of the animated one',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: 'copilot-cloud',
|
|
31
|
+
description: 'Generate GitHub Copilot cloud coding-agent files (opt-in; default: prompt)',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: 'no-copilot-cloud',
|
|
35
|
+
description: 'Skip generating GitHub Copilot cloud coding-agent files',
|
|
36
|
+
},
|
|
25
37
|
],
|
|
26
38
|
},
|
|
27
39
|
{
|
|
@@ -62,7 +74,9 @@ export const COMMAND_REGISTRY = [
|
|
|
62
74
|
{
|
|
63
75
|
name: 'view',
|
|
64
76
|
description: 'Display an interactive dashboard of specs and changes',
|
|
65
|
-
flags: [
|
|
77
|
+
flags: [
|
|
78
|
+
COMMON_FLAGS.store,
|
|
79
|
+
],
|
|
66
80
|
},
|
|
67
81
|
{
|
|
68
82
|
name: 'validate',
|
|
@@ -177,7 +191,7 @@ export const COMMAND_REGISTRY = [
|
|
|
177
191
|
},
|
|
178
192
|
{
|
|
179
193
|
name: 'instructions',
|
|
180
|
-
description: 'Output enriched instructions for
|
|
194
|
+
description: 'Output enriched instructions for artifacts, apply, or archive',
|
|
181
195
|
acceptsPositional: true,
|
|
182
196
|
positionals: [{ name: 'artifact', optional: true }],
|
|
183
197
|
flags: [
|
|
@@ -164,6 +164,20 @@ Register-ArgumentCompleter -CommandName openspec -ScriptBlock $openspecCompleter
|
|
|
164
164
|
return lines;
|
|
165
165
|
}
|
|
166
166
|
generateIndexedPositionalCompletion(positionals, flags, firstPositionalTokenIndex, indent) {
|
|
167
|
+
const caseLines = [];
|
|
168
|
+
for (const [index, positional] of positionals.entries()) {
|
|
169
|
+
const completion = this.generatePositionalCompletion(positional.type, indent + ' ');
|
|
170
|
+
if (completion.length === 0)
|
|
171
|
+
continue;
|
|
172
|
+
caseLines.push(`${indent} ${index} {`);
|
|
173
|
+
caseLines.push(...completion);
|
|
174
|
+
caseLines.push(`${indent} }`);
|
|
175
|
+
}
|
|
176
|
+
// A switch with no clauses is a PowerShell parse error, so when no
|
|
177
|
+
// positional produces completions skip the whole block (it would only
|
|
178
|
+
// feed the empty switch anyway).
|
|
179
|
+
if (caseLines.length === 0)
|
|
180
|
+
return [];
|
|
167
181
|
const lines = [];
|
|
168
182
|
const valueFlags = this.generateValueFlags(flags);
|
|
169
183
|
if (valueFlags.length > 0) {
|
|
@@ -192,14 +206,7 @@ Register-ArgumentCompleter -CommandName openspec -ScriptBlock $openspecCompleter
|
|
|
192
206
|
lines.push(`${indent}}`);
|
|
193
207
|
lines.push('');
|
|
194
208
|
lines.push(`${indent}switch ($positionalIndex) {`);
|
|
195
|
-
|
|
196
|
-
const completion = this.generatePositionalCompletion(positional.type, indent + ' ');
|
|
197
|
-
if (completion.length === 0)
|
|
198
|
-
continue;
|
|
199
|
-
lines.push(`${indent} ${index} {`);
|
|
200
|
-
lines.push(...completion);
|
|
201
|
-
lines.push(`${indent} }`);
|
|
202
|
-
}
|
|
209
|
+
lines.push(...caseLines);
|
|
203
210
|
lines.push(`${indent}}`);
|
|
204
211
|
return lines;
|
|
205
212
|
}
|
|
@@ -16,6 +16,15 @@ export declare class ZshInstaller {
|
|
|
16
16
|
* @returns true if Oh My Zsh is detected via $ZSH env var or directory exists
|
|
17
17
|
*/
|
|
18
18
|
isOhMyZshInstalled(): Promise<boolean>;
|
|
19
|
+
/**
|
|
20
|
+
* Oh My Zsh exports its root as $ZSH; honor a custom location, or the
|
|
21
|
+
* completion lands in a ~/.oh-my-zsh tree that nothing ever loads.
|
|
22
|
+
*/
|
|
23
|
+
private ohMyZshRoot;
|
|
24
|
+
/**
|
|
25
|
+
* The custom dir is separately relocatable via $ZSH_CUSTOM.
|
|
26
|
+
*/
|
|
27
|
+
private ohMyZshCustomDir;
|
|
19
28
|
/**
|
|
20
29
|
* Get the appropriate installation path for the completion script
|
|
21
30
|
*
|
|
@@ -29,15 +29,27 @@ export class ZshInstaller {
|
|
|
29
29
|
return true;
|
|
30
30
|
}
|
|
31
31
|
// Fall back to checking for ~/.oh-my-zsh directory
|
|
32
|
-
const ohMyZshPath = path.join(this.homeDir, '.oh-my-zsh');
|
|
33
32
|
try {
|
|
34
|
-
const stat = await fs.stat(
|
|
33
|
+
const stat = await fs.stat(this.ohMyZshRoot());
|
|
35
34
|
return stat.isDirectory();
|
|
36
35
|
}
|
|
37
36
|
catch {
|
|
38
37
|
return false;
|
|
39
38
|
}
|
|
40
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Oh My Zsh exports its root as $ZSH; honor a custom location, or the
|
|
42
|
+
* completion lands in a ~/.oh-my-zsh tree that nothing ever loads.
|
|
43
|
+
*/
|
|
44
|
+
ohMyZshRoot() {
|
|
45
|
+
return process.env.ZSH || path.join(this.homeDir, '.oh-my-zsh');
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The custom dir is separately relocatable via $ZSH_CUSTOM.
|
|
49
|
+
*/
|
|
50
|
+
ohMyZshCustomDir() {
|
|
51
|
+
return process.env.ZSH_CUSTOM || path.join(this.ohMyZshRoot(), 'custom');
|
|
52
|
+
}
|
|
41
53
|
/**
|
|
42
54
|
* Get the appropriate installation path for the completion script
|
|
43
55
|
*
|
|
@@ -48,7 +60,7 @@ export class ZshInstaller {
|
|
|
48
60
|
if (isOhMyZsh) {
|
|
49
61
|
// Oh My Zsh custom completions directory
|
|
50
62
|
return {
|
|
51
|
-
path: path.join(this.
|
|
63
|
+
path: path.join(this.ohMyZshCustomDir(), 'completions', '_openspec'),
|
|
52
64
|
isOhMyZsh: true,
|
|
53
65
|
};
|
|
54
66
|
}
|
|
@@ -292,10 +304,14 @@ export class ZshInstaller {
|
|
|
292
304
|
* @returns Array of guidance strings, or undefined if not needed
|
|
293
305
|
*/
|
|
294
306
|
generateOhMyZshFpathGuidance(completionsDir) {
|
|
307
|
+
// One fpath entry per line, matched as a literal: a relocated $ZSH_CUSTOM
|
|
308
|
+
// need not contain "custom/completions", and the path may hold characters
|
|
309
|
+
// grep would otherwise read as a pattern. Single-quoted for the shell.
|
|
310
|
+
const quotedDir = `'${completionsDir.replace(/'/g, `'\\''`)}'`;
|
|
295
311
|
return [
|
|
296
312
|
'Note: Oh My Zsh typically auto-loads completions from custom/completions.',
|
|
297
313
|
`Verify that ${completionsDir} is in your fpath by running:`,
|
|
298
|
-
|
|
314
|
+
` printf '%s\\n' $fpath | grep -F ${quotedDir}`,
|
|
299
315
|
'',
|
|
300
316
|
'If not found, completions may not work. Restart your shell to ensure changes take effect.',
|
|
301
317
|
];
|
|
@@ -2,7 +2,7 @@ import type { ProjectConfig } from './project-config.js';
|
|
|
2
2
|
/**
|
|
3
3
|
* Serialize config to YAML string with helpful comments.
|
|
4
4
|
*
|
|
5
|
-
* @param config - Partial config object (schema required,
|
|
5
|
+
* @param config - Partial config object (schema required, other fields optional)
|
|
6
6
|
* @returns YAML string ready to write to file
|
|
7
7
|
*/
|
|
8
8
|
export declare function serializeConfig(config: Partial<ProjectConfig>): string;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Serialize config to YAML string with helpful comments.
|
|
3
3
|
*
|
|
4
|
-
* @param config - Partial config object (schema required,
|
|
4
|
+
* @param config - Partial config object (schema required, other fields optional)
|
|
5
5
|
* @returns YAML string ready to write to file
|
|
6
6
|
*/
|
|
7
7
|
export function serializeConfig(config) {
|
|
@@ -29,6 +29,19 @@ export function serializeConfig(config) {
|
|
|
29
29
|
lines.push('# - Always include a "Non-goals" section');
|
|
30
30
|
lines.push('# tasks:');
|
|
31
31
|
lines.push('# - Break tasks into chunks of max 2 hours');
|
|
32
|
+
lines.push('');
|
|
33
|
+
// Operation guidance section with comments
|
|
34
|
+
lines.push('# Per-operation guidance (optional)');
|
|
35
|
+
lines.push('# Add advisory guidance for how apply and archive work should be conducted.');
|
|
36
|
+
lines.push('# This is separate from artifact rules above.');
|
|
37
|
+
lines.push('# Example:');
|
|
38
|
+
lines.push('# operations:');
|
|
39
|
+
lines.push('# apply:');
|
|
40
|
+
lines.push('# guidance:');
|
|
41
|
+
lines.push('# - Keep test summaries concise');
|
|
42
|
+
lines.push('# archive:');
|
|
43
|
+
lines.push('# guidance:');
|
|
44
|
+
lines.push('# - Summarize the archive outcome before finishing');
|
|
32
45
|
return lines.join('\n') + '\n';
|
|
33
46
|
}
|
|
34
47
|
//# sourceMappingURL=config-prompts.js.map
|
|
@@ -11,16 +11,25 @@ export declare const GlobalConfigSchema: z.ZodObject<{
|
|
|
11
11
|
}>>>;
|
|
12
12
|
delivery: z.ZodDefault<z.ZodOptional<z.ZodEnum<{
|
|
13
13
|
commands: "commands";
|
|
14
|
-
skills: "skills";
|
|
15
14
|
both: "both";
|
|
15
|
+
skills: "skills";
|
|
16
16
|
}>>>;
|
|
17
17
|
workflows: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
18
|
+
defaultStore: z.ZodOptional<z.ZodString>;
|
|
19
|
+
telemetry: z.ZodOptional<z.ZodObject<{
|
|
20
|
+
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
21
|
+
}, z.core.$loose>>;
|
|
18
22
|
}, z.core.$loose>;
|
|
19
23
|
export type GlobalConfigType = z.infer<typeof GlobalConfigSchema>;
|
|
20
24
|
/**
|
|
21
25
|
* Default configuration values.
|
|
22
26
|
*/
|
|
23
27
|
export declare const DEFAULT_CONFIG: GlobalConfigType;
|
|
28
|
+
/**
|
|
29
|
+
* True when a dot-notation key path contains a prototype-reaching segment.
|
|
30
|
+
* Callers that bypass key validation (e.g. --allow-unknown) still must not bypass this.
|
|
31
|
+
*/
|
|
32
|
+
export declare function hasUnsafeKeySegment(path: string): boolean;
|
|
24
33
|
/**
|
|
25
34
|
* Validate a config key path for CLI set operations.
|
|
26
35
|
* Unknown top-level keys are rejected unless explicitly allowed by the caller.
|