@fission-ai/openspec 1.8.0 → 1.10.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 (76) hide show
  1. package/dist/cli/index.d.ts +32 -0
  2. package/dist/cli/index.js +80 -9
  3. package/dist/commands/change.js +1 -0
  4. package/dist/commands/config.d.ts +5 -0
  5. package/dist/commands/config.js +19 -4
  6. package/dist/commands/feedback.js +28 -7
  7. package/dist/commands/schema.js +193 -28
  8. package/dist/commands/validate.d.ts +23 -0
  9. package/dist/commands/validate.js +148 -4
  10. package/dist/commands/workflow/schemas.d.ts +2 -0
  11. package/dist/commands/workflow/schemas.js +9 -2
  12. package/dist/core/archive.js +74 -10
  13. package/dist/core/artifact-graph/instruction-loader.js +2 -7
  14. package/dist/core/artifact-graph/outputs.d.ts +4 -0
  15. package/dist/core/artifact-graph/outputs.js +7 -0
  16. package/dist/core/artifact-graph/resolver.d.ts +0 -12
  17. package/dist/core/artifact-graph/resolver.js +14 -0
  18. package/dist/core/command-generation/adapters/command-code.d.ts +19 -0
  19. package/dist/core/command-generation/adapters/command-code.js +34 -0
  20. package/dist/core/command-generation/adapters/index.d.ts +1 -0
  21. package/dist/core/command-generation/adapters/index.js +1 -0
  22. package/dist/core/command-generation/adapters/opencode.d.ts +2 -1
  23. package/dist/core/command-generation/adapters/opencode.js +13 -2
  24. package/dist/core/command-generation/registry.js +2 -0
  25. package/dist/core/completion-tip.d.ts +15 -0
  26. package/dist/core/completion-tip.js +133 -0
  27. package/dist/core/completions/command-registry.js +10 -0
  28. package/dist/core/completions/factory.d.ts +11 -0
  29. package/dist/core/completions/installers/bash-installer.d.ts +7 -0
  30. package/dist/core/completions/installers/bash-installer.js +15 -0
  31. package/dist/core/completions/installers/fish-installer.d.ts +7 -0
  32. package/dist/core/completions/installers/fish-installer.js +15 -0
  33. package/dist/core/completions/installers/powershell-installer.d.ts +7 -0
  34. package/dist/core/completions/installers/powershell-installer.js +15 -0
  35. package/dist/core/config-prompts.js +19 -10
  36. package/dist/core/config-schema.d.ts +1 -0
  37. package/dist/core/config-schema.js +2 -0
  38. package/dist/core/config.d.ts +1 -0
  39. package/dist/core/config.js +17 -15
  40. package/dist/core/global-config.d.ts +2 -0
  41. package/dist/core/init.d.ts +5 -0
  42. package/dist/core/init.js +110 -14
  43. package/dist/core/legacy-cleanup.d.ts +10 -0
  44. package/dist/core/legacy-cleanup.js +48 -26
  45. package/dist/core/parsers/requirement-blocks.js +41 -9
  46. package/dist/core/parsers/requirement-text.d.ts +8 -0
  47. package/dist/core/parsers/requirement-text.js +4 -3
  48. package/dist/core/profiles.d.ts +1 -1
  49. package/dist/core/profiles.js +11 -2
  50. package/dist/core/root-selection.d.ts +1 -1
  51. package/dist/core/shared-skill-target.d.ts +15 -0
  52. package/dist/core/shared-skill-target.js +30 -0
  53. package/dist/core/specs-apply.js +11 -4
  54. package/dist/core/templates/workflows/apply-change.js +3 -0
  55. package/dist/core/templates/workflows/feedback.js +1 -0
  56. package/dist/core/templates/workflows/onboard.js +4 -4
  57. package/dist/core/templates/workflows/propose.js +2 -2
  58. package/dist/core/templates/workflows/store-selection.d.ts +1 -1
  59. package/dist/core/templates/workflows/store-selection.js +1 -1
  60. package/dist/core/templates/workflows/update-change.js +4 -4
  61. package/dist/core/update.js +49 -11
  62. package/dist/core/validation/task-numbering.d.ts +16 -0
  63. package/dist/core/validation/task-numbering.js +57 -0
  64. package/dist/core/validation/validator.d.ts +5 -2
  65. package/dist/core/validation/validator.js +57 -3
  66. package/dist/telemetry/index.d.ts +3 -1
  67. package/dist/telemetry/index.js +10 -3
  68. package/dist/utils/change-metadata.js +17 -13
  69. package/dist/utils/change-utils.js +5 -0
  70. package/dist/utils/interactive.d.ts +30 -1
  71. package/dist/utils/interactive.js +105 -2
  72. package/dist/utils/task-progress.d.ts +34 -1
  73. package/dist/utils/task-progress.js +52 -28
  74. package/package.json +3 -6
  75. package/schemas/spec-driven/schema.yaml +17 -7
  76. package/scripts/postinstall.js +0 -83
@@ -0,0 +1,133 @@
1
+ /**
2
+ * First-run hint pointing users at opt-in shell completions.
3
+ *
4
+ * This hint used to be an npm `postinstall` script. Printing it from the CLI
5
+ * instead lets the package ship with no install scripts at all, so `npm install`
6
+ * no longer emits an `allow-scripts` warning. Completions stay opt-in: the tip
7
+ * only names the command, it never installs anything.
8
+ *
9
+ * The tip goes to stderr, never stdout, so it cannot contaminate piped command
10
+ * output.
11
+ *
12
+ * The tip is suppressed when:
13
+ * - CI is set (any value npm/telemetry would treat as CI)
14
+ * - OPENSPEC_NO_COMPLETIONS=1
15
+ * - completions are already installed, or the shell is one the installer would
16
+ * reject
17
+ * - the caller passes `silent` — JSON runs, `openspec completion ...`, and
18
+ * non-TTY runs, which are deferred rather than consumed (see `silent`)
19
+ */
20
+ import * as fs from 'node:fs';
21
+ import * as path from 'node:path';
22
+ import { getGlobalConfigPath } from './global-config.js';
23
+ import { isCiEnvironment } from '../utils/ci.js';
24
+ import { detectShell } from '../utils/shell-detection.js';
25
+ import { CompletionFactory } from './completions/factory.js';
26
+ export const COMPLETION_TIP_MESSAGE = "Tip: Run 'openspec completion install' for shell completions";
27
+ function isSuppressedByEnv() {
28
+ // isCiEnvironment, not a CI==='true' string check: providers set CI to "True",
29
+ // "yes", "on", and the tip should be as quiet in those builds as telemetry is.
30
+ return isCiEnvironment() || process.env.OPENSPEC_NO_COMPLETIONS === '1';
31
+ }
32
+ /**
33
+ * Whether the tip is worth showing, once we know it is owed and readable.
34
+ *
35
+ * "retire" consumes the tip without printing: the user either already has
36
+ * completions, or is on a shell `openspec completion install` would refuse.
37
+ *
38
+ * Without the installed check the tip tells people to install completions they
39
+ * installed long ago — including on the run right after `completion install`,
40
+ * whose own run only defers the tip. An undetected or unsupported shell retires
41
+ * it too: `completion install` exits 1 for those users, so pointing them at it
42
+ * is a dead end, and this tip is the only message about completions they would
43
+ * ever get.
44
+ *
45
+ * Not free: detectShell() forks `ps` to read the parent process (except on
46
+ * Windows), so this costs a spawn plus a stat. It runs only on interactive runs
47
+ * that still owe the tip, which is normally exactly one — but a config that
48
+ * cannot be written never records the flag, and then every interactive run pays
49
+ * it. On any unexpected error we show the tip rather than swallow it.
50
+ */
51
+ async function decideTip() {
52
+ try {
53
+ const { shell } = detectShell();
54
+ if (!shell) {
55
+ return 'retire';
56
+ }
57
+ return (await CompletionFactory.createInstaller(shell).isInstalled())
58
+ ? 'retire'
59
+ : 'show';
60
+ }
61
+ catch {
62
+ return 'show';
63
+ }
64
+ }
65
+ /**
66
+ * Read the global config exactly as it sits on disk.
67
+ *
68
+ * Deliberately NOT `getGlobalConfig()`: that merges in defaults, and writing the
69
+ * merged result back would stamp `profile`/`delivery` into a file the user never
70
+ * set them in. `migrateIfNeeded` treats a raw `profile` as "already migrated",
71
+ * so that stamp would permanently suppress the one-time profile migration and
72
+ * cost users their installed workflow skills.
73
+ *
74
+ * Returns null when the file exists but cannot be read or parsed — a config we
75
+ * cannot understand is left strictly alone rather than overwritten.
76
+ */
77
+ function readRawConfig() {
78
+ const configPath = getGlobalConfigPath();
79
+ if (!fs.existsSync(configPath)) {
80
+ return {};
81
+ }
82
+ const parsed = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
83
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
84
+ return null;
85
+ }
86
+ return parsed;
87
+ }
88
+ /**
89
+ * Record the flag, re-reading the config first and replacing the file by rename.
90
+ *
91
+ * Deciding whether to show the tip costs a `ps` spawn and a stat, and a sibling
92
+ * `openspec` process can write the same file in that window — on a first run
93
+ * that is exactly when telemetry mints `anonymousId`. Re-reading here keeps the
94
+ * write down to this one key, and the rename keeps a reader from ever seeing a
95
+ * half-written config.
96
+ */
97
+ function markTipSeen() {
98
+ const configPath = getGlobalConfigPath();
99
+ const current = readRawConfig() ?? {};
100
+ const tempPath = `${configPath}.${process.pid}.tmp`;
101
+ fs.mkdirSync(path.dirname(configPath), { recursive: true });
102
+ fs.writeFileSync(tempPath, JSON.stringify({ ...current, completionTipSeen: true }, null, 2) + '\n', 'utf-8');
103
+ fs.renameSync(tempPath, configPath);
104
+ }
105
+ /**
106
+ * Print the completion tip once, the first time the CLI runs.
107
+ * Never throws — a hint must not break a command.
108
+ */
109
+ export async function maybeShowCompletionTip(options = {}) {
110
+ if (isSuppressedByEnv()) {
111
+ return;
112
+ }
113
+ try {
114
+ const raw = readRawConfig();
115
+ if (raw === null || raw.completionTipSeen === true) {
116
+ return;
117
+ }
118
+ if (options.silent) {
119
+ return;
120
+ }
121
+ const decision = await decideTip();
122
+ // Record before printing: if the flag cannot be persisted, staying quiet
123
+ // beats reprinting the tip on every future run.
124
+ markTipSeen();
125
+ if (decision === 'show') {
126
+ console.error(`\n${COMPLETION_TIP_MESSAGE}`);
127
+ }
128
+ }
129
+ catch {
130
+ // Silent failure - a hint should never break the CLI.
131
+ }
132
+ }
133
+ //# sourceMappingURL=completion-tip.js.map
@@ -12,6 +12,11 @@ export const COMMAND_REGISTRY = [
12
12
  description: 'Configure AI tools non-interactively (e.g., "all", "none", or comma-separated tool IDs)',
13
13
  takesValue: true,
14
14
  },
15
+ {
16
+ name: 'language',
17
+ description: 'Write new OpenSpec artifacts in this language',
18
+ takesValue: true,
19
+ },
15
20
  {
16
21
  name: 'force',
17
22
  description: 'Auto-cleanup legacy files without prompting',
@@ -97,6 +102,10 @@ export const COMMAND_REGISTRY = [
97
102
  name: 'specs',
98
103
  description: 'Validate all specs',
99
104
  },
105
+ {
106
+ name: 'archived',
107
+ description: 'Validate that archived changes have all tasks completed (for pre-commit linting)',
108
+ },
100
109
  COMMON_FLAGS.type,
101
110
  COMMON_FLAGS.strict,
102
111
  COMMON_FLAGS.jsonValidation,
@@ -226,6 +235,7 @@ export const COMMAND_REGISTRY = [
226
235
  description: 'List available workflow schemas with descriptions',
227
236
  flags: [
228
237
  COMMON_FLAGS.json,
238
+ COMMON_FLAGS.store,
229
239
  ],
230
240
  },
231
241
  {
@@ -24,6 +24,17 @@ export interface CompletionInstaller {
24
24
  success: boolean;
25
25
  message: string;
26
26
  }>;
27
+ /**
28
+ * True when a completion script file is present at the install path.
29
+ *
30
+ * Deliberately just the script: bash and PowerShell also need a sourcing
31
+ * line in the user's profile, and `install()` adds that on a best-effort
32
+ * basis (it is skipped by OPENSPEC_NO_AUTO_CONFIG=1 or an unwritable
33
+ * profile, printing manual instructions instead). Someone in that state has
34
+ * already met the installer, so callers that use this to decide whether to
35
+ * *advertise* completions should not advertise again.
36
+ */
37
+ isInstalled(): Promise<boolean>;
27
38
  }
28
39
  /**
29
40
  * Factory for creating completion generators and installers
@@ -22,6 +22,13 @@ export declare class BashInstaller {
22
22
  * @returns Installation path
23
23
  */
24
24
  getInstallationPath(): Promise<string>;
25
+ /**
26
+ * Check if a completion script is currently installed.
27
+ * Mirrors ZshInstaller.isInstalled so callers can ask any installer.
28
+ *
29
+ * @returns true if the completion script exists
30
+ */
31
+ isInstalled(): Promise<boolean>;
25
32
  /**
26
33
  * Backup an existing completion file if it exists
27
34
  *
@@ -55,6 +55,21 @@ export class BashInstaller {
55
55
  // For user installation, use local directory
56
56
  return path.join(localCompletionDir, 'openspec');
57
57
  }
58
+ /**
59
+ * Check if a completion script is currently installed.
60
+ * Mirrors ZshInstaller.isInstalled so callers can ask any installer.
61
+ *
62
+ * @returns true if the completion script exists
63
+ */
64
+ async isInstalled() {
65
+ try {
66
+ // stat, not access: a directory at the install path is not a script.
67
+ return (await fs.stat(await this.getInstallationPath())).isFile();
68
+ }
69
+ catch {
70
+ return false;
71
+ }
72
+ }
58
73
  /**
59
74
  * Backup an existing completion file if it exists
60
75
  *
@@ -12,6 +12,13 @@ export declare class FishInstaller {
12
12
  * @returns Installation path
13
13
  */
14
14
  getInstallationPath(): string;
15
+ /**
16
+ * Check if a completion script is currently installed.
17
+ * Mirrors ZshInstaller.isInstalled so callers can ask any installer.
18
+ *
19
+ * @returns true if the completion script exists
20
+ */
21
+ isInstalled(): Promise<boolean>;
15
22
  /**
16
23
  * Backup an existing completion file if it exists
17
24
  *
@@ -19,6 +19,21 @@ export class FishInstaller {
19
19
  getInstallationPath() {
20
20
  return path.join(this.homeDir, '.config', 'fish', 'completions', 'openspec.fish');
21
21
  }
22
+ /**
23
+ * Check if a completion script is currently installed.
24
+ * Mirrors ZshInstaller.isInstalled so callers can ask any installer.
25
+ *
26
+ * @returns true if the completion script exists
27
+ */
28
+ async isInstalled() {
29
+ try {
30
+ // stat, not access: a directory at the install path is not a script.
31
+ return (await fs.stat(this.getInstallationPath())).isFile();
32
+ }
33
+ catch {
34
+ return false;
35
+ }
36
+ }
22
37
  /**
23
38
  * Backup an existing completion file if it exists
24
39
  *
@@ -43,6 +43,13 @@ export declare class PowerShellInstaller {
43
43
  * @returns Installation path
44
44
  */
45
45
  getInstallationPath(): string;
46
+ /**
47
+ * Check if a completion script is currently installed.
48
+ * Mirrors ZshInstaller.isInstalled so callers can ask any installer.
49
+ *
50
+ * @returns true if the completion script exists
51
+ */
52
+ isInstalled(): Promise<boolean>;
46
53
  /**
47
54
  * Backup an existing completion file if it exists
48
55
  *
@@ -110,6 +110,21 @@ export class PowerShellInstaller {
110
110
  const profileDir = path.dirname(profilePath);
111
111
  return path.join(profileDir, 'OpenSpecCompletion.ps1');
112
112
  }
113
+ /**
114
+ * Check if a completion script is currently installed.
115
+ * Mirrors ZshInstaller.isInstalled so callers can ask any installer.
116
+ *
117
+ * @returns true if the completion script exists
118
+ */
119
+ async isInstalled() {
120
+ try {
121
+ // stat, not access: a directory at the install path is not a script.
122
+ return (await fs.stat(this.getInstallationPath())).isFile();
123
+ }
124
+ catch {
125
+ return false;
126
+ }
127
+ }
113
128
  /**
114
129
  * Backup an existing completion file if it exists
115
130
  *
@@ -9,16 +9,25 @@ export function serializeConfig(config) {
9
9
  // Schema (required)
10
10
  lines.push(`schema: ${config.schema}`);
11
11
  lines.push('');
12
- // Context section with comments
13
- lines.push('# Project context (optional)');
14
- lines.push('# This is shown to AI when creating artifacts.');
15
- lines.push('# Add your tech stack, conventions, style guides, domain knowledge, etc.');
16
- lines.push('# Example:');
17
- lines.push('# context: |');
18
- lines.push('# Tech stack: TypeScript, React, Node.js');
19
- lines.push('# We use conventional commits');
20
- lines.push('# Domain: e-commerce platform');
21
- lines.push('');
12
+ if (config.context !== undefined) {
13
+ lines.push('context: |');
14
+ for (const line of config.context.split('\n')) {
15
+ lines.push(` ${line}`);
16
+ }
17
+ lines.push('');
18
+ }
19
+ else {
20
+ // Context section with comments
21
+ lines.push('# Project context (optional)');
22
+ lines.push('# This is shown to AI when creating artifacts.');
23
+ lines.push('# Add your tech stack, conventions, style guides, domain knowledge, etc.');
24
+ lines.push('# Example:');
25
+ lines.push('# context: |');
26
+ lines.push('# Tech stack: TypeScript, React, Node.js');
27
+ lines.push('# We use conventional commits');
28
+ lines.push('# Domain: e-commerce platform');
29
+ lines.push('');
30
+ }
22
31
  // Rules section with comments
23
32
  lines.push('# Per-artifact rules (optional)');
24
33
  lines.push('# Add custom rules for specific artifacts.');
@@ -19,6 +19,7 @@ export declare const GlobalConfigSchema: z.ZodObject<{
19
19
  telemetry: z.ZodOptional<z.ZodObject<{
20
20
  enabled: z.ZodOptional<z.ZodBoolean>;
21
21
  }, z.core.$loose>>;
22
+ completionTipSeen: z.ZodOptional<z.ZodBoolean>;
22
23
  }, z.core.$loose>;
23
24
  export type GlobalConfigType = z.infer<typeof GlobalConfigSchema>;
24
25
  /**
@@ -32,6 +32,8 @@ export const GlobalConfigSchema = z
32
32
  })
33
33
  .passthrough()
34
34
  .optional(),
35
+ // Runtime-managed (like telemetry.noticeSeen); not user-settable via CLI set.
36
+ completionTipSeen: z.boolean().optional(),
35
37
  })
36
38
  .passthrough();
37
39
  /**
@@ -17,6 +17,7 @@ export interface AIToolOption {
17
17
  globalSkillsDir?: string;
18
18
  detectionPaths?: string[];
19
19
  setupNote?: string;
20
+ requiresIdeRestart?: boolean;
20
21
  }
21
22
  export declare const AI_TOOLS: AIToolOption[];
22
23
  /**
@@ -18,41 +18,43 @@ export const OPENSPEC_MARKERS = {
18
18
  end: '<!-- OPENSPEC:END -->'
19
19
  };
20
20
  export const AI_TOOLS = [
21
- { name: 'Amazon Q Developer', value: 'amazon-q', available: true, successLabel: 'Amazon Q Developer', skillsDir: '.amazonq' },
22
- { name: 'Antigravity', value: 'antigravity', available: true, successLabel: 'Antigravity', skillsDir: '.agent' },
21
+ { name: 'Amazon Q Developer', value: 'amazon-q', available: true, successLabel: 'Amazon Q Developer', skillsDir: '.amazonq', requiresIdeRestart: true },
22
+ { name: 'Antigravity', value: 'antigravity', available: true, successLabel: 'Antigravity', skillsDir: '.agent', requiresIdeRestart: true },
23
23
  { name: 'Auggie (Augment CLI)', value: 'auggie', available: true, successLabel: 'Auggie', skillsDir: '.augment' },
24
24
  { name: 'Bob Shell', value: 'bob', available: true, successLabel: 'Bob Shell', skillsDir: '.bob' },
25
25
  { name: 'Claude Code', value: 'claude', available: true, successLabel: 'Claude Code', skillsDir: '.claude' },
26
- { name: 'Cline', value: 'cline', available: true, successLabel: 'Cline', skillsDir: '.cline' },
26
+ { name: 'Cline', value: 'cline', available: true, successLabel: 'Cline', skillsDir: '.cline', requiresIdeRestart: true },
27
+ { name: 'Command Code', value: 'command-code', available: true, successLabel: 'Command Code', skillsDir: '.commandcode' },
27
28
  { name: 'CodeArts', value: 'codeartsagent', available: true, successLabel: 'CodeArts', skillsDir: '.codeartsdoer' },
28
29
  { name: 'Codex', value: 'codex', available: true, successLabel: 'Codex', skillsDir: '.agents', legacySkillsDirs: ['.codex'], detectionPaths: ['.agents/skills', '.codex/skills'] },
29
- { name: 'Devin Desktop (formerly Windsurf)', value: 'devin', available: true, successLabel: 'Devin Desktop', skillsDir: '.devin', detectionPaths: ['.devin', '.windsurf'] },
30
+ { name: 'Devin Desktop (formerly Windsurf)', value: 'devin', available: true, successLabel: 'Devin Desktop', skillsDir: '.devin', detectionPaths: ['.devin', '.windsurf'], requiresIdeRestart: true },
30
31
  { name: 'ForgeCode', value: 'forgecode', available: true, successLabel: 'ForgeCode', skillsDir: '.forge' },
31
32
  { name: 'CodeBuddy Code (CLI)', value: 'codebuddy', available: true, successLabel: 'CodeBuddy Code', skillsDir: '.codebuddy' },
32
- { name: 'Continue', value: 'continue', available: true, successLabel: 'Continue (VS Code / JetBrains / Cli)', skillsDir: '.continue' },
33
- { name: 'CoStrict', value: 'costrict', available: true, successLabel: 'CoStrict', skillsDir: '.cospec' },
33
+ { name: 'Continue', value: 'continue', available: true, successLabel: 'Continue (VS Code / JetBrains / Cli)', skillsDir: '.continue', requiresIdeRestart: true },
34
+ { name: 'CoStrict', value: 'costrict', available: true, successLabel: 'CoStrict', skillsDir: '.cospec', requiresIdeRestart: true },
34
35
  { name: 'Crush', value: 'crush', available: true, successLabel: 'Crush', skillsDir: '.crush' },
35
- { name: 'Cursor', value: 'cursor', available: true, successLabel: 'Cursor', skillsDir: '.cursor' },
36
+ { name: 'Cursor', value: 'cursor', available: true, successLabel: 'Cursor', skillsDir: '.cursor', requiresIdeRestart: true },
36
37
  { name: 'Factory Droid', value: 'factory', available: true, successLabel: 'Factory Droid', skillsDir: '.factory' },
37
38
  { name: 'Gemini CLI', value: 'gemini', available: true, successLabel: 'Gemini CLI', skillsDir: '.gemini' },
38
- { 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'] },
39
+ { 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'], requiresIdeRestart: true },
39
40
  { 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." },
40
41
  { name: 'iFlow', value: 'iflow', available: true, successLabel: 'iFlow', skillsDir: '.iflow' },
41
- { name: 'Junie', value: 'junie', available: true, successLabel: 'Junie', skillsDir: '.junie' },
42
- { name: 'Kilo Code', value: 'kilocode', available: true, successLabel: 'Kilo Code', skillsDir: '.kilocode' },
42
+ { name: 'Junie', value: 'junie', available: true, successLabel: 'Junie', skillsDir: '.junie', requiresIdeRestart: true },
43
+ { name: 'Kilo Code', value: 'kilocode', available: true, successLabel: 'Kilo Code', skillsDir: '.kilocode', requiresIdeRestart: true },
43
44
  { name: 'Kimi Code', value: 'kimi', available: true, successLabel: 'Kimi Code', skillsDir: '.kimi-code', detectionPaths: ['.kimi-code', '.kimi'] },
44
- { name: 'Kiro', value: 'kiro', available: true, successLabel: 'Kiro', skillsDir: '.kiro' },
45
- { name: 'Lingma', value: 'lingma', available: true, successLabel: 'Lingma', skillsDir: '.lingma' },
45
+ { name: 'Kiro', value: 'kiro', available: true, successLabel: 'Kiro', skillsDir: '.kiro', requiresIdeRestart: true },
46
+ { name: 'Lingma', value: 'lingma', available: true, successLabel: 'Lingma', skillsDir: '.lingma', requiresIdeRestart: true },
46
47
  { name: 'MiniMax Code', value: 'minimax-code', available: true, successLabel: 'MiniMax Code', globalSkillsDir: '.minimax' },
47
48
  { name: 'Mistral Vibe', value: 'vibe', available: true, successLabel: 'Mistral Vibe', skillsDir: '.vibe' },
48
49
  { name: 'Oh My Pi', value: 'oh-my-pi', available: true, successLabel: 'Oh My Pi', skillsDir: '.omp' },
49
50
  { name: 'OpenCode', value: 'opencode', available: true, successLabel: 'OpenCode', skillsDir: '.opencode' },
50
51
  { name: 'Pi', value: 'pi', available: true, successLabel: 'Pi', skillsDir: '.pi' },
51
- { name: 'Qoder', value: 'qoder', available: true, successLabel: 'Qoder', skillsDir: '.qoder' },
52
+ { name: 'Qoder', value: 'qoder', available: true, successLabel: 'Qoder', skillsDir: '.qoder', requiresIdeRestart: true },
52
53
  { name: 'Qwen Code', value: 'qwen', available: true, successLabel: 'Qwen Code', skillsDir: '.qwen' },
53
54
  { name: 'Rovo Dev CLI', value: 'rovodev', available: true, successLabel: 'Rovo Dev CLI', skillsDir: '.rovodev', detectionPaths: ['.rovodev/skills', '.rovodev'] },
54
- { name: 'Zoo Code', value: 'roocode', available: true, successLabel: 'Zoo Code', skillsDir: '.roo' },
55
- { name: 'Trae', value: 'trae', available: true, successLabel: 'Trae', skillsDir: '.trae' },
55
+ { name: 'Zoo Code', value: 'roocode', available: true, successLabel: 'Zoo Code', skillsDir: '.roo', requiresIdeRestart: true },
56
+ { name: 'Trae', value: 'trae', available: true, successLabel: 'Trae', skillsDir: '.trae', requiresIdeRestart: true },
57
+ { name: 'Zed Agent', value: 'zed', available: true, successLabel: 'Zed Agent', skillsDir: '.agents', detectionPaths: ['.zed', '.agents/skills'] },
56
58
  { name: 'ZCode', value: 'zcode', available: true, successLabel: 'ZCode', skillsDir: '.zcode' },
57
59
  // Vendor-neutral target for assistants that read the shared `.agents` root.
58
60
  // Detection keys off `.agents/skills` rather than the bare root: frameworks use
@@ -26,6 +26,8 @@ export interface GlobalConfig {
26
26
  openers?: unknown;
27
27
  /** Anonymous usage analytics settings and identity. */
28
28
  telemetry?: TelemetryConfig;
29
+ /** Whether the first-run shell-completions tip has been shown. */
30
+ completionTipSeen?: boolean;
29
31
  }
30
32
  /**
31
33
  * Gets the global configuration directory path following XDG Base Directory Specification.
@@ -6,6 +6,7 @@
6
6
  */
7
7
  type InitCommandOptions = {
8
8
  tools?: string;
9
+ language?: string;
9
10
  force?: boolean;
10
11
  interactive?: boolean;
11
12
  profile?: string;
@@ -20,6 +21,7 @@ type InitCommandOptions = {
20
21
  };
21
22
  export declare class InitCommand {
22
23
  private readonly toolsArg?;
24
+ private readonly language?;
23
25
  private readonly force;
24
26
  private readonly interactiveOption?;
25
27
  private readonly profileOverride?;
@@ -85,6 +87,9 @@ export declare class InitCommand {
85
87
  * @returns Created, refreshed, and failed tools plus removed artifact counts
86
88
  */
87
89
  private generateSkillsAndCommands;
90
+ private normalizeLanguage;
91
+ private languageContext;
92
+ private assertLanguageCanBeApplied;
88
93
  private createConfig;
89
94
  private displaySuccessMessage;
90
95
  private startSpinner;