@fission-ai/openspec 1.6.0-beta.1 → 1.7.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 +28 -1
- package/dist/cli/index.js +73 -7
- package/dist/commands/change.d.ts +0 -1
- package/dist/commands/change.js +90 -50
- 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 +21 -11
- package/dist/commands/show.js +5 -2
- package/dist/commands/spec.js +20 -24
- package/dist/commands/validate.js +18 -3
- 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 +89 -6
- package/dist/commands/workflow/shared.d.ts +13 -2
- package/dist/commands/workflow/shared.js +34 -4
- package/dist/commands/workflow/status.js +7 -2
- package/dist/core/archive.d.ts +0 -1
- package/dist/core/archive.js +96 -33
- package/dist/core/artifact-graph/graph.d.ts +16 -0
- package/dist/core/artifact-graph/graph.js +30 -6
- package/dist/core/artifact-graph/instruction-loader.d.ts +27 -2
- package/dist/core/artifact-graph/instruction-loader.js +55 -7
- package/dist/core/artifact-graph/resolver.d.ts +14 -0
- package/dist/core/artifact-graph/resolver.js +34 -6
- package/dist/core/change-metadata/schema.d.ts +1 -0
- package/dist/core/change-metadata/schema.js +6 -0
- package/dist/core/change-status-policy.d.ts +1 -1
- 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 +8 -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 +7 -1
- package/dist/core/config-schema.js +40 -1
- package/dist/core/config.d.ts +12 -0
- package/dist/core/config.js +21 -3
- package/dist/core/global-config.d.ts +5 -0
- package/dist/core/init.d.ts +35 -0
- package/dist/core/init.js +225 -39
- package/dist/core/legacy-cleanup.d.ts +59 -0
- package/dist/core/legacy-cleanup.js +249 -22
- package/dist/core/list.js +5 -7
- package/dist/core/migration.d.ts +71 -1
- package/dist/core/migration.js +294 -1
- 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 +8 -0
- package/dist/core/parsers/requirement-blocks.js +60 -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.js +3 -29
- package/dist/core/profile-sync-drift.d.ts +0 -8
- package/dist/core/profile-sync-drift.js +9 -45
- package/dist/core/project-config.d.ts +25 -6
- package/dist/core/project-config.js +77 -7
- 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/tool-detection.d.ts +25 -5
- package/dist/core/shared/tool-detection.js +105 -6
- package/dist/core/specs-apply.d.ts +8 -33
- package/dist/core/specs-apply.js +252 -157
- package/dist/core/store/git.d.ts +13 -0
- package/dist/core/store/git.js +24 -0
- package/dist/core/templates/workflows/apply-change.js +54 -4
- package/dist/core/templates/workflows/archive-change.js +166 -42
- package/dist/core/templates/workflows/bulk-archive-change.js +240 -63
- package/dist/core/templates/workflows/continue-change.js +32 -40
- package/dist/core/templates/workflows/explore.js +12 -0
- 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 +4 -4
- package/dist/core/templates/workflows/propose.js +50 -32
- 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 +172 -22
- package/dist/core/templates/workflows/update-change.js +20 -12
- package/dist/core/templates/workflows/verify-change.js +16 -10
- package/dist/core/update.d.ts +39 -5
- package/dist/core/update.js +288 -71
- package/dist/core/validation/constants.d.ts +4 -1
- package/dist/core/validation/constants.js +4 -1
- package/dist/core/validation/validator.d.ts +1 -7
- package/dist/core/validation/validator.js +92 -36
- package/dist/core/version-check.d.ts +152 -0
- package/dist/core/version-check.js +736 -0
- package/dist/core/view.js +12 -17
- package/dist/prompts/searchable-multi-select.js +1 -1
- package/dist/telemetry/index.d.ts +1 -1
- package/dist/telemetry/index.js +55 -33
- package/dist/ui/welcome-screen.d.ts +9 -1
- package/dist/ui/welcome-screen.js +91 -38
- package/dist/utils/change-metadata.d.ts +30 -0
- package/dist/utils/change-metadata.js +101 -7
- package/dist/utils/change-utils.d.ts +10 -4
- package/dist/utils/change-utils.js +20 -13
- package/dist/utils/command-references.d.ts +80 -6
- package/dist/utils/command-references.js +138 -7
- package/dist/utils/date.d.ts +8 -0
- package/dist/utils/date.js +13 -0
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.js +1 -1
- 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 +102 -0
- package/package.json +29 -30
- package/schemas/spec-driven/schema.yaml +60 -5
- package/schemas/spec-driven/templates/design.md +2 -2
- package/schemas/spec-driven/templates/proposal.md +5 -1
- 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,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,10 @@ 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
|
+
},
|
|
25
29
|
],
|
|
26
30
|
},
|
|
27
31
|
{
|
|
@@ -62,7 +66,9 @@ export const COMMAND_REGISTRY = [
|
|
|
62
66
|
{
|
|
63
67
|
name: 'view',
|
|
64
68
|
description: 'Display an interactive dashboard of specs and changes',
|
|
65
|
-
flags: [
|
|
69
|
+
flags: [
|
|
70
|
+
COMMON_FLAGS.store,
|
|
71
|
+
],
|
|
66
72
|
},
|
|
67
73
|
{
|
|
68
74
|
name: 'validate',
|
|
@@ -177,7 +183,7 @@ export const COMMAND_REGISTRY = [
|
|
|
177
183
|
},
|
|
178
184
|
{
|
|
179
185
|
name: 'instructions',
|
|
180
|
-
description: 'Output enriched instructions for
|
|
186
|
+
description: 'Output enriched instructions for artifacts, apply, or archive',
|
|
181
187
|
acceptsPositional: true,
|
|
182
188
|
positionals: [{ name: 'artifact', optional: true }],
|
|
183
189
|
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,22 @@ 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>;
|
|
18
19
|
}, z.core.$loose>;
|
|
19
20
|
export type GlobalConfigType = z.infer<typeof GlobalConfigSchema>;
|
|
20
21
|
/**
|
|
21
22
|
* Default configuration values.
|
|
22
23
|
*/
|
|
23
24
|
export declare const DEFAULT_CONFIG: GlobalConfigType;
|
|
25
|
+
/**
|
|
26
|
+
* True when a dot-notation key path contains a prototype-reaching segment.
|
|
27
|
+
* Callers that bypass key validation (e.g. --allow-unknown) still must not bypass this.
|
|
28
|
+
*/
|
|
29
|
+
export declare function hasUnsafeKeySegment(path: string): boolean;
|
|
24
30
|
/**
|
|
25
31
|
* Validate a config key path for CLI set operations.
|
|
26
32
|
* Unknown top-level keys are rejected unless explicitly allowed by the caller.
|
|
@@ -20,6 +20,10 @@ export const GlobalConfigSchema = z
|
|
|
20
20
|
workflows: z
|
|
21
21
|
.array(z.string())
|
|
22
22
|
.optional(),
|
|
23
|
+
defaultStore: z
|
|
24
|
+
.string()
|
|
25
|
+
.optional()
|
|
26
|
+
.describe('Store id used as fallback root when no explicit --store, local root, or project-level store: pointer resolves'),
|
|
23
27
|
})
|
|
24
28
|
.passthrough();
|
|
25
29
|
/**
|
|
@@ -30,7 +34,22 @@ export const DEFAULT_CONFIG = {
|
|
|
30
34
|
profile: 'core',
|
|
31
35
|
delivery: 'both',
|
|
32
36
|
};
|
|
33
|
-
const KNOWN_TOP_LEVEL_KEYS = new Set([...Object.keys(DEFAULT_CONFIG), 'workflows']);
|
|
37
|
+
const KNOWN_TOP_LEVEL_KEYS = new Set([...Object.keys(DEFAULT_CONFIG), 'workflows', 'defaultStore']);
|
|
38
|
+
/**
|
|
39
|
+
* Key segments that would reach the prototype chain instead of the config object.
|
|
40
|
+
* Never valid as configuration keys, so rejecting them costs nothing.
|
|
41
|
+
*/
|
|
42
|
+
const UNSAFE_KEY_SEGMENTS = new Set(['__proto__', 'constructor', 'prototype']);
|
|
43
|
+
function hasUnsafeSegment(keys) {
|
|
44
|
+
return keys.some((key) => UNSAFE_KEY_SEGMENTS.has(key));
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* True when a dot-notation key path contains a prototype-reaching segment.
|
|
48
|
+
* Callers that bypass key validation (e.g. --allow-unknown) still must not bypass this.
|
|
49
|
+
*/
|
|
50
|
+
export function hasUnsafeKeySegment(path) {
|
|
51
|
+
return hasUnsafeSegment(path.split('.'));
|
|
52
|
+
}
|
|
34
53
|
/**
|
|
35
54
|
* Validate a config key path for CLI set operations.
|
|
36
55
|
* Unknown top-level keys are rejected unless explicitly allowed by the caller.
|
|
@@ -40,6 +59,10 @@ export function validateConfigKeyPath(path) {
|
|
|
40
59
|
if (rawKeys.length === 0 || rawKeys.some((key) => key.trim() === '')) {
|
|
41
60
|
return { valid: false, reason: 'Key path must not be empty' };
|
|
42
61
|
}
|
|
62
|
+
const unsafeKey = rawKeys.find((key) => UNSAFE_KEY_SEGMENTS.has(key));
|
|
63
|
+
if (unsafeKey) {
|
|
64
|
+
return { valid: false, reason: `Key segment "${unsafeKey}" is not allowed` };
|
|
65
|
+
}
|
|
43
66
|
const rootKey = rawKeys[0];
|
|
44
67
|
if (!KNOWN_TOP_LEVEL_KEYS.has(rootKey)) {
|
|
45
68
|
return { valid: false, reason: `Unknown top-level key "${rootKey}"` };
|
|
@@ -64,6 +87,9 @@ export function validateConfigKeyPath(path) {
|
|
|
64
87
|
*/
|
|
65
88
|
export function getNestedValue(obj, path) {
|
|
66
89
|
const keys = path.split('.');
|
|
90
|
+
if (hasUnsafeSegment(keys)) {
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
67
93
|
let current = obj;
|
|
68
94
|
for (const key of keys) {
|
|
69
95
|
if (current === null || current === undefined) {
|
|
@@ -86,6 +112,14 @@ export function getNestedValue(obj, path) {
|
|
|
86
112
|
*/
|
|
87
113
|
export function setNestedValue(obj, path, value) {
|
|
88
114
|
const keys = path.split('.');
|
|
115
|
+
// Compared literally rather than through a helper, so the guard is plain to a
|
|
116
|
+
// reader and to static analysis. Checked for the whole path before anything is
|
|
117
|
+
// written, so a rejected key never leaves half-created objects behind.
|
|
118
|
+
for (const key of keys) {
|
|
119
|
+
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
89
123
|
let current = obj;
|
|
90
124
|
for (let i = 0; i < keys.length - 1; i++) {
|
|
91
125
|
const key = keys[i];
|
|
@@ -106,6 +140,11 @@ export function setNestedValue(obj, path, value) {
|
|
|
106
140
|
*/
|
|
107
141
|
export function deleteNestedValue(obj, path) {
|
|
108
142
|
const keys = path.split('.');
|
|
143
|
+
for (const key of keys) {
|
|
144
|
+
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
109
148
|
let current = obj;
|
|
110
149
|
for (let i = 0; i < keys.length - 1; i++) {
|
|
111
150
|
const key = keys[i];
|
package/dist/core/config.d.ts
CHANGED
|
@@ -13,6 +13,18 @@ export interface AIToolOption {
|
|
|
13
13
|
successLabel?: string;
|
|
14
14
|
skillsDir?: string;
|
|
15
15
|
detectionPaths?: string[];
|
|
16
|
+
setupNote?: string;
|
|
16
17
|
}
|
|
17
18
|
export declare const AI_TOOLS: AIToolOption[];
|
|
19
|
+
/**
|
|
20
|
+
* Retired tool ids that still resolve, so a rebrand does not break scripted
|
|
21
|
+
* `--tools` invocations. Windsurf was rebranded to Devin Desktop on
|
|
22
|
+
* 2026-06-02 and its config directory moved from `.windsurf/` to `.devin/`;
|
|
23
|
+
* `--tools windsurf` therefore configures `devin`.
|
|
24
|
+
*/
|
|
25
|
+
export declare const TOOL_ID_ALIASES: Record<string, string>;
|
|
26
|
+
/**
|
|
27
|
+
* Resolves a tool id through TOOL_ID_ALIASES, leaving current ids untouched.
|
|
28
|
+
*/
|
|
29
|
+
export declare function resolveToolIdAlias(toolId: string): string;
|
|
18
30
|
//# sourceMappingURL=config.d.ts.map
|
package/dist/core/config.js
CHANGED
|
@@ -10,7 +10,9 @@ export const AI_TOOLS = [
|
|
|
10
10
|
{ name: 'Bob Shell', value: 'bob', available: true, successLabel: 'Bob Shell', skillsDir: '.bob' },
|
|
11
11
|
{ name: 'Claude Code', value: 'claude', available: true, successLabel: 'Claude Code', skillsDir: '.claude' },
|
|
12
12
|
{ name: 'Cline', value: 'cline', available: true, successLabel: 'Cline', skillsDir: '.cline' },
|
|
13
|
+
{ name: 'CodeArts', value: 'codeartsagent', available: true, successLabel: 'CodeArts', skillsDir: '.codeartsdoer' },
|
|
13
14
|
{ name: 'Codex', value: 'codex', available: true, successLabel: 'Codex', skillsDir: '.codex' },
|
|
15
|
+
{ name: 'Devin Desktop (formerly Windsurf)', value: 'devin', available: true, successLabel: 'Devin Desktop', skillsDir: '.devin', detectionPaths: ['.devin', '.windsurf'] },
|
|
14
16
|
{ name: 'ForgeCode', value: 'forgecode', available: true, successLabel: 'ForgeCode', skillsDir: '.forge' },
|
|
15
17
|
{ name: 'CodeBuddy Code (CLI)', value: 'codebuddy', available: true, successLabel: 'CodeBuddy Code', skillsDir: '.codebuddy' },
|
|
16
18
|
{ name: 'Continue', value: 'continue', available: true, successLabel: 'Continue (VS Code / JetBrains / Cli)', skillsDir: '.continue' },
|
|
@@ -20,10 +22,11 @@ export const AI_TOOLS = [
|
|
|
20
22
|
{ name: 'Factory Droid', value: 'factory', available: true, successLabel: 'Factory Droid', skillsDir: '.factory' },
|
|
21
23
|
{ name: 'Gemini CLI', value: 'gemini', available: true, successLabel: 'Gemini CLI', skillsDir: '.gemini' },
|
|
22
24
|
{ name: 'GitHub Copilot', value: 'github-copilot', available: true, successLabel: 'GitHub Copilot', skillsDir: '.github', detectionPaths: ['.github/copilot-instructions.md', '.github/instructions', '.github/workflows/copilot-setup-steps.yml', '.github/prompts', '.github/agents', '.github/skills', '.github/.mcp.json'] },
|
|
25
|
+
{ name: 'Hermes Agent', value: 'hermes', available: true, successLabel: 'Hermes Agent', skillsDir: '.hermes', detectionPaths: ['.hermes', 'HERMES.md', '.hermes.md'], setupNote: "Hermes only loads skills from ~/.hermes/skills by default. Add this project's .hermes/skills directory to skills.external_dirs in ~/.hermes/config.yaml so Hermes picks up the generated OpenSpec skills." },
|
|
23
26
|
{ name: 'iFlow', value: 'iflow', available: true, successLabel: 'iFlow', skillsDir: '.iflow' },
|
|
24
27
|
{ name: 'Junie', value: 'junie', available: true, successLabel: 'Junie', skillsDir: '.junie' },
|
|
25
28
|
{ name: 'Kilo Code', value: 'kilocode', available: true, successLabel: 'Kilo Code', skillsDir: '.kilocode' },
|
|
26
|
-
{ name: 'Kimi
|
|
29
|
+
{ name: 'Kimi Code', value: 'kimi', available: true, successLabel: 'Kimi Code', skillsDir: '.kimi-code', detectionPaths: ['.kimi-code', '.kimi'] },
|
|
27
30
|
{ name: 'Kiro', value: 'kiro', available: true, successLabel: 'Kiro', skillsDir: '.kiro' },
|
|
28
31
|
{ name: 'Lingma', value: 'lingma', available: true, successLabel: 'Lingma', skillsDir: '.lingma' },
|
|
29
32
|
{ name: 'Mistral Vibe', value: 'vibe', available: true, successLabel: 'Mistral Vibe', skillsDir: '.vibe' },
|
|
@@ -32,9 +35,24 @@ export const AI_TOOLS = [
|
|
|
32
35
|
{ name: 'Pi', value: 'pi', available: true, successLabel: 'Pi', skillsDir: '.pi' },
|
|
33
36
|
{ name: 'Qoder', value: 'qoder', available: true, successLabel: 'Qoder', skillsDir: '.qoder' },
|
|
34
37
|
{ name: 'Qwen Code', value: 'qwen', available: true, successLabel: 'Qwen Code', skillsDir: '.qwen' },
|
|
35
|
-
{ name: '
|
|
38
|
+
{ name: 'Zoo Code', value: 'roocode', available: true, successLabel: 'Zoo Code', skillsDir: '.roo' },
|
|
36
39
|
{ name: 'Trae', value: 'trae', available: true, successLabel: 'Trae', skillsDir: '.trae' },
|
|
37
|
-
{ name: '
|
|
40
|
+
{ name: 'ZCode', value: 'zcode', available: true, successLabel: 'ZCode', skillsDir: '.zcode' },
|
|
38
41
|
{ name: 'AGENTS.md (works with Amp, VS Code, …)', value: 'agents', available: false, successLabel: 'your AGENTS.md-compatible assistant' }
|
|
39
42
|
];
|
|
43
|
+
/**
|
|
44
|
+
* Retired tool ids that still resolve, so a rebrand does not break scripted
|
|
45
|
+
* `--tools` invocations. Windsurf was rebranded to Devin Desktop on
|
|
46
|
+
* 2026-06-02 and its config directory moved from `.windsurf/` to `.devin/`;
|
|
47
|
+
* `--tools windsurf` therefore configures `devin`.
|
|
48
|
+
*/
|
|
49
|
+
export const TOOL_ID_ALIASES = {
|
|
50
|
+
windsurf: 'devin',
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Resolves a tool id through TOOL_ID_ALIASES, leaving current ids untouched.
|
|
54
|
+
*/
|
|
55
|
+
export function resolveToolIdAlias(toolId) {
|
|
56
|
+
return TOOL_ID_ALIASES[toolId] ?? toolId;
|
|
57
|
+
}
|
|
40
58
|
//# sourceMappingURL=config.js.map
|
|
@@ -8,6 +8,11 @@ export interface GlobalConfig {
|
|
|
8
8
|
profile?: Profile;
|
|
9
9
|
delivery?: Delivery;
|
|
10
10
|
workflows?: string[];
|
|
11
|
+
/**
|
|
12
|
+
* Machine-level fallback store id, consulted during root resolution only
|
|
13
|
+
* when no --store flag, local root, or project-level store: pointer resolves.
|
|
14
|
+
*/
|
|
15
|
+
defaultStore?: string;
|
|
11
16
|
/** Workset opener rows (slice 7.1); hand-edited, validated on use. */
|
|
12
17
|
openers?: unknown;
|
|
13
18
|
}
|
package/dist/core/init.d.ts
CHANGED
|
@@ -9,23 +9,58 @@ type InitCommandOptions = {
|
|
|
9
9
|
force?: boolean;
|
|
10
10
|
interactive?: boolean;
|
|
11
11
|
profile?: string;
|
|
12
|
+
/** Commander's --no-animation flag: false disables the welcome animation. */
|
|
13
|
+
animation?: boolean;
|
|
12
14
|
};
|
|
13
15
|
export declare class InitCommand {
|
|
14
16
|
private readonly toolsArg?;
|
|
15
17
|
private readonly force;
|
|
16
18
|
private readonly interactiveOption?;
|
|
17
19
|
private readonly profileOverride?;
|
|
20
|
+
private readonly animation;
|
|
18
21
|
constructor(options?: InitCommandOptions);
|
|
19
22
|
execute(targetPath: string): Promise<void>;
|
|
20
23
|
private validate;
|
|
21
24
|
private canPromptInteractively;
|
|
22
25
|
private resolveProfileOverride;
|
|
26
|
+
/**
|
|
27
|
+
* Resolves the workflows the effective profile installs, so onboarding output
|
|
28
|
+
* only mentions commands that will actually exist.
|
|
29
|
+
*/
|
|
30
|
+
private getActiveWorkflows;
|
|
31
|
+
/**
|
|
32
|
+
* Cleans repo-local legacy artifacts immediately and defers global Codex prompt
|
|
33
|
+
* cleanup until replacement skills have been installed.
|
|
34
|
+
*/
|
|
23
35
|
private handleLegacyCleanup;
|
|
36
|
+
/**
|
|
37
|
+
* Applies the safe subset of legacy cleanup that does not depend on newly
|
|
38
|
+
* generated Codex skills.
|
|
39
|
+
*/
|
|
40
|
+
private performImmediateLegacyCleanup;
|
|
41
|
+
/**
|
|
42
|
+
* Removes only the legacy global Codex prompts whose workflows now have
|
|
43
|
+
* replacement skills in the project.
|
|
44
|
+
*/
|
|
45
|
+
private finalizeDeferredLegacyCleanup;
|
|
46
|
+
/**
|
|
47
|
+
* Reads the currently installed workflow IDs for a single tool from the
|
|
48
|
+
* generated skill layout on disk.
|
|
49
|
+
*/
|
|
50
|
+
private getInstalledWorkflowsForTool;
|
|
24
51
|
private performLegacyCleanup;
|
|
25
52
|
private getSelectedTools;
|
|
26
53
|
private resolveToolsArg;
|
|
27
54
|
private validateTools;
|
|
28
55
|
private createDirectoryStructure;
|
|
56
|
+
/**
|
|
57
|
+
* Generates skill files and slash commands for each selected tool,
|
|
58
|
+
* honoring the configured delivery mode (skills, commands, or both).
|
|
59
|
+
*
|
|
60
|
+
* @param projectPath - Absolute path to the project root
|
|
61
|
+
* @param tools - Selected tools with their skill directory metadata
|
|
62
|
+
* @returns Created, refreshed, and failed tools plus removed artifact counts
|
|
63
|
+
*/
|
|
29
64
|
private generateSkillsAndCommands;
|
|
30
65
|
private createConfig;
|
|
31
66
|
private displaySuccessMessage;
|