@fission-ai/openspec 0.17.2 → 0.19.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 +52 -0
- package/dist/cli/index.js +39 -3
- package/dist/commands/artifact-workflow.d.ts +17 -0
- package/dist/commands/artifact-workflow.js +823 -0
- package/dist/commands/completion.js +42 -6
- package/dist/core/archive.d.ts +0 -5
- package/dist/core/archive.js +4 -257
- package/dist/core/artifact-graph/graph.d.ts +56 -0
- package/dist/core/artifact-graph/graph.js +141 -0
- package/dist/core/artifact-graph/index.d.ts +7 -0
- package/dist/core/artifact-graph/index.js +13 -0
- package/dist/core/artifact-graph/instruction-loader.d.ts +130 -0
- package/dist/core/artifact-graph/instruction-loader.js +173 -0
- package/dist/core/artifact-graph/resolver.d.ts +61 -0
- package/dist/core/artifact-graph/resolver.js +187 -0
- package/dist/core/artifact-graph/schema.d.ts +13 -0
- package/dist/core/artifact-graph/schema.js +108 -0
- package/dist/core/artifact-graph/state.d.ts +12 -0
- package/dist/core/artifact-graph/state.js +54 -0
- package/dist/core/artifact-graph/types.d.ts +45 -0
- package/dist/core/artifact-graph/types.js +43 -0
- package/dist/core/completions/command-registry.js +7 -1
- package/dist/core/completions/factory.d.ts +15 -2
- package/dist/core/completions/factory.js +19 -1
- package/dist/core/completions/generators/bash-generator.d.ts +32 -0
- package/dist/core/completions/generators/bash-generator.js +174 -0
- package/dist/core/completions/generators/fish-generator.d.ts +32 -0
- package/dist/core/completions/generators/fish-generator.js +157 -0
- package/dist/core/completions/generators/powershell-generator.d.ts +32 -0
- package/dist/core/completions/generators/powershell-generator.js +198 -0
- package/dist/core/completions/generators/zsh-generator.d.ts +0 -14
- package/dist/core/completions/generators/zsh-generator.js +55 -124
- package/dist/core/completions/installers/bash-installer.d.ts +87 -0
- package/dist/core/completions/installers/bash-installer.js +318 -0
- package/dist/core/completions/installers/fish-installer.d.ts +43 -0
- package/dist/core/completions/installers/fish-installer.js +143 -0
- package/dist/core/completions/installers/powershell-installer.d.ts +88 -0
- package/dist/core/completions/installers/powershell-installer.js +327 -0
- package/dist/core/completions/installers/zsh-installer.d.ts +1 -12
- package/dist/core/completions/templates/bash-templates.d.ts +6 -0
- package/dist/core/completions/templates/bash-templates.js +24 -0
- package/dist/core/completions/templates/fish-templates.d.ts +7 -0
- package/dist/core/completions/templates/fish-templates.js +39 -0
- package/dist/core/completions/templates/powershell-templates.d.ts +6 -0
- package/dist/core/completions/templates/powershell-templates.js +25 -0
- package/dist/core/completions/templates/zsh-templates.d.ts +6 -0
- package/dist/core/completions/templates/zsh-templates.js +36 -0
- package/dist/core/config.js +1 -0
- package/dist/core/configurators/slash/codebuddy.js +6 -9
- package/dist/core/configurators/slash/continue.d.ts +9 -0
- package/dist/core/configurators/slash/continue.js +46 -0
- package/dist/core/configurators/slash/registry.js +3 -0
- package/dist/core/converters/json-converter.js +2 -1
- package/dist/core/global-config.d.ts +10 -0
- package/dist/core/global-config.js +28 -0
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.js +1 -1
- package/dist/core/list.d.ts +6 -1
- package/dist/core/list.js +88 -6
- package/dist/core/specs-apply.d.ts +73 -0
- package/dist/core/specs-apply.js +384 -0
- package/dist/core/templates/skill-templates.d.ts +86 -0
- package/dist/core/templates/skill-templates.js +1934 -0
- package/dist/core/update.js +1 -1
- package/dist/core/validation/validator.js +2 -1
- package/dist/core/view.js +28 -8
- package/dist/telemetry/config.d.ts +32 -0
- package/dist/telemetry/config.js +68 -0
- package/dist/telemetry/index.d.ts +31 -0
- package/dist/telemetry/index.js +145 -0
- package/dist/utils/change-metadata.d.ts +47 -0
- package/dist/utils/change-metadata.js +130 -0
- package/dist/utils/change-utils.d.ts +51 -0
- package/dist/utils/change-utils.js +100 -0
- package/dist/utils/file-system.d.ts +11 -0
- package/dist/utils/file-system.js +50 -2
- package/dist/utils/index.d.ts +3 -1
- package/dist/utils/index.js +4 -1
- package/package.json +5 -1
- package/schemas/spec-driven/schema.yaml +148 -0
- package/schemas/spec-driven/templates/design.md +19 -0
- package/schemas/spec-driven/templates/proposal.md +23 -0
- package/schemas/spec-driven/templates/spec.md +8 -0
- package/schemas/spec-driven/templates/tasks.md +9 -0
- package/schemas/tdd/schema.yaml +213 -0
- package/schemas/tdd/templates/docs.md +15 -0
- package/schemas/tdd/templates/implementation.md +11 -0
- package/schemas/tdd/templates/spec.md +11 -0
- package/schemas/tdd/templates/test.md +11 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import fg from 'fast-glob';
|
|
4
|
+
import { FileSystemUtils } from '../../utils/file-system.js';
|
|
5
|
+
/**
|
|
6
|
+
* Detects which artifacts are completed by checking file existence in the change directory.
|
|
7
|
+
* Returns a Set of completed artifact IDs.
|
|
8
|
+
*
|
|
9
|
+
* @param graph - The artifact graph to check
|
|
10
|
+
* @param changeDir - The change directory to scan for files
|
|
11
|
+
* @returns Set of artifact IDs whose generated files exist
|
|
12
|
+
*/
|
|
13
|
+
export function detectCompleted(graph, changeDir) {
|
|
14
|
+
const completed = new Set();
|
|
15
|
+
// Handle missing change directory gracefully
|
|
16
|
+
if (!fs.existsSync(changeDir)) {
|
|
17
|
+
return completed;
|
|
18
|
+
}
|
|
19
|
+
for (const artifact of graph.getAllArtifacts()) {
|
|
20
|
+
if (isArtifactComplete(artifact.generates, changeDir)) {
|
|
21
|
+
completed.add(artifact.id);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return completed;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Checks if an artifact is complete by checking if its generated file(s) exist.
|
|
28
|
+
* Supports both simple paths and glob patterns.
|
|
29
|
+
*/
|
|
30
|
+
function isArtifactComplete(generates, changeDir) {
|
|
31
|
+
const fullPattern = path.join(changeDir, generates);
|
|
32
|
+
// Check if it's a glob pattern
|
|
33
|
+
if (isGlobPattern(generates)) {
|
|
34
|
+
return hasGlobMatches(fullPattern);
|
|
35
|
+
}
|
|
36
|
+
// Simple file path - check if file exists
|
|
37
|
+
return fs.existsSync(fullPattern);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Checks if a path contains glob pattern characters.
|
|
41
|
+
*/
|
|
42
|
+
function isGlobPattern(pattern) {
|
|
43
|
+
return pattern.includes('*') || pattern.includes('?') || pattern.includes('[');
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Checks if a glob pattern has any matches.
|
|
47
|
+
* Normalizes Windows backslashes to forward slashes for cross-platform glob compatibility.
|
|
48
|
+
*/
|
|
49
|
+
function hasGlobMatches(pattern) {
|
|
50
|
+
const normalizedPattern = FileSystemUtils.toPosixPath(pattern);
|
|
51
|
+
const matches = fg.sync(normalizedPattern, { onlyFiles: true });
|
|
52
|
+
return matches.length > 0;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=state.js.map
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const ArtifactSchema: z.ZodObject<{
|
|
3
|
+
id: z.ZodString;
|
|
4
|
+
generates: z.ZodString;
|
|
5
|
+
description: z.ZodString;
|
|
6
|
+
template: z.ZodString;
|
|
7
|
+
instruction: z.ZodOptional<z.ZodString>;
|
|
8
|
+
requires: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
9
|
+
}, z.core.$strip>;
|
|
10
|
+
export declare const ApplyPhaseSchema: z.ZodObject<{
|
|
11
|
+
requires: z.ZodArray<z.ZodString>;
|
|
12
|
+
tracks: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
13
|
+
instruction: z.ZodOptional<z.ZodString>;
|
|
14
|
+
}, z.core.$strip>;
|
|
15
|
+
export declare const SchemaYamlSchema: z.ZodObject<{
|
|
16
|
+
name: z.ZodString;
|
|
17
|
+
version: z.ZodNumber;
|
|
18
|
+
description: z.ZodOptional<z.ZodString>;
|
|
19
|
+
artifacts: z.ZodArray<z.ZodObject<{
|
|
20
|
+
id: z.ZodString;
|
|
21
|
+
generates: z.ZodString;
|
|
22
|
+
description: z.ZodString;
|
|
23
|
+
template: z.ZodString;
|
|
24
|
+
instruction: z.ZodOptional<z.ZodString>;
|
|
25
|
+
requires: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
26
|
+
}, z.core.$strip>>;
|
|
27
|
+
apply: z.ZodOptional<z.ZodObject<{
|
|
28
|
+
requires: z.ZodArray<z.ZodString>;
|
|
29
|
+
tracks: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
30
|
+
instruction: z.ZodOptional<z.ZodString>;
|
|
31
|
+
}, z.core.$strip>>;
|
|
32
|
+
}, z.core.$strip>;
|
|
33
|
+
export type Artifact = z.infer<typeof ArtifactSchema>;
|
|
34
|
+
export type ApplyPhase = z.infer<typeof ApplyPhaseSchema>;
|
|
35
|
+
export type SchemaYaml = z.infer<typeof SchemaYamlSchema>;
|
|
36
|
+
export declare const ChangeMetadataSchema: z.ZodObject<{
|
|
37
|
+
schema: z.ZodString;
|
|
38
|
+
created: z.ZodOptional<z.ZodString>;
|
|
39
|
+
}, z.core.$strip>;
|
|
40
|
+
export type ChangeMetadata = z.infer<typeof ChangeMetadataSchema>;
|
|
41
|
+
export type CompletedSet = Set<string>;
|
|
42
|
+
export interface BlockedArtifacts {
|
|
43
|
+
[artifactId: string]: string[];
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
// Artifact definition schema
|
|
3
|
+
export const ArtifactSchema = z.object({
|
|
4
|
+
id: z.string().min(1, { error: 'Artifact ID is required' }),
|
|
5
|
+
generates: z.string().min(1, { error: 'generates field is required' }),
|
|
6
|
+
description: z.string(),
|
|
7
|
+
template: z.string().min(1, { error: 'template field is required' }),
|
|
8
|
+
instruction: z.string().optional(),
|
|
9
|
+
requires: z.array(z.string()).default([]),
|
|
10
|
+
});
|
|
11
|
+
// Apply phase configuration for schema-aware apply instructions
|
|
12
|
+
export const ApplyPhaseSchema = z.object({
|
|
13
|
+
// Artifact IDs that must exist before apply is available
|
|
14
|
+
requires: z.array(z.string()).min(1, { error: 'At least one required artifact' }),
|
|
15
|
+
// Path to file with checkboxes for progress (relative to change dir), or null if no tracking
|
|
16
|
+
tracks: z.string().nullable().optional(),
|
|
17
|
+
// Custom guidance for the apply phase
|
|
18
|
+
instruction: z.string().optional(),
|
|
19
|
+
});
|
|
20
|
+
// Full schema YAML structure
|
|
21
|
+
export const SchemaYamlSchema = z.object({
|
|
22
|
+
name: z.string().min(1, { error: 'Schema name is required' }),
|
|
23
|
+
version: z.number().int().positive({ error: 'Version must be a positive integer' }),
|
|
24
|
+
description: z.string().optional(),
|
|
25
|
+
artifacts: z.array(ArtifactSchema).min(1, { error: 'At least one artifact required' }),
|
|
26
|
+
// Optional apply phase configuration (for schema-aware apply instructions)
|
|
27
|
+
apply: ApplyPhaseSchema.optional(),
|
|
28
|
+
});
|
|
29
|
+
// Per-change metadata schema
|
|
30
|
+
// Note: schema field is validated at parse time against available schemas
|
|
31
|
+
// using a lazy import to avoid circular dependencies
|
|
32
|
+
export const ChangeMetadataSchema = z.object({
|
|
33
|
+
// Required: which workflow schema this change uses
|
|
34
|
+
schema: z.string().min(1, { message: 'schema is required' }),
|
|
35
|
+
// Optional: creation timestamp (ISO date string)
|
|
36
|
+
created: z
|
|
37
|
+
.string()
|
|
38
|
+
.regex(/^\d{4}-\d{2}-\d{2}$/, {
|
|
39
|
+
message: 'created must be YYYY-MM-DD format',
|
|
40
|
+
})
|
|
41
|
+
.optional(),
|
|
42
|
+
});
|
|
43
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -281,7 +281,13 @@ export const COMMAND_REGISTRY = [
|
|
|
281
281
|
description: 'Uninstall completion script for a shell',
|
|
282
282
|
acceptsPositional: true,
|
|
283
283
|
positionalType: 'shell',
|
|
284
|
-
flags: [
|
|
284
|
+
flags: [
|
|
285
|
+
{
|
|
286
|
+
name: 'yes',
|
|
287
|
+
short: 'y',
|
|
288
|
+
description: 'Skip confirmation prompts',
|
|
289
|
+
},
|
|
290
|
+
],
|
|
285
291
|
},
|
|
286
292
|
],
|
|
287
293
|
},
|
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
import { CompletionGenerator } from './types.js';
|
|
2
|
-
import { InstallationResult } from './installers/zsh-installer.js';
|
|
3
2
|
import { SupportedShell } from '../../utils/shell-detection.js';
|
|
3
|
+
/**
|
|
4
|
+
* Common installation result interface
|
|
5
|
+
*/
|
|
6
|
+
export interface InstallationResult {
|
|
7
|
+
success: boolean;
|
|
8
|
+
installedPath?: string;
|
|
9
|
+
backupPath?: string;
|
|
10
|
+
message: string;
|
|
11
|
+
instructions?: string[];
|
|
12
|
+
warnings?: string[];
|
|
13
|
+
isOhMyZsh?: boolean;
|
|
14
|
+
zshrcConfigured?: boolean;
|
|
15
|
+
bashrcConfigured?: boolean;
|
|
16
|
+
profileConfigured?: boolean;
|
|
17
|
+
}
|
|
4
18
|
/**
|
|
5
19
|
* Interface for completion installers
|
|
6
20
|
*/
|
|
@@ -11,7 +25,6 @@ export interface CompletionInstaller {
|
|
|
11
25
|
message: string;
|
|
12
26
|
}>;
|
|
13
27
|
}
|
|
14
|
-
export type { InstallationResult };
|
|
15
28
|
/**
|
|
16
29
|
* Factory for creating completion generators and installers
|
|
17
30
|
* This design makes it easy to add support for additional shells
|
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import { ZshGenerator } from './generators/zsh-generator.js';
|
|
2
|
+
import { BashGenerator } from './generators/bash-generator.js';
|
|
3
|
+
import { FishGenerator } from './generators/fish-generator.js';
|
|
4
|
+
import { PowerShellGenerator } from './generators/powershell-generator.js';
|
|
2
5
|
import { ZshInstaller } from './installers/zsh-installer.js';
|
|
6
|
+
import { BashInstaller } from './installers/bash-installer.js';
|
|
7
|
+
import { FishInstaller } from './installers/fish-installer.js';
|
|
8
|
+
import { PowerShellInstaller } from './installers/powershell-installer.js';
|
|
3
9
|
/**
|
|
4
10
|
* Factory for creating completion generators and installers
|
|
5
11
|
* This design makes it easy to add support for additional shells
|
|
6
12
|
*/
|
|
7
13
|
export class CompletionFactory {
|
|
8
|
-
static SUPPORTED_SHELLS = ['zsh'];
|
|
14
|
+
static SUPPORTED_SHELLS = ['zsh', 'bash', 'fish', 'powershell'];
|
|
9
15
|
/**
|
|
10
16
|
* Create a completion generator for the specified shell
|
|
11
17
|
*
|
|
@@ -17,6 +23,12 @@ export class CompletionFactory {
|
|
|
17
23
|
switch (shell) {
|
|
18
24
|
case 'zsh':
|
|
19
25
|
return new ZshGenerator();
|
|
26
|
+
case 'bash':
|
|
27
|
+
return new BashGenerator();
|
|
28
|
+
case 'fish':
|
|
29
|
+
return new FishGenerator();
|
|
30
|
+
case 'powershell':
|
|
31
|
+
return new PowerShellGenerator();
|
|
20
32
|
default:
|
|
21
33
|
throw new Error(`Unsupported shell: ${shell}`);
|
|
22
34
|
}
|
|
@@ -32,6 +44,12 @@ export class CompletionFactory {
|
|
|
32
44
|
switch (shell) {
|
|
33
45
|
case 'zsh':
|
|
34
46
|
return new ZshInstaller();
|
|
47
|
+
case 'bash':
|
|
48
|
+
return new BashInstaller();
|
|
49
|
+
case 'fish':
|
|
50
|
+
return new FishInstaller();
|
|
51
|
+
case 'powershell':
|
|
52
|
+
return new PowerShellInstaller();
|
|
35
53
|
default:
|
|
36
54
|
throw new Error(`Unsupported shell: ${shell}`);
|
|
37
55
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { CompletionGenerator, CommandDefinition } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Generates Bash completion scripts for the OpenSpec CLI.
|
|
4
|
+
* Follows Bash completion conventions using complete builtin and COMPREPLY array.
|
|
5
|
+
*/
|
|
6
|
+
export declare class BashGenerator implements CompletionGenerator {
|
|
7
|
+
readonly shell: "bash";
|
|
8
|
+
/**
|
|
9
|
+
* Generate a Bash completion script
|
|
10
|
+
*
|
|
11
|
+
* @param commands - Command definitions to generate completions for
|
|
12
|
+
* @returns Bash completion script as a string
|
|
13
|
+
*/
|
|
14
|
+
generate(commands: CommandDefinition[]): string;
|
|
15
|
+
/**
|
|
16
|
+
* Generate completion case logic for a command
|
|
17
|
+
*/
|
|
18
|
+
private generateCommandCase;
|
|
19
|
+
/**
|
|
20
|
+
* Generate argument completion (flags and positional arguments)
|
|
21
|
+
*/
|
|
22
|
+
private generateArgumentCompletion;
|
|
23
|
+
/**
|
|
24
|
+
* Generate positional argument completion based on type
|
|
25
|
+
*/
|
|
26
|
+
private generatePositionalCompletion;
|
|
27
|
+
/**
|
|
28
|
+
* Escape command/subcommand names for safe use in Bash scripts
|
|
29
|
+
*/
|
|
30
|
+
private escapeCommandName;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=bash-generator.d.ts.map
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { BASH_DYNAMIC_HELPERS } from '../templates/bash-templates.js';
|
|
2
|
+
/**
|
|
3
|
+
* Generates Bash completion scripts for the OpenSpec CLI.
|
|
4
|
+
* Follows Bash completion conventions using complete builtin and COMPREPLY array.
|
|
5
|
+
*/
|
|
6
|
+
export class BashGenerator {
|
|
7
|
+
shell = 'bash';
|
|
8
|
+
/**
|
|
9
|
+
* Generate a Bash completion script
|
|
10
|
+
*
|
|
11
|
+
* @param commands - Command definitions to generate completions for
|
|
12
|
+
* @returns Bash completion script as a string
|
|
13
|
+
*/
|
|
14
|
+
generate(commands) {
|
|
15
|
+
// Build command list for top-level completions
|
|
16
|
+
const commandList = commands.map(c => this.escapeCommandName(c.name)).join(' ');
|
|
17
|
+
// Build command cases using push() for loop clarity
|
|
18
|
+
const caseLines = [];
|
|
19
|
+
for (const cmd of commands) {
|
|
20
|
+
caseLines.push(` ${cmd.name})`);
|
|
21
|
+
caseLines.push(...this.generateCommandCase(cmd, ' '));
|
|
22
|
+
caseLines.push(' ;;');
|
|
23
|
+
}
|
|
24
|
+
const commandCases = caseLines.join('\n');
|
|
25
|
+
// Dynamic completion helpers from template
|
|
26
|
+
const helpers = BASH_DYNAMIC_HELPERS;
|
|
27
|
+
// Assemble final script with template literal
|
|
28
|
+
return `# Bash completion script for OpenSpec CLI
|
|
29
|
+
# Auto-generated - do not edit manually
|
|
30
|
+
|
|
31
|
+
_openspec_completion() {
|
|
32
|
+
local cur prev words cword
|
|
33
|
+
|
|
34
|
+
# Use _init_completion if available (from bash-completion package)
|
|
35
|
+
# The -n : option prevents colons from being treated as word separators
|
|
36
|
+
# (important for spec/change IDs that may contain colons)
|
|
37
|
+
# Otherwise, fall back to manual initialization
|
|
38
|
+
if declare -F _init_completion >/dev/null 2>&1; then
|
|
39
|
+
_init_completion -n : || return
|
|
40
|
+
else
|
|
41
|
+
# Manual fallback when bash-completion is not installed
|
|
42
|
+
COMPREPLY=()
|
|
43
|
+
cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
44
|
+
prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
45
|
+
words=("\${COMP_WORDS[@]}")
|
|
46
|
+
cword=$COMP_CWORD
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
local cmd="\${words[1]}"
|
|
50
|
+
local subcmd="\${words[2]}"
|
|
51
|
+
|
|
52
|
+
# Top-level commands
|
|
53
|
+
if [[ $cword -eq 1 ]]; then
|
|
54
|
+
local commands="${commandList}"
|
|
55
|
+
COMPREPLY=($(compgen -W "$commands" -- "$cur"))
|
|
56
|
+
return 0
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
# Command-specific completion
|
|
60
|
+
case "$cmd" in
|
|
61
|
+
${commandCases}
|
|
62
|
+
esac
|
|
63
|
+
|
|
64
|
+
return 0
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
${helpers}
|
|
68
|
+
complete -F _openspec_completion openspec
|
|
69
|
+
`;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Generate completion case logic for a command
|
|
73
|
+
*/
|
|
74
|
+
generateCommandCase(cmd, indent) {
|
|
75
|
+
const lines = [];
|
|
76
|
+
// Handle subcommands
|
|
77
|
+
if (cmd.subcommands && cmd.subcommands.length > 0) {
|
|
78
|
+
// First, check if user is typing a flag for the parent command
|
|
79
|
+
if (cmd.flags.length > 0) {
|
|
80
|
+
lines.push(`${indent}if [[ "$cur" == -* ]]; then`);
|
|
81
|
+
const flags = cmd.flags.map(f => {
|
|
82
|
+
const parts = [];
|
|
83
|
+
if (f.short)
|
|
84
|
+
parts.push(`-${f.short}`);
|
|
85
|
+
parts.push(`--${f.name}`);
|
|
86
|
+
return parts.join(' ');
|
|
87
|
+
}).join(' ');
|
|
88
|
+
lines.push(`${indent} local flags="${flags}"`);
|
|
89
|
+
lines.push(`${indent} COMPREPLY=($(compgen -W "$flags" -- "$cur"))`);
|
|
90
|
+
lines.push(`${indent} return 0`);
|
|
91
|
+
lines.push(`${indent}fi`);
|
|
92
|
+
lines.push('');
|
|
93
|
+
}
|
|
94
|
+
lines.push(`${indent}if [[ $cword -eq 2 ]]; then`);
|
|
95
|
+
lines.push(`${indent} local subcommands="` + cmd.subcommands.map(s => this.escapeCommandName(s.name)).join(' ') + '"');
|
|
96
|
+
lines.push(`${indent} COMPREPLY=($(compgen -W "$subcommands" -- "$cur"))`);
|
|
97
|
+
lines.push(`${indent} return 0`);
|
|
98
|
+
lines.push(`${indent}fi`);
|
|
99
|
+
lines.push('');
|
|
100
|
+
lines.push(`${indent}case "$subcmd" in`);
|
|
101
|
+
for (const subcmd of cmd.subcommands) {
|
|
102
|
+
lines.push(`${indent} ${subcmd.name})`);
|
|
103
|
+
lines.push(...this.generateArgumentCompletion(subcmd, indent + ' '));
|
|
104
|
+
lines.push(`${indent} ;;`);
|
|
105
|
+
}
|
|
106
|
+
lines.push(`${indent}esac`);
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
// No subcommands, just complete arguments
|
|
110
|
+
lines.push(...this.generateArgumentCompletion(cmd, indent));
|
|
111
|
+
}
|
|
112
|
+
return lines;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Generate argument completion (flags and positional arguments)
|
|
116
|
+
*/
|
|
117
|
+
generateArgumentCompletion(cmd, indent) {
|
|
118
|
+
const lines = [];
|
|
119
|
+
// Check for flag completion
|
|
120
|
+
if (cmd.flags.length > 0) {
|
|
121
|
+
lines.push(`${indent}if [[ "$cur" == -* ]]; then`);
|
|
122
|
+
const flags = cmd.flags.map(f => {
|
|
123
|
+
const parts = [];
|
|
124
|
+
if (f.short)
|
|
125
|
+
parts.push(`-${f.short}`);
|
|
126
|
+
parts.push(`--${f.name}`);
|
|
127
|
+
return parts.join(' ');
|
|
128
|
+
}).join(' ');
|
|
129
|
+
lines.push(`${indent} local flags="${flags}"`);
|
|
130
|
+
lines.push(`${indent} COMPREPLY=($(compgen -W "$flags" -- "$cur"))`);
|
|
131
|
+
lines.push(`${indent} return 0`);
|
|
132
|
+
lines.push(`${indent}fi`);
|
|
133
|
+
lines.push('');
|
|
134
|
+
}
|
|
135
|
+
// Handle positional completions
|
|
136
|
+
if (cmd.acceptsPositional) {
|
|
137
|
+
lines.push(...this.generatePositionalCompletion(cmd.positionalType, indent));
|
|
138
|
+
}
|
|
139
|
+
return lines;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Generate positional argument completion based on type
|
|
143
|
+
*/
|
|
144
|
+
generatePositionalCompletion(positionalType, indent) {
|
|
145
|
+
const lines = [];
|
|
146
|
+
switch (positionalType) {
|
|
147
|
+
case 'change-id':
|
|
148
|
+
lines.push(`${indent}_openspec_complete_changes`);
|
|
149
|
+
break;
|
|
150
|
+
case 'spec-id':
|
|
151
|
+
lines.push(`${indent}_openspec_complete_specs`);
|
|
152
|
+
break;
|
|
153
|
+
case 'change-or-spec-id':
|
|
154
|
+
lines.push(`${indent}_openspec_complete_items`);
|
|
155
|
+
break;
|
|
156
|
+
case 'shell':
|
|
157
|
+
lines.push(`${indent}local shells="zsh bash fish powershell"`);
|
|
158
|
+
lines.push(`${indent}COMPREPLY=($(compgen -W "$shells" -- "$cur"))`);
|
|
159
|
+
break;
|
|
160
|
+
case 'path':
|
|
161
|
+
lines.push(`${indent}COMPREPLY=($(compgen -f -- "$cur"))`);
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
return lines;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Escape command/subcommand names for safe use in Bash scripts
|
|
168
|
+
*/
|
|
169
|
+
escapeCommandName(name) {
|
|
170
|
+
// Escape shell metacharacters to prevent command injection
|
|
171
|
+
return name.replace(/["\$`\\]/g, '\\$&');
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=bash-generator.js.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { CompletionGenerator, CommandDefinition } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Generates Fish completion scripts for the OpenSpec CLI.
|
|
4
|
+
* Follows Fish completion conventions using the complete command.
|
|
5
|
+
*/
|
|
6
|
+
export declare class FishGenerator implements CompletionGenerator {
|
|
7
|
+
readonly shell: "fish";
|
|
8
|
+
/**
|
|
9
|
+
* Generate a Fish completion script
|
|
10
|
+
*
|
|
11
|
+
* @param commands - Command definitions to generate completions for
|
|
12
|
+
* @returns Fish completion script as a string
|
|
13
|
+
*/
|
|
14
|
+
generate(commands: CommandDefinition[]): string;
|
|
15
|
+
/**
|
|
16
|
+
* Generate completions for a specific command
|
|
17
|
+
*/
|
|
18
|
+
private generateCommandCompletions;
|
|
19
|
+
/**
|
|
20
|
+
* Generate flag completion
|
|
21
|
+
*/
|
|
22
|
+
private generateFlagCompletion;
|
|
23
|
+
/**
|
|
24
|
+
* Generate positional argument completion
|
|
25
|
+
*/
|
|
26
|
+
private generatePositionalCompletion;
|
|
27
|
+
/**
|
|
28
|
+
* Escape description text for Fish
|
|
29
|
+
*/
|
|
30
|
+
private escapeDescription;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=fish-generator.d.ts.map
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { FISH_STATIC_HELPERS, FISH_DYNAMIC_HELPERS } from '../templates/fish-templates.js';
|
|
2
|
+
/**
|
|
3
|
+
* Generates Fish completion scripts for the OpenSpec CLI.
|
|
4
|
+
* Follows Fish completion conventions using the complete command.
|
|
5
|
+
*/
|
|
6
|
+
export class FishGenerator {
|
|
7
|
+
shell = 'fish';
|
|
8
|
+
/**
|
|
9
|
+
* Generate a Fish completion script
|
|
10
|
+
*
|
|
11
|
+
* @param commands - Command definitions to generate completions for
|
|
12
|
+
* @returns Fish completion script as a string
|
|
13
|
+
*/
|
|
14
|
+
generate(commands) {
|
|
15
|
+
// Build top-level commands using push() for loop clarity
|
|
16
|
+
const topLevelLines = [];
|
|
17
|
+
for (const cmd of commands) {
|
|
18
|
+
topLevelLines.push(`# ${cmd.name} command`);
|
|
19
|
+
topLevelLines.push(`complete -c openspec -n '__fish_openspec_no_subcommand' -a '${cmd.name}' -d '${this.escapeDescription(cmd.description)}'`);
|
|
20
|
+
}
|
|
21
|
+
const topLevelCommands = topLevelLines.join('\n');
|
|
22
|
+
// Build command-specific completions using push() for loop clarity
|
|
23
|
+
const commandCompletionLines = [];
|
|
24
|
+
for (const cmd of commands) {
|
|
25
|
+
commandCompletionLines.push(...this.generateCommandCompletions(cmd));
|
|
26
|
+
commandCompletionLines.push('');
|
|
27
|
+
}
|
|
28
|
+
const commandCompletions = commandCompletionLines.join('\n');
|
|
29
|
+
// Static helper functions from template
|
|
30
|
+
const helperFunctions = FISH_STATIC_HELPERS;
|
|
31
|
+
// Dynamic completion helpers from template
|
|
32
|
+
const dynamicHelpers = FISH_DYNAMIC_HELPERS;
|
|
33
|
+
// Assemble final script with template literal
|
|
34
|
+
return `# Fish completion script for OpenSpec CLI
|
|
35
|
+
# Auto-generated - do not edit manually
|
|
36
|
+
|
|
37
|
+
${helperFunctions}
|
|
38
|
+
${dynamicHelpers}
|
|
39
|
+
${topLevelCommands}
|
|
40
|
+
|
|
41
|
+
${commandCompletions}`;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Generate completions for a specific command
|
|
45
|
+
*/
|
|
46
|
+
generateCommandCompletions(cmd) {
|
|
47
|
+
const lines = [];
|
|
48
|
+
// If command has subcommands
|
|
49
|
+
if (cmd.subcommands && cmd.subcommands.length > 0) {
|
|
50
|
+
// Add subcommand completions
|
|
51
|
+
for (const subcmd of cmd.subcommands) {
|
|
52
|
+
lines.push(`complete -c openspec -n '__fish_openspec_using_subcommand ${cmd.name}; and not __fish_openspec_using_subcommand ${subcmd.name}' -a '${subcmd.name}' -d '${this.escapeDescription(subcmd.description)}'`);
|
|
53
|
+
}
|
|
54
|
+
lines.push('');
|
|
55
|
+
// Add flags for parent command
|
|
56
|
+
for (const flag of cmd.flags) {
|
|
57
|
+
lines.push(...this.generateFlagCompletion(flag, `__fish_openspec_using_subcommand ${cmd.name}`));
|
|
58
|
+
}
|
|
59
|
+
// Add completions for each subcommand
|
|
60
|
+
for (const subcmd of cmd.subcommands) {
|
|
61
|
+
lines.push(`# ${cmd.name} ${subcmd.name} flags`);
|
|
62
|
+
for (const flag of subcmd.flags) {
|
|
63
|
+
lines.push(...this.generateFlagCompletion(flag, `__fish_openspec_using_subcommand ${cmd.name}; and __fish_openspec_using_subcommand ${subcmd.name}`));
|
|
64
|
+
}
|
|
65
|
+
// Add positional completions for subcommand
|
|
66
|
+
if (subcmd.acceptsPositional) {
|
|
67
|
+
lines.push(...this.generatePositionalCompletion(subcmd.positionalType, `__fish_openspec_using_subcommand ${cmd.name}; and __fish_openspec_using_subcommand ${subcmd.name}`));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
// Command without subcommands
|
|
73
|
+
lines.push(`# ${cmd.name} flags`);
|
|
74
|
+
for (const flag of cmd.flags) {
|
|
75
|
+
lines.push(...this.generateFlagCompletion(flag, `__fish_openspec_using_subcommand ${cmd.name}`));
|
|
76
|
+
}
|
|
77
|
+
// Add positional completions
|
|
78
|
+
if (cmd.acceptsPositional) {
|
|
79
|
+
lines.push(...this.generatePositionalCompletion(cmd.positionalType, `__fish_openspec_using_subcommand ${cmd.name}`));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return lines;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Generate flag completion
|
|
86
|
+
*/
|
|
87
|
+
generateFlagCompletion(flag, condition) {
|
|
88
|
+
const lines = [];
|
|
89
|
+
const longFlag = `--${flag.name}`;
|
|
90
|
+
const shortFlag = flag.short ? `-${flag.short}` : undefined;
|
|
91
|
+
if (flag.takesValue && flag.values) {
|
|
92
|
+
// Flag with enum values
|
|
93
|
+
for (const value of flag.values) {
|
|
94
|
+
if (shortFlag) {
|
|
95
|
+
lines.push(`complete -c openspec -n '${condition}' -s ${flag.short} -l ${flag.name} -a '${value}' -d '${this.escapeDescription(flag.description)}'`);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
lines.push(`complete -c openspec -n '${condition}' -l ${flag.name} -a '${value}' -d '${this.escapeDescription(flag.description)}'`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
else if (flag.takesValue) {
|
|
103
|
+
// Flag that takes a value but no specific values defined
|
|
104
|
+
if (shortFlag) {
|
|
105
|
+
lines.push(`complete -c openspec -n '${condition}' -s ${flag.short} -l ${flag.name} -r -d '${this.escapeDescription(flag.description)}'`);
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
lines.push(`complete -c openspec -n '${condition}' -l ${flag.name} -r -d '${this.escapeDescription(flag.description)}'`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
// Boolean flag
|
|
113
|
+
if (shortFlag) {
|
|
114
|
+
lines.push(`complete -c openspec -n '${condition}' -s ${flag.short} -l ${flag.name} -d '${this.escapeDescription(flag.description)}'`);
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
lines.push(`complete -c openspec -n '${condition}' -l ${flag.name} -d '${this.escapeDescription(flag.description)}'`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return lines;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Generate positional argument completion
|
|
124
|
+
*/
|
|
125
|
+
generatePositionalCompletion(positionalType, condition) {
|
|
126
|
+
const lines = [];
|
|
127
|
+
switch (positionalType) {
|
|
128
|
+
case 'change-id':
|
|
129
|
+
lines.push(`complete -c openspec -n '${condition}' -a '(__fish_openspec_changes)' -f`);
|
|
130
|
+
break;
|
|
131
|
+
case 'spec-id':
|
|
132
|
+
lines.push(`complete -c openspec -n '${condition}' -a '(__fish_openspec_specs)' -f`);
|
|
133
|
+
break;
|
|
134
|
+
case 'change-or-spec-id':
|
|
135
|
+
lines.push(`complete -c openspec -n '${condition}' -a '(__fish_openspec_items)' -f`);
|
|
136
|
+
break;
|
|
137
|
+
case 'shell':
|
|
138
|
+
lines.push(`complete -c openspec -n '${condition}' -a 'zsh bash fish powershell' -f`);
|
|
139
|
+
break;
|
|
140
|
+
case 'path':
|
|
141
|
+
// Fish automatically completes files, no need to specify
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
return lines;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Escape description text for Fish
|
|
148
|
+
*/
|
|
149
|
+
escapeDescription(description) {
|
|
150
|
+
return description
|
|
151
|
+
.replace(/\\/g, '\\\\') // Backslashes first
|
|
152
|
+
.replace(/'/g, "\\'") // Single quotes
|
|
153
|
+
.replace(/\$/g, '\\$') // Dollar signs (prevents $())
|
|
154
|
+
.replace(/`/g, '\\`'); // Backticks
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=fish-generator.js.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { CompletionGenerator, CommandDefinition } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Generates PowerShell completion scripts for the OpenSpec CLI.
|
|
4
|
+
* Uses Register-ArgumentCompleter for command completion.
|
|
5
|
+
*/
|
|
6
|
+
export declare class PowerShellGenerator implements CompletionGenerator {
|
|
7
|
+
readonly shell: "powershell";
|
|
8
|
+
/**
|
|
9
|
+
* Generate a PowerShell completion script
|
|
10
|
+
*
|
|
11
|
+
* @param commands - Command definitions to generate completions for
|
|
12
|
+
* @returns PowerShell completion script as a string
|
|
13
|
+
*/
|
|
14
|
+
generate(commands: CommandDefinition[]): string;
|
|
15
|
+
/**
|
|
16
|
+
* Generate completion case for a command
|
|
17
|
+
*/
|
|
18
|
+
private generateCommandCase;
|
|
19
|
+
/**
|
|
20
|
+
* Generate argument completion (flags and positional)
|
|
21
|
+
*/
|
|
22
|
+
private generateArgumentCompletion;
|
|
23
|
+
/**
|
|
24
|
+
* Generate positional argument completion
|
|
25
|
+
*/
|
|
26
|
+
private generatePositionalCompletion;
|
|
27
|
+
/**
|
|
28
|
+
* Escape description text for PowerShell
|
|
29
|
+
*/
|
|
30
|
+
private escapeDescription;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=powershell-generator.d.ts.map
|