@fission-ai/openspec 1.1.0 → 1.2.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 (73) hide show
  1. package/dist/cli/index.js +2 -0
  2. package/dist/commands/config.d.ts +28 -0
  3. package/dist/commands/config.js +359 -5
  4. package/dist/core/available-tools.d.ts +16 -0
  5. package/dist/core/available-tools.js +30 -0
  6. package/dist/core/command-generation/adapters/index.d.ts +2 -0
  7. package/dist/core/command-generation/adapters/index.js +2 -0
  8. package/dist/core/command-generation/adapters/kiro.d.ts +13 -0
  9. package/dist/core/command-generation/adapters/kiro.js +26 -0
  10. package/dist/core/command-generation/adapters/opencode.js +4 -1
  11. package/dist/core/command-generation/adapters/pi.d.ts +14 -0
  12. package/dist/core/command-generation/adapters/pi.js +41 -0
  13. package/dist/core/command-generation/registry.js +4 -0
  14. package/dist/core/completions/command-registry.js +5 -0
  15. package/dist/core/config-schema.d.ts +10 -0
  16. package/dist/core/config-schema.js +14 -1
  17. package/dist/core/config.js +2 -0
  18. package/dist/core/global-config.d.ts +5 -0
  19. package/dist/core/global-config.js +12 -2
  20. package/dist/core/init.d.ts +5 -0
  21. package/dist/core/init.js +206 -42
  22. package/dist/core/legacy-cleanup.js +1 -0
  23. package/dist/core/migration.d.ts +23 -0
  24. package/dist/core/migration.js +108 -0
  25. package/dist/core/profile-sync-drift.d.ts +38 -0
  26. package/dist/core/profile-sync-drift.js +200 -0
  27. package/dist/core/profiles.d.ts +26 -0
  28. package/dist/core/profiles.js +40 -0
  29. package/dist/core/shared/index.d.ts +1 -1
  30. package/dist/core/shared/index.js +1 -1
  31. package/dist/core/shared/skill-generation.d.ts +16 -8
  32. package/dist/core/shared/skill-generation.js +42 -22
  33. package/dist/core/shared/tool-detection.d.ts +8 -3
  34. package/dist/core/shared/tool-detection.js +18 -0
  35. package/dist/core/templates/index.d.ts +1 -1
  36. package/dist/core/templates/index.js +2 -2
  37. package/dist/core/templates/skill-templates.d.ts +15 -118
  38. package/dist/core/templates/skill-templates.js +14 -3424
  39. package/dist/core/templates/types.d.ts +19 -0
  40. package/dist/core/templates/types.js +5 -0
  41. package/dist/core/templates/workflows/apply-change.d.ts +10 -0
  42. package/dist/core/templates/workflows/apply-change.js +308 -0
  43. package/dist/core/templates/workflows/archive-change.d.ts +10 -0
  44. package/dist/core/templates/workflows/archive-change.js +271 -0
  45. package/dist/core/templates/workflows/bulk-archive-change.d.ts +10 -0
  46. package/dist/core/templates/workflows/bulk-archive-change.js +488 -0
  47. package/dist/core/templates/workflows/continue-change.d.ts +10 -0
  48. package/dist/core/templates/workflows/continue-change.js +232 -0
  49. package/dist/core/templates/workflows/explore.d.ts +10 -0
  50. package/dist/core/templates/workflows/explore.js +461 -0
  51. package/dist/core/templates/workflows/feedback.d.ts +9 -0
  52. package/dist/core/templates/workflows/feedback.js +108 -0
  53. package/dist/core/templates/workflows/ff-change.d.ts +10 -0
  54. package/dist/core/templates/workflows/ff-change.js +198 -0
  55. package/dist/core/templates/workflows/new-change.d.ts +10 -0
  56. package/dist/core/templates/workflows/new-change.js +143 -0
  57. package/dist/core/templates/workflows/onboard.d.ts +10 -0
  58. package/dist/core/templates/workflows/onboard.js +565 -0
  59. package/dist/core/templates/workflows/propose.d.ts +10 -0
  60. package/dist/core/templates/workflows/propose.js +216 -0
  61. package/dist/core/templates/workflows/sync-specs.d.ts +10 -0
  62. package/dist/core/templates/workflows/sync-specs.js +272 -0
  63. package/dist/core/templates/workflows/verify-change.d.ts +10 -0
  64. package/dist/core/templates/workflows/verify-change.js +332 -0
  65. package/dist/core/update.d.ts +36 -1
  66. package/dist/core/update.js +292 -61
  67. package/dist/prompts/searchable-multi-select.d.ts +3 -2
  68. package/dist/prompts/searchable-multi-select.js +22 -12
  69. package/dist/utils/command-references.d.ts +18 -0
  70. package/dist/utils/command-references.js +20 -0
  71. package/dist/utils/index.d.ts +1 -0
  72. package/dist/utils/index.js +2 -0
  73. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -70,6 +70,7 @@ program
70
70
  .description('Initialize OpenSpec in your project')
71
71
  .option('--tools <tools>', toolsOptionDescription)
72
72
  .option('--force', 'Auto-cleanup legacy files without prompting')
73
+ .option('--profile <profile>', 'Override global config profile (core or custom)')
73
74
  .action(async (targetPath = '.', options) => {
74
75
  try {
75
76
  // Validate that the path is a valid directory
@@ -96,6 +97,7 @@ program
96
97
  const initCommand = new InitCommand({
97
98
  tools: options?.tools,
98
99
  force: options?.force,
100
+ profile: options?.profile,
99
101
  });
100
102
  await initCommand.execute(targetPath);
101
103
  }
@@ -1,8 +1,36 @@
1
1
  import { Command } from 'commander';
2
+ import { GlobalConfig } from '../core/global-config.js';
3
+ import type { Profile, Delivery } from '../core/global-config.js';
4
+ interface ProfileState {
5
+ profile: Profile;
6
+ delivery: Delivery;
7
+ workflows: string[];
8
+ }
9
+ interface ProfileStateDiff {
10
+ hasChanges: boolean;
11
+ lines: string[];
12
+ }
13
+ /**
14
+ * Resolve the effective current profile state from global config defaults.
15
+ */
16
+ export declare function resolveCurrentProfileState(config: GlobalConfig): ProfileState;
17
+ /**
18
+ * Derive profile type from selected workflows.
19
+ */
20
+ export declare function deriveProfileFromWorkflowSelection(selectedWorkflows: string[]): Profile;
21
+ /**
22
+ * Format a compact workflow summary for the profile header.
23
+ */
24
+ export declare function formatWorkflowSummary(workflows: readonly string[], profile: Profile): string;
25
+ /**
26
+ * Build a user-facing diff summary between two profile states.
27
+ */
28
+ export declare function diffProfileState(before: ProfileState, after: ProfileState): ProfileStateDiff;
2
29
  /**
3
30
  * Register the config command and all its subcommands.
4
31
  *
5
32
  * @param program - The Commander program instance
6
33
  */
7
34
  export declare function registerConfigCommand(program: Command): void;
35
+ export {};
8
36
  //# sourceMappingURL=config.d.ts.map
@@ -1,7 +1,147 @@
1
- import { spawn } from 'node:child_process';
1
+ import { spawn, execSync } from 'node:child_process';
2
2
  import * as fs from 'node:fs';
3
+ import * as path from 'node:path';
3
4
  import { getGlobalConfigPath, getGlobalConfig, saveGlobalConfig, } from '../core/global-config.js';
4
5
  import { getNestedValue, setNestedValue, deleteNestedValue, coerceValue, formatValueYaml, validateConfigKeyPath, validateConfig, DEFAULT_CONFIG, } from '../core/config-schema.js';
6
+ import { CORE_WORKFLOWS, ALL_WORKFLOWS, getProfileWorkflows } from '../core/profiles.js';
7
+ import { OPENSPEC_DIR_NAME } from '../core/config.js';
8
+ import { hasProjectConfigDrift } from '../core/profile-sync-drift.js';
9
+ const WORKFLOW_PROMPT_META = {
10
+ propose: {
11
+ name: 'Propose change',
12
+ description: 'Create proposal, design, and tasks from a request',
13
+ },
14
+ explore: {
15
+ name: 'Explore ideas',
16
+ description: 'Investigate a problem before implementation',
17
+ },
18
+ new: {
19
+ name: 'New change',
20
+ description: 'Create a new change scaffold quickly',
21
+ },
22
+ continue: {
23
+ name: 'Continue change',
24
+ description: 'Resume work on an existing change',
25
+ },
26
+ apply: {
27
+ name: 'Apply tasks',
28
+ description: 'Implement tasks from the current change',
29
+ },
30
+ ff: {
31
+ name: 'Fast-forward',
32
+ description: 'Run a faster implementation workflow',
33
+ },
34
+ sync: {
35
+ name: 'Sync specs',
36
+ description: 'Sync change artifacts with specs',
37
+ },
38
+ archive: {
39
+ name: 'Archive change',
40
+ description: 'Finalize and archive a completed change',
41
+ },
42
+ 'bulk-archive': {
43
+ name: 'Bulk archive',
44
+ description: 'Archive multiple completed changes together',
45
+ },
46
+ verify: {
47
+ name: 'Verify change',
48
+ description: 'Run verification checks against a change',
49
+ },
50
+ onboard: {
51
+ name: 'Onboard',
52
+ description: 'Guided onboarding flow for OpenSpec',
53
+ },
54
+ };
55
+ function isPromptCancellationError(error) {
56
+ return (error instanceof Error &&
57
+ (error.name === 'ExitPromptError' || error.message.includes('force closed the prompt with SIGINT')));
58
+ }
59
+ /**
60
+ * Resolve the effective current profile state from global config defaults.
61
+ */
62
+ export function resolveCurrentProfileState(config) {
63
+ const profile = config.profile || 'core';
64
+ const delivery = config.delivery || 'both';
65
+ const workflows = [
66
+ ...getProfileWorkflows(profile, config.workflows ? [...config.workflows] : undefined),
67
+ ];
68
+ return { profile, delivery, workflows };
69
+ }
70
+ /**
71
+ * Derive profile type from selected workflows.
72
+ */
73
+ export function deriveProfileFromWorkflowSelection(selectedWorkflows) {
74
+ const isCoreMatch = selectedWorkflows.length === CORE_WORKFLOWS.length &&
75
+ CORE_WORKFLOWS.every((w) => selectedWorkflows.includes(w));
76
+ return isCoreMatch ? 'core' : 'custom';
77
+ }
78
+ /**
79
+ * Format a compact workflow summary for the profile header.
80
+ */
81
+ export function formatWorkflowSummary(workflows, profile) {
82
+ return `${workflows.length} selected (${profile})`;
83
+ }
84
+ function stableWorkflowOrder(workflows) {
85
+ const seen = new Set();
86
+ const ordered = [];
87
+ for (const workflow of ALL_WORKFLOWS) {
88
+ if (workflows.includes(workflow) && !seen.has(workflow)) {
89
+ ordered.push(workflow);
90
+ seen.add(workflow);
91
+ }
92
+ }
93
+ const extras = workflows.filter((w) => !ALL_WORKFLOWS.includes(w));
94
+ extras.sort();
95
+ for (const extra of extras) {
96
+ if (!seen.has(extra)) {
97
+ ordered.push(extra);
98
+ seen.add(extra);
99
+ }
100
+ }
101
+ return ordered;
102
+ }
103
+ /**
104
+ * Build a user-facing diff summary between two profile states.
105
+ */
106
+ export function diffProfileState(before, after) {
107
+ const lines = [];
108
+ if (before.delivery !== after.delivery) {
109
+ lines.push(`delivery: ${before.delivery} -> ${after.delivery}`);
110
+ }
111
+ if (before.profile !== after.profile) {
112
+ lines.push(`profile: ${before.profile} -> ${after.profile}`);
113
+ }
114
+ const beforeOrdered = stableWorkflowOrder(before.workflows);
115
+ const afterOrdered = stableWorkflowOrder(after.workflows);
116
+ const beforeSet = new Set(beforeOrdered);
117
+ const afterSet = new Set(afterOrdered);
118
+ const added = afterOrdered.filter((w) => !beforeSet.has(w));
119
+ const removed = beforeOrdered.filter((w) => !afterSet.has(w));
120
+ if (added.length > 0 || removed.length > 0) {
121
+ const tokens = [];
122
+ if (added.length > 0) {
123
+ tokens.push(`added ${added.join(', ')}`);
124
+ }
125
+ if (removed.length > 0) {
126
+ tokens.push(`removed ${removed.join(', ')}`);
127
+ }
128
+ lines.push(`workflows: ${tokens.join('; ')}`);
129
+ }
130
+ return {
131
+ hasChanges: lines.length > 0,
132
+ lines,
133
+ };
134
+ }
135
+ function maybeWarnConfigDrift(projectDir, state, colorize) {
136
+ const openspecDir = path.join(projectDir, OPENSPEC_DIR_NAME);
137
+ if (!fs.existsSync(openspecDir)) {
138
+ return;
139
+ }
140
+ if (!hasProjectConfigDrift(projectDir, state.workflows, state.delivery)) {
141
+ return;
142
+ }
143
+ console.log(colorize('Warning: Global config is not applied to this project. Run `openspec update` to sync.'));
144
+ }
5
145
  /**
6
146
  * Register the config command and all its subcommands.
7
147
  *
@@ -37,7 +177,33 @@ export function registerConfigCommand(program) {
37
177
  console.log(JSON.stringify(config, null, 2));
38
178
  }
39
179
  else {
180
+ // Read raw config to determine which values are explicit vs defaults
181
+ const configPath = getGlobalConfigPath();
182
+ let rawConfig = {};
183
+ try {
184
+ if (fs.existsSync(configPath)) {
185
+ rawConfig = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
186
+ }
187
+ }
188
+ catch {
189
+ // If reading fails, treat all as defaults
190
+ }
40
191
  console.log(formatValueYaml(config));
192
+ // Annotate profile settings
193
+ const profileSource = rawConfig.profile !== undefined ? '(explicit)' : '(default)';
194
+ const deliverySource = rawConfig.delivery !== undefined ? '(explicit)' : '(default)';
195
+ console.log(`\nProfile settings:`);
196
+ console.log(` profile: ${config.profile} ${profileSource}`);
197
+ console.log(` delivery: ${config.delivery} ${deliverySource}`);
198
+ if (config.profile === 'core') {
199
+ console.log(` workflows: ${CORE_WORKFLOWS.join(', ')} (from core profile)`);
200
+ }
201
+ else if (config.workflows && config.workflows.length > 0) {
202
+ console.log(` workflows: ${config.workflows.join(', ')} (explicit)`);
203
+ }
204
+ else {
205
+ console.log(` workflows: (none)`);
206
+ }
41
207
  }
42
208
  });
43
209
  // config get
@@ -123,10 +289,21 @@ export function registerConfigCommand(program) {
123
289
  }
124
290
  if (!options.yes) {
125
291
  const { confirm } = await import('@inquirer/prompts');
126
- const confirmed = await confirm({
127
- message: 'Reset all configuration to defaults?',
128
- default: false,
129
- });
292
+ let confirmed;
293
+ try {
294
+ confirmed = await confirm({
295
+ message: 'Reset all configuration to defaults?',
296
+ default: false,
297
+ });
298
+ }
299
+ catch (error) {
300
+ if (isPromptCancellationError(error)) {
301
+ console.log('Reset cancelled.');
302
+ process.exitCode = 130;
303
+ return;
304
+ }
305
+ throw error;
306
+ }
130
307
  if (!confirmed) {
131
308
  console.log('Reset cancelled.');
132
309
  return;
@@ -194,5 +371,182 @@ export function registerConfigCommand(program) {
194
371
  process.exitCode = 1;
195
372
  }
196
373
  });
374
+ // config profile [preset]
375
+ configCmd
376
+ .command('profile [preset]')
377
+ .description('Configure workflow profile (interactive picker or preset shortcut)')
378
+ .action(async (preset) => {
379
+ // Preset shortcut: `openspec config profile core`
380
+ if (preset === 'core') {
381
+ const config = getGlobalConfig();
382
+ config.profile = 'core';
383
+ config.workflows = [...CORE_WORKFLOWS];
384
+ // Preserve delivery setting
385
+ saveGlobalConfig(config);
386
+ console.log('Config updated. Run `openspec update` in your projects to apply.');
387
+ return;
388
+ }
389
+ if (preset) {
390
+ console.error(`Error: Unknown profile preset "${preset}". Available presets: core`);
391
+ process.exitCode = 1;
392
+ return;
393
+ }
394
+ // Non-interactive check
395
+ if (!process.stdout.isTTY) {
396
+ console.error('Interactive mode required. Use `openspec config profile core` or set config via environment/flags.');
397
+ process.exitCode = 1;
398
+ return;
399
+ }
400
+ // Interactive picker
401
+ const { select, checkbox, confirm } = await import('@inquirer/prompts');
402
+ const chalk = (await import('chalk')).default;
403
+ try {
404
+ const config = getGlobalConfig();
405
+ const currentState = resolveCurrentProfileState(config);
406
+ console.log(chalk.bold('\nCurrent profile settings'));
407
+ console.log(` Delivery: ${currentState.delivery}`);
408
+ console.log(` Workflows: ${formatWorkflowSummary(currentState.workflows, currentState.profile)}`);
409
+ console.log(chalk.dim(' Delivery = where workflows are installed (skills, commands, or both)'));
410
+ console.log(chalk.dim(' Workflows = which actions are available (propose, explore, apply, etc.)'));
411
+ console.log();
412
+ const action = await select({
413
+ message: 'What do you want to configure?',
414
+ choices: [
415
+ {
416
+ value: 'both',
417
+ name: 'Delivery and workflows',
418
+ description: 'Update install mode and available actions together',
419
+ },
420
+ {
421
+ value: 'delivery',
422
+ name: 'Delivery only',
423
+ description: 'Change where workflows are installed',
424
+ },
425
+ {
426
+ value: 'workflows',
427
+ name: 'Workflows only',
428
+ description: 'Change which workflow actions are available',
429
+ },
430
+ {
431
+ value: 'keep',
432
+ name: 'Keep current settings (exit)',
433
+ description: 'Leave configuration unchanged and exit',
434
+ },
435
+ ],
436
+ });
437
+ if (action === 'keep') {
438
+ console.log('No config changes.');
439
+ maybeWarnConfigDrift(process.cwd(), currentState, chalk.yellow);
440
+ return;
441
+ }
442
+ const nextState = {
443
+ profile: currentState.profile,
444
+ delivery: currentState.delivery,
445
+ workflows: [...currentState.workflows],
446
+ };
447
+ if (action === 'both' || action === 'delivery') {
448
+ const deliveryChoices = [
449
+ {
450
+ value: 'both',
451
+ name: 'Both (skills + commands)',
452
+ description: 'Install workflows as both skills and slash commands',
453
+ },
454
+ {
455
+ value: 'skills',
456
+ name: 'Skills only',
457
+ description: 'Install workflows only as skills',
458
+ },
459
+ {
460
+ value: 'commands',
461
+ name: 'Commands only',
462
+ description: 'Install workflows only as slash commands',
463
+ },
464
+ ];
465
+ for (const choice of deliveryChoices) {
466
+ if (choice.value === currentState.delivery) {
467
+ choice.name += ' [current]';
468
+ }
469
+ }
470
+ nextState.delivery = await select({
471
+ message: 'Delivery mode (how workflows are installed):',
472
+ choices: deliveryChoices,
473
+ default: currentState.delivery,
474
+ });
475
+ }
476
+ if (action === 'both' || action === 'workflows') {
477
+ const formatWorkflowChoice = (workflow) => {
478
+ const metadata = WORKFLOW_PROMPT_META[workflow] ?? {
479
+ name: workflow,
480
+ description: `Workflow: ${workflow}`,
481
+ };
482
+ return {
483
+ value: workflow,
484
+ name: metadata.name,
485
+ description: metadata.description,
486
+ short: metadata.name,
487
+ checked: currentState.workflows.includes(workflow),
488
+ };
489
+ };
490
+ const selectedWorkflows = await checkbox({
491
+ message: 'Select workflows to make available:',
492
+ instructions: 'Space to toggle, Enter to confirm',
493
+ pageSize: ALL_WORKFLOWS.length,
494
+ theme: {
495
+ icon: {
496
+ checked: '[x]',
497
+ unchecked: '[ ]',
498
+ },
499
+ },
500
+ choices: ALL_WORKFLOWS.map(formatWorkflowChoice),
501
+ });
502
+ nextState.workflows = selectedWorkflows;
503
+ nextState.profile = deriveProfileFromWorkflowSelection(selectedWorkflows);
504
+ }
505
+ const diff = diffProfileState(currentState, nextState);
506
+ if (!diff.hasChanges) {
507
+ console.log('No config changes.');
508
+ maybeWarnConfigDrift(process.cwd(), nextState, chalk.yellow);
509
+ return;
510
+ }
511
+ console.log(chalk.bold('\nConfig changes:'));
512
+ for (const line of diff.lines) {
513
+ console.log(` ${line}`);
514
+ }
515
+ console.log();
516
+ config.profile = nextState.profile;
517
+ config.delivery = nextState.delivery;
518
+ config.workflows = nextState.workflows;
519
+ saveGlobalConfig(config);
520
+ // Check if inside an OpenSpec project
521
+ const projectDir = process.cwd();
522
+ const openspecDir = path.join(projectDir, OPENSPEC_DIR_NAME);
523
+ if (fs.existsSync(openspecDir)) {
524
+ const applyNow = await confirm({
525
+ message: 'Apply changes to this project now?',
526
+ default: true,
527
+ });
528
+ if (applyNow) {
529
+ try {
530
+ execSync('npx openspec update', { stdio: 'inherit', cwd: projectDir });
531
+ console.log('Run `openspec update` in your other projects to apply.');
532
+ }
533
+ catch {
534
+ console.error('`openspec update` failed. Please run it manually to apply the profile changes.');
535
+ process.exitCode = 1;
536
+ }
537
+ return;
538
+ }
539
+ }
540
+ console.log('Config updated. Run `openspec update` in your projects to apply.');
541
+ }
542
+ catch (error) {
543
+ if (isPromptCancellationError(error)) {
544
+ console.log('Config profile cancelled.');
545
+ process.exitCode = 130;
546
+ return;
547
+ }
548
+ throw error;
549
+ }
550
+ });
197
551
  }
198
552
  //# sourceMappingURL=config.js.map
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Available Tools Detection
3
+ *
4
+ * Detects which AI tools are available in a project by scanning
5
+ * for their configuration directories.
6
+ */
7
+ import { type AIToolOption } from './config.js';
8
+ /**
9
+ * Scans the project path for AI tool configuration directories and returns
10
+ * the tools that are present.
11
+ *
12
+ * Checks for each tool's `skillsDir` (e.g., `.claude/`, `.cursor/`) at the
13
+ * project root. Only tools with a `skillsDir` property are considered.
14
+ */
15
+ export declare function getAvailableTools(projectPath: string): AIToolOption[];
16
+ //# sourceMappingURL=available-tools.d.ts.map
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Available Tools Detection
3
+ *
4
+ * Detects which AI tools are available in a project by scanning
5
+ * for their configuration directories.
6
+ */
7
+ import path from 'path';
8
+ import * as fs from 'fs';
9
+ import { AI_TOOLS } from './config.js';
10
+ /**
11
+ * Scans the project path for AI tool configuration directories and returns
12
+ * the tools that are present.
13
+ *
14
+ * Checks for each tool's `skillsDir` (e.g., `.claude/`, `.cursor/`) at the
15
+ * project root. Only tools with a `skillsDir` property are considered.
16
+ */
17
+ export function getAvailableTools(projectPath) {
18
+ return AI_TOOLS.filter((tool) => {
19
+ if (!tool.skillsDir)
20
+ return false;
21
+ const dirPath = path.join(projectPath, tool.skillsDir);
22
+ try {
23
+ return fs.statSync(dirPath).isDirectory();
24
+ }
25
+ catch {
26
+ return false;
27
+ }
28
+ });
29
+ }
30
+ //# sourceMappingURL=available-tools.js.map
@@ -19,7 +19,9 @@ export { geminiAdapter } from './gemini.js';
19
19
  export { githubCopilotAdapter } from './github-copilot.js';
20
20
  export { iflowAdapter } from './iflow.js';
21
21
  export { kilocodeAdapter } from './kilocode.js';
22
+ export { kiroAdapter } from './kiro.js';
22
23
  export { opencodeAdapter } from './opencode.js';
24
+ export { piAdapter } from './pi.js';
23
25
  export { qoderAdapter } from './qoder.js';
24
26
  export { qwenAdapter } from './qwen.js';
25
27
  export { roocodeAdapter } from './roocode.js';
@@ -19,7 +19,9 @@ export { geminiAdapter } from './gemini.js';
19
19
  export { githubCopilotAdapter } from './github-copilot.js';
20
20
  export { iflowAdapter } from './iflow.js';
21
21
  export { kilocodeAdapter } from './kilocode.js';
22
+ export { kiroAdapter } from './kiro.js';
22
23
  export { opencodeAdapter } from './opencode.js';
24
+ export { piAdapter } from './pi.js';
23
25
  export { qoderAdapter } from './qoder.js';
24
26
  export { qwenAdapter } from './qwen.js';
25
27
  export { roocodeAdapter } from './roocode.js';
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Kiro Command Adapter
3
+ *
4
+ * Formats commands for Kiro following its .prompt.md specification.
5
+ */
6
+ import type { ToolCommandAdapter } from '../types.js';
7
+ /**
8
+ * Kiro adapter for command generation.
9
+ * File path: .kiro/prompts/opsx-<id>.prompt.md
10
+ * Frontmatter: description
11
+ */
12
+ export declare const kiroAdapter: ToolCommandAdapter;
13
+ //# sourceMappingURL=kiro.d.ts.map
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Kiro Command Adapter
3
+ *
4
+ * Formats commands for Kiro following its .prompt.md specification.
5
+ */
6
+ import path from 'path';
7
+ /**
8
+ * Kiro adapter for command generation.
9
+ * File path: .kiro/prompts/opsx-<id>.prompt.md
10
+ * Frontmatter: description
11
+ */
12
+ export const kiroAdapter = {
13
+ toolId: 'kiro',
14
+ getFilePath(commandId) {
15
+ return path.join('.kiro', 'prompts', `opsx-${commandId}.prompt.md`);
16
+ },
17
+ formatFile(content) {
18
+ return `---
19
+ description: ${content.description}
20
+ ---
21
+
22
+ ${content.body}
23
+ `;
24
+ },
25
+ };
26
+ //# sourceMappingURL=kiro.js.map
@@ -4,6 +4,7 @@
4
4
  * Formats commands for OpenCode following its frontmatter specification.
5
5
  */
6
6
  import path from 'path';
7
+ import { transformToHyphenCommands } from '../../../utils/command-references.js';
7
8
  /**
8
9
  * OpenCode adapter for command generation.
9
10
  * File path: .opencode/command/opsx-<id>.md
@@ -15,11 +16,13 @@ export const opencodeAdapter = {
15
16
  return path.join('.opencode', 'command', `opsx-${commandId}.md`);
16
17
  },
17
18
  formatFile(content) {
19
+ // Transform command references from colon to hyphen format for OpenCode
20
+ const transformedBody = transformToHyphenCommands(content.body);
18
21
  return `---
19
22
  description: ${content.description}
20
23
  ---
21
24
 
22
- ${content.body}
25
+ ${transformedBody}
23
26
  `;
24
27
  },
25
28
  };
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Pi Command Adapter
3
+ *
4
+ * Formats commands for Pi (pi.dev) following its prompt template specification.
5
+ * Pi prompt templates live in .pi/prompts/*.md with description frontmatter.
6
+ */
7
+ import type { ToolCommandAdapter } from '../types.js';
8
+ /**
9
+ * Pi adapter for prompt template generation.
10
+ * File path: .pi/prompts/opsx-<id>.md
11
+ * Frontmatter: description
12
+ */
13
+ export declare const piAdapter: ToolCommandAdapter;
14
+ //# sourceMappingURL=pi.d.ts.map
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Pi Command Adapter
3
+ *
4
+ * Formats commands for Pi (pi.dev) following its prompt template specification.
5
+ * Pi prompt templates live in .pi/prompts/*.md with description frontmatter.
6
+ */
7
+ import path from 'path';
8
+ /**
9
+ * Escapes a string value for safe YAML output.
10
+ * Quotes the string if it contains special YAML characters.
11
+ */
12
+ function escapeYamlValue(value) {
13
+ // Check if value needs quoting (contains special YAML characters or starts/ends with whitespace)
14
+ const needsQuoting = /[:\n\r#{}[\],&*!|>'"%@`]|^\s|\s$/.test(value);
15
+ if (needsQuoting) {
16
+ // Use double quotes and escape internal double quotes and backslashes
17
+ const escaped = value.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n');
18
+ return `"${escaped}"`;
19
+ }
20
+ return value;
21
+ }
22
+ /**
23
+ * Pi adapter for prompt template generation.
24
+ * File path: .pi/prompts/opsx-<id>.md
25
+ * Frontmatter: description
26
+ */
27
+ export const piAdapter = {
28
+ toolId: 'pi',
29
+ getFilePath(commandId) {
30
+ return path.join('.pi', 'prompts', `opsx-${commandId}.md`);
31
+ },
32
+ formatFile(content) {
33
+ return `---
34
+ description: ${escapeYamlValue(content.description)}
35
+ ---
36
+
37
+ ${content.body}
38
+ `;
39
+ },
40
+ };
41
+ //# sourceMappingURL=pi.js.map
@@ -20,7 +20,9 @@ import { geminiAdapter } from './adapters/gemini.js';
20
20
  import { githubCopilotAdapter } from './adapters/github-copilot.js';
21
21
  import { iflowAdapter } from './adapters/iflow.js';
22
22
  import { kilocodeAdapter } from './adapters/kilocode.js';
23
+ import { kiroAdapter } from './adapters/kiro.js';
23
24
  import { opencodeAdapter } from './adapters/opencode.js';
25
+ import { piAdapter } from './adapters/pi.js';
24
26
  import { qoderAdapter } from './adapters/qoder.js';
25
27
  import { qwenAdapter } from './adapters/qwen.js';
26
28
  import { roocodeAdapter } from './adapters/roocode.js';
@@ -48,7 +50,9 @@ export class CommandAdapterRegistry {
48
50
  CommandAdapterRegistry.register(githubCopilotAdapter);
49
51
  CommandAdapterRegistry.register(iflowAdapter);
50
52
  CommandAdapterRegistry.register(kilocodeAdapter);
53
+ CommandAdapterRegistry.register(kiroAdapter);
51
54
  CommandAdapterRegistry.register(opencodeAdapter);
55
+ CommandAdapterRegistry.register(piAdapter);
52
56
  CommandAdapterRegistry.register(qoderAdapter);
53
57
  CommandAdapterRegistry.register(qwenAdapter);
54
58
  CommandAdapterRegistry.register(roocodeAdapter);
@@ -374,6 +374,11 @@ export const COMMAND_REGISTRY = [
374
374
  description: 'Open config in $EDITOR',
375
375
  flags: [],
376
376
  },
377
+ {
378
+ name: 'profile',
379
+ description: 'Configure workflow profile (interactive picker or preset shortcut)',
380
+ flags: [],
381
+ },
377
382
  ],
378
383
  },
379
384
  {