@exodus/openspec 1.2.1 → 1.2.2

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.
@@ -29,6 +29,13 @@ 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
+ lines.push('# CLI command (optional)');
34
+ lines.push('# Override the `openspec` command used in generated skill files.');
35
+ lines.push('# Useful when openspec is only installed at a monorepo root.');
36
+ lines.push('# Example:');
37
+ lines.push('# cli: pnpm exec openspec');
38
+ lines.push('# cli: npx @exodus/openspec');
32
39
  return lines.join('\n') + '\n';
33
40
  }
34
41
  //# sourceMappingURL=config-prompts.js.map
@@ -6,8 +6,8 @@ import { z } from 'zod';
6
6
  export declare const GlobalConfigSchema: z.ZodObject<{
7
7
  featureFlags: z.ZodDefault<z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodBoolean>>>;
8
8
  profile: z.ZodDefault<z.ZodOptional<z.ZodEnum<{
9
- core: "core";
10
9
  custom: "custom";
10
+ core: "core";
11
11
  }>>>;
12
12
  delivery: z.ZodDefault<z.ZodOptional<z.ZodEnum<{
13
13
  commands: "commands";
package/dist/core/init.js CHANGED
@@ -10,7 +10,8 @@ import ora from 'ora';
10
10
  import * as fs from 'fs';
11
11
  import { createRequire } from 'module';
12
12
  import { FileSystemUtils } from '../utils/file-system.js';
13
- import { transformToHyphenCommands } from '../utils/command-references.js';
13
+ import { transformToHyphenCommands, createCliTransformer } from '../utils/command-references.js';
14
+ import { readProjectConfig } from './project-config.js';
14
15
  import { AI_TOOLS, OPENSPEC_DIR_NAME, } from './config.js';
15
16
  import { PALETTE } from './styles/palette.js';
16
17
  import { isInteractive } from '../utils/interactive.js';
@@ -364,6 +365,9 @@ export class InitCommand {
364
365
  const profile = this.resolveProfileOverride() ?? globalConfig.profile ?? 'core';
365
366
  const delivery = globalConfig.delivery ?? 'both';
366
367
  const workflows = getProfileWorkflows(profile, globalConfig.workflows);
368
+ // Read project config for cli override
369
+ const projectConfig = readProjectConfig(projectPath);
370
+ const cliTransformer = createCliTransformer(projectConfig?.cli);
367
371
  // Get skill and command templates filtered by profile workflows
368
372
  const shouldGenerateSkills = delivery !== 'commands';
369
373
  const shouldGenerateCommands = delivery !== 'skills';
@@ -382,8 +386,11 @@ export class InitCommand {
382
386
  const skillDir = path.join(skillsDir, dirName);
383
387
  const skillFile = path.join(skillDir, 'SKILL.md');
384
388
  // Generate SKILL.md content with YAML frontmatter including generatedBy
385
- // Use hyphen-based command references for OpenCode
386
- const transformer = tool.value === 'opencode' ? transformToHyphenCommands : undefined;
389
+ // Use hyphen-based command references for OpenCode; apply cli override if set
390
+ const toolTransformer = tool.value === 'opencode' ? transformToHyphenCommands : undefined;
391
+ const transformer = cliTransformer && toolTransformer
392
+ ? (text) => cliTransformer(toolTransformer(text))
393
+ : cliTransformer ?? toolTransformer;
387
394
  const skillContent = generateSkillContent(template, OPENSPEC_VERSION, transformer);
388
395
  // Write the skill file
389
396
  await FileSystemUtils.writeFile(skillFile, skillContent);
@@ -16,6 +16,7 @@ export declare const ProjectConfigSchema: z.ZodObject<{
16
16
  schema: z.ZodString;
17
17
  context: z.ZodOptional<z.ZodString>;
18
18
  rules: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>>;
19
+ cli: z.ZodOptional<z.ZodString>;
19
20
  }, z.core.$strip>;
20
21
  export type ProjectConfig = z.infer<typeof ProjectConfigSchema>;
21
22
  /**
@@ -34,6 +34,14 @@ export const ProjectConfigSchema = z.object({
34
34
  )
35
35
  .optional()
36
36
  .describe('Per-artifact rules, keyed by artifact ID'),
37
+ // Optional: CLI command to use for openspec invocations in generated skills
38
+ // Useful when openspec is only installed at a monorepo root (e.g., pnpm workspaces)
39
+ // Example: "pnpm exec openspec" or "npx @exodus/openspec"
40
+ cli: z
41
+ .string()
42
+ .min(1)
43
+ .optional()
44
+ .describe('CLI command used to invoke openspec in generated skill files'),
37
45
  });
38
46
  const MAX_CONTEXT_SIZE = 50 * 1024; // 50KB hard limit
39
47
  /**
@@ -131,6 +139,17 @@ export function readProjectConfig(projectRoot) {
131
139
  console.warn(`Invalid 'rules' field in config (must be object)`);
132
140
  }
133
141
  }
142
+ // Parse cli field using Zod
143
+ if (raw.cli !== undefined) {
144
+ const cliField = z.string().min(1);
145
+ const cliResult = cliField.safeParse(raw.cli);
146
+ if (cliResult.success) {
147
+ config.cli = cliResult.data;
148
+ }
149
+ else {
150
+ console.warn(`Invalid 'cli' field in config (must be non-empty string)`);
151
+ }
152
+ }
134
153
  // Return partial config even if some fields failed
135
154
  return Object.keys(config).length > 0 ? config : null;
136
155
  }
@@ -10,7 +10,8 @@ import ora from 'ora';
10
10
  import * as fs from 'fs';
11
11
  import { createRequire } from 'module';
12
12
  import { FileSystemUtils } from '../utils/file-system.js';
13
- import { transformToHyphenCommands } from '../utils/command-references.js';
13
+ import { transformToHyphenCommands, createCliTransformer } from '../utils/command-references.js';
14
+ import { readProjectConfig } from './project-config.js';
14
15
  import { AI_TOOLS, OPENSPEC_DIR_NAME } from './config.js';
15
16
  import { generateCommands, CommandAdapterRegistry, } from './command-generation/index.js';
16
17
  import { getToolVersionStatus, getSkillTemplates, getCommandContents, generateSkillContent, getToolsWithSkillsDir, } from './shared/index.js';
@@ -106,6 +107,8 @@ export class UpdateCommand {
106
107
  }
107
108
  console.log();
108
109
  // 9. Determine what to generate based on delivery
110
+ const projectConfig = readProjectConfig(resolvedProjectPath);
111
+ const cliTransformer = createCliTransformer(projectConfig?.cli);
109
112
  const skillTemplates = shouldGenerateSkills ? getSkillTemplates(desiredWorkflows) : [];
110
113
  const commandContents = shouldGenerateCommands ? getCommandContents(desiredWorkflows) : [];
111
114
  // 10. Update tools (all if force, otherwise only those needing update)
@@ -128,8 +131,11 @@ export class UpdateCommand {
128
131
  for (const { template, dirName } of skillTemplates) {
129
132
  const skillDir = path.join(skillsDir, dirName);
130
133
  const skillFile = path.join(skillDir, 'SKILL.md');
131
- // Use hyphen-based command references for OpenCode
132
- const transformer = tool.value === 'opencode' ? transformToHyphenCommands : undefined;
134
+ // Use hyphen-based command references for OpenCode; apply cli override if set
135
+ const toolTransformer = tool.value === 'opencode' ? transformToHyphenCommands : undefined;
136
+ const transformer = cliTransformer && toolTransformer
137
+ ? (text) => cliTransformer(toolTransformer(text))
138
+ : cliTransformer ?? toolTransformer;
133
139
  const skillContent = generateSkillContent(template, OPENSPEC_VERSION, transformer);
134
140
  await FileSystemUtils.writeFile(skillFile, skillContent);
135
141
  }
@@ -489,6 +495,8 @@ export class UpdateCommand {
489
495
  const newlyConfigured = [];
490
496
  const shouldGenerateSkills = delivery !== 'commands';
491
497
  const shouldGenerateCommands = delivery !== 'skills';
498
+ const legacyProjectConfig = readProjectConfig(projectPath);
499
+ const legacyCliTransformer = createCliTransformer(legacyProjectConfig?.cli);
492
500
  const skillTemplates = shouldGenerateSkills ? getSkillTemplates(desiredWorkflows) : [];
493
501
  const commandContents = shouldGenerateCommands ? getCommandContents(desiredWorkflows) : [];
494
502
  for (const toolId of selectedTools) {
@@ -503,8 +511,11 @@ export class UpdateCommand {
503
511
  for (const { template, dirName } of skillTemplates) {
504
512
  const skillDir = path.join(skillsDir, dirName);
505
513
  const skillFile = path.join(skillDir, 'SKILL.md');
506
- // Use hyphen-based command references for OpenCode
507
- const transformer = tool.value === 'opencode' ? transformToHyphenCommands : undefined;
514
+ // Use hyphen-based command references for OpenCode; apply cli override if set
515
+ const toolTransformer = tool.value === 'opencode' ? transformToHyphenCommands : undefined;
516
+ const transformer = legacyCliTransformer && toolTransformer
517
+ ? (text) => legacyCliTransformer(toolTransformer(text))
518
+ : legacyCliTransformer ?? toolTransformer;
508
519
  const skillContent = generateSkillContent(template, OPENSPEC_VERSION, transformer);
509
520
  await FileSystemUtils.writeFile(skillFile, skillContent);
510
521
  }
@@ -15,4 +15,18 @@
15
15
  * transformToHyphenCommands('Use /opsx:apply to implement') // returns 'Use /opsx-apply to implement'
16
16
  */
17
17
  export declare function transformToHyphenCommands(text: string): string;
18
+ /**
19
+ * Creates a transformer that replaces bare `openspec` CLI invocations with a custom command.
20
+ * Only replaces command invocations (backtick-quoted or after `&&`), not path references
21
+ * like `openspec/changes/`.
22
+ *
23
+ * @param cli - The CLI command to use instead of bare `openspec`
24
+ * @returns Transformer function, or undefined if cli is not set
25
+ *
26
+ * @example
27
+ * const t = createCliTransformer('pnpm exec openspec');
28
+ * t('`openspec list --json`') // '`pnpm exec openspec list --json`'
29
+ * t('openspec/changes/foo') // 'openspec/changes/foo' (path unchanged)
30
+ */
31
+ export declare function createCliTransformer(cli: string | undefined): ((text: string) => string) | undefined;
18
32
  //# sourceMappingURL=command-references.d.ts.map
@@ -17,4 +17,24 @@
17
17
  export function transformToHyphenCommands(text) {
18
18
  return text.replace(/\/opsx:/g, '/opsx-');
19
19
  }
20
+ /**
21
+ * Creates a transformer that replaces bare `openspec` CLI invocations with a custom command.
22
+ * Only replaces command invocations (backtick-quoted or after `&&`), not path references
23
+ * like `openspec/changes/`.
24
+ *
25
+ * @param cli - The CLI command to use instead of bare `openspec`
26
+ * @returns Transformer function, or undefined if cli is not set
27
+ *
28
+ * @example
29
+ * const t = createCliTransformer('pnpm exec openspec');
30
+ * t('`openspec list --json`') // '`pnpm exec openspec list --json`'
31
+ * t('openspec/changes/foo') // 'openspec/changes/foo' (path unchanged)
32
+ */
33
+ export function createCliTransformer(cli) {
34
+ if (!cli)
35
+ return undefined;
36
+ return (text) => text
37
+ .replace(/`openspec\s/g, `\`${cli} `)
38
+ .replace(/&& openspec\s/g, `&& ${cli} `);
39
+ }
20
40
  //# sourceMappingURL=command-references.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/openspec",
3
- "version": "1.2.1",
3
+ "version": "1.2.2",
4
4
  "description": "AI-native system for spec-driven development",
5
5
  "keywords": [
6
6
  "openspec",