@fission-ai/openspec 1.6.0 → 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.
Files changed (175) hide show
  1. package/README.md +28 -1
  2. package/dist/cli/index.js +73 -7
  3. package/dist/commands/change.d.ts +0 -1
  4. package/dist/commands/change.js +90 -50
  5. package/dist/commands/config.js +14 -8
  6. package/dist/commands/doctor.js +9 -4
  7. package/dist/commands/feedback.js +74 -23
  8. package/dist/commands/schema.js +21 -11
  9. package/dist/commands/show.js +5 -2
  10. package/dist/commands/spec.js +20 -24
  11. package/dist/commands/validate.js +18 -3
  12. package/dist/commands/workflow/index.d.ts +1 -1
  13. package/dist/commands/workflow/index.js +1 -1
  14. package/dist/commands/workflow/instructions.d.ts +7 -1
  15. package/dist/commands/workflow/instructions.js +89 -6
  16. package/dist/commands/workflow/shared.d.ts +13 -2
  17. package/dist/commands/workflow/shared.js +34 -4
  18. package/dist/commands/workflow/status.js +7 -2
  19. package/dist/core/archive.d.ts +0 -1
  20. package/dist/core/archive.js +96 -33
  21. package/dist/core/artifact-graph/graph.d.ts +16 -0
  22. package/dist/core/artifact-graph/graph.js +30 -6
  23. package/dist/core/artifact-graph/instruction-loader.d.ts +27 -2
  24. package/dist/core/artifact-graph/instruction-loader.js +55 -7
  25. package/dist/core/artifact-graph/resolver.d.ts +14 -0
  26. package/dist/core/artifact-graph/resolver.js +34 -6
  27. package/dist/core/change-metadata/schema.d.ts +1 -0
  28. package/dist/core/change-metadata/schema.js +6 -0
  29. package/dist/core/change-status-policy.d.ts +1 -1
  30. package/dist/core/command-generation/adapters/amazon-q.d.ts +4 -0
  31. package/dist/core/command-generation/adapters/amazon-q.js +7 -1
  32. package/dist/core/command-generation/adapters/antigravity.js +2 -1
  33. package/dist/core/command-generation/adapters/auggie.js +2 -1
  34. package/dist/core/command-generation/adapters/bob.d.ts +5 -1
  35. package/dist/core/command-generation/adapters/bob.js +6 -5
  36. package/dist/core/command-generation/adapters/claude.js +1 -8
  37. package/dist/core/command-generation/adapters/codebuddy.js +3 -2
  38. package/dist/core/command-generation/adapters/continue.js +3 -2
  39. package/dist/core/command-generation/adapters/costrict.js +2 -1
  40. package/dist/core/command-generation/adapters/crush.js +5 -5
  41. package/dist/core/command-generation/adapters/cursor.js +2 -2
  42. package/dist/core/command-generation/adapters/devin.d.ts +19 -0
  43. package/dist/core/command-generation/adapters/devin.js +36 -0
  44. package/dist/core/command-generation/adapters/factory.js +2 -1
  45. package/dist/core/command-generation/adapters/gemini.js +37 -2
  46. package/dist/core/command-generation/adapters/github-copilot.js +2 -1
  47. package/dist/core/command-generation/adapters/iflow.js +5 -4
  48. package/dist/core/command-generation/adapters/index.d.ts +2 -2
  49. package/dist/core/command-generation/adapters/index.js +2 -2
  50. package/dist/core/command-generation/adapters/junie.js +2 -1
  51. package/dist/core/command-generation/adapters/kiro.js +2 -1
  52. package/dist/core/command-generation/adapters/lingma.js +5 -5
  53. package/dist/core/command-generation/adapters/oh-my-pi.d.ts +2 -2
  54. package/dist/core/command-generation/adapters/oh-my-pi.js +3 -5
  55. package/dist/core/command-generation/adapters/opencode.js +3 -5
  56. package/dist/core/command-generation/adapters/pi.d.ts +2 -2
  57. package/dist/core/command-generation/adapters/pi.js +3 -6
  58. package/dist/core/command-generation/adapters/qoder.js +5 -5
  59. package/dist/core/command-generation/adapters/qwen.d.ts +7 -3
  60. package/dist/core/command-generation/adapters/qwen.js +12 -7
  61. package/dist/core/command-generation/adapters/roocode.d.ts +4 -4
  62. package/dist/core/command-generation/adapters/roocode.js +4 -4
  63. package/dist/core/command-generation/adapters/trae.js +1 -21
  64. package/dist/core/command-generation/adapters/zcode.d.ts +16 -0
  65. package/dist/core/command-generation/adapters/zcode.js +33 -0
  66. package/dist/core/command-generation/generator.d.ts +8 -0
  67. package/dist/core/command-generation/generator.js +15 -1
  68. package/dist/core/command-generation/index.d.ts +1 -1
  69. package/dist/core/command-generation/index.js +1 -1
  70. package/dist/core/command-generation/invocation.d.ts +73 -0
  71. package/dist/core/command-generation/invocation.js +77 -0
  72. package/dist/core/command-generation/registry.js +4 -4
  73. package/dist/core/command-generation/types.d.ts +9 -2
  74. package/dist/core/command-generation/yaml.d.ts +20 -6
  75. package/dist/core/command-generation/yaml.js +31 -21
  76. package/dist/core/command-surface.d.ts +15 -0
  77. package/dist/core/command-surface.js +33 -0
  78. package/dist/core/completions/command-registry.js +8 -2
  79. package/dist/core/completions/generators/powershell-generator.js +15 -8
  80. package/dist/core/completions/installers/zsh-installer.d.ts +9 -0
  81. package/dist/core/completions/installers/zsh-installer.js +20 -4
  82. package/dist/core/config-prompts.d.ts +1 -1
  83. package/dist/core/config-prompts.js +14 -1
  84. package/dist/core/config-schema.d.ts +7 -1
  85. package/dist/core/config-schema.js +40 -1
  86. package/dist/core/config.d.ts +12 -0
  87. package/dist/core/config.js +21 -3
  88. package/dist/core/global-config.d.ts +5 -0
  89. package/dist/core/init.d.ts +35 -0
  90. package/dist/core/init.js +225 -39
  91. package/dist/core/legacy-cleanup.d.ts +59 -0
  92. package/dist/core/legacy-cleanup.js +249 -22
  93. package/dist/core/list.js +5 -7
  94. package/dist/core/migration.d.ts +71 -1
  95. package/dist/core/migration.js +294 -1
  96. package/dist/core/onboarding-commands.d.ts +30 -0
  97. package/dist/core/onboarding-commands.js +38 -0
  98. package/dist/core/parsers/change-parser.d.ts +17 -2
  99. package/dist/core/parsers/change-parser.js +33 -20
  100. package/dist/core/parsers/code-fence.d.ts +15 -0
  101. package/dist/core/parsers/code-fence.js +49 -0
  102. package/dist/core/parsers/markdown-parser.js +2 -1
  103. package/dist/core/parsers/requirement-blocks.d.ts +8 -0
  104. package/dist/core/parsers/requirement-blocks.js +60 -35
  105. package/dist/core/parsers/requirement-text.d.ts +1 -8
  106. package/dist/core/parsers/requirement-text.js +4 -42
  107. package/dist/core/parsers/spec-structure.js +3 -29
  108. package/dist/core/profile-sync-drift.d.ts +0 -8
  109. package/dist/core/profile-sync-drift.js +9 -45
  110. package/dist/core/project-config.d.ts +25 -6
  111. package/dist/core/project-config.js +77 -7
  112. package/dist/core/references.js +42 -4
  113. package/dist/core/relationship-health.d.ts +8 -0
  114. package/dist/core/relationship-health.js +13 -0
  115. package/dist/core/root-selection.d.ts +9 -6
  116. package/dist/core/root-selection.js +42 -5
  117. package/dist/core/shared/tool-detection.d.ts +25 -5
  118. package/dist/core/shared/tool-detection.js +105 -6
  119. package/dist/core/specs-apply.d.ts +8 -33
  120. package/dist/core/specs-apply.js +252 -157
  121. package/dist/core/store/git.d.ts +13 -0
  122. package/dist/core/store/git.js +24 -0
  123. package/dist/core/templates/workflows/apply-change.js +54 -4
  124. package/dist/core/templates/workflows/archive-change.js +166 -42
  125. package/dist/core/templates/workflows/bulk-archive-change.js +240 -63
  126. package/dist/core/templates/workflows/continue-change.js +32 -40
  127. package/dist/core/templates/workflows/explore.js +12 -0
  128. package/dist/core/templates/workflows/ff-change.js +46 -30
  129. package/dist/core/templates/workflows/new-change.js +2 -2
  130. package/dist/core/templates/workflows/onboard.js +4 -4
  131. package/dist/core/templates/workflows/propose.js +50 -32
  132. package/dist/core/templates/workflows/store-selection.d.ts +1 -1
  133. package/dist/core/templates/workflows/store-selection.js +1 -1
  134. package/dist/core/templates/workflows/sync-specs.js +172 -22
  135. package/dist/core/templates/workflows/update-change.js +20 -12
  136. package/dist/core/templates/workflows/verify-change.js +16 -10
  137. package/dist/core/update.d.ts +39 -5
  138. package/dist/core/update.js +288 -71
  139. package/dist/core/validation/constants.d.ts +4 -1
  140. package/dist/core/validation/constants.js +4 -1
  141. package/dist/core/validation/validator.d.ts +1 -7
  142. package/dist/core/validation/validator.js +92 -36
  143. package/dist/core/version-check.d.ts +152 -0
  144. package/dist/core/version-check.js +736 -0
  145. package/dist/core/view.js +12 -17
  146. package/dist/prompts/searchable-multi-select.js +1 -1
  147. package/dist/telemetry/index.d.ts +1 -1
  148. package/dist/telemetry/index.js +55 -33
  149. package/dist/ui/welcome-screen.d.ts +9 -1
  150. package/dist/ui/welcome-screen.js +91 -38
  151. package/dist/utils/change-metadata.d.ts +30 -0
  152. package/dist/utils/change-metadata.js +101 -7
  153. package/dist/utils/change-utils.d.ts +10 -4
  154. package/dist/utils/change-utils.js +20 -13
  155. package/dist/utils/command-references.d.ts +80 -6
  156. package/dist/utils/command-references.js +138 -7
  157. package/dist/utils/date.d.ts +8 -0
  158. package/dist/utils/date.js +13 -0
  159. package/dist/utils/index.d.ts +1 -1
  160. package/dist/utils/index.js +1 -1
  161. package/dist/utils/item-discovery.d.ts +19 -0
  162. package/dist/utils/item-discovery.js +30 -48
  163. package/dist/utils/shell-detection.d.ts +1 -1
  164. package/dist/utils/shell-detection.js +64 -11
  165. package/dist/utils/spec-discovery.d.ts +40 -0
  166. package/dist/utils/spec-discovery.js +102 -0
  167. package/package.json +14 -12
  168. package/schemas/spec-driven/schema.yaml +60 -5
  169. package/schemas/spec-driven/templates/design.md +2 -2
  170. package/schemas/spec-driven/templates/proposal.md +5 -1
  171. package/schemas/spec-driven/templates/spec.md +3 -0
  172. package/dist/core/command-generation/adapters/codex.d.ts +0 -16
  173. package/dist/core/command-generation/adapters/codex.js +0 -39
  174. package/dist/core/command-generation/adapters/windsurf.d.ts +0 -14
  175. 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 creating an artifact or applying tasks',
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
- for (const [index, positional] of positionals.entries()) {
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(ohMyZshPath);
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.homeDir, '.oh-my-zsh', 'custom', 'completions', '_openspec'),
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
- ' echo $fpath | grep "custom/completions"',
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, context/rules optional)
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, context/rules optional)
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];
@@ -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
@@ -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 CLI', value: 'kimi', available: true, successLabel: 'Kimi CLI', skillsDir: '.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: 'RooCode', value: 'roocode', available: true, successLabel: 'RooCode', skillsDir: '.roo' },
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: 'Windsurf', value: 'windsurf', available: true, successLabel: 'Windsurf', skillsDir: '.windsurf' },
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
  }
@@ -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;