@exodus/openspec 1.2.1 → 1.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/config-prompts.js +7 -0
- package/dist/core/config-schema.d.ts +1 -1
- package/dist/core/init.js +10 -3
- package/dist/core/project-config.d.ts +1 -0
- package/dist/core/project-config.js +19 -0
- package/dist/core/templates/workflows/apply-change.js +2 -2
- package/dist/core/templates/workflows/archive-change.js +34 -2
- package/dist/core/templates/workflows/continue-change.js +34 -2
- package/dist/core/update.js +16 -5
- package/dist/utils/command-references.d.ts +15 -0
- package/dist/utils/command-references.js +22 -0
- package/package.json +1 -1
|
@@ -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
|
|
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
|
}
|
|
@@ -13,7 +13,7 @@ export function getApplyChangeSkillTemplate() {
|
|
|
13
13
|
If a name is provided, use it. Otherwise:
|
|
14
14
|
- Infer from conversation context if the user mentioned a change
|
|
15
15
|
- Auto-select if only one active change exists
|
|
16
|
-
- If ambiguous, run \`openspec list --json\`
|
|
16
|
+
- If ambiguous, check for a workspace manifest (\`cat openspec/workspace.yaml 2>/dev/null\`). If it exists, run \`(cd <scope.path> && openspec list --json)\` for each scope and \`ls openspec/changes/ 2>/dev/null\` for umbrella changes; otherwise run \`openspec list --json\`. Aggregate all results and use the **AskUserQuestion tool** to let the user select, showing which scope each change belongs to
|
|
17
17
|
|
|
18
18
|
Always announce: "Using change: <name>" and how to override (e.g., \`/opsx:apply <other>\`).
|
|
19
19
|
|
|
@@ -196,7 +196,7 @@ export function getOpsxApplyCommandTemplate() {
|
|
|
196
196
|
If a name is provided, use it. Otherwise:
|
|
197
197
|
- Infer from conversation context if the user mentioned a change
|
|
198
198
|
- Auto-select if only one active change exists
|
|
199
|
-
- If ambiguous, run \`openspec list --json\`
|
|
199
|
+
- If ambiguous, check for a workspace manifest (\`cat openspec/workspace.yaml 2>/dev/null\`). If it exists, run \`(cd <scope.path> && openspec list --json)\` for each scope and \`ls openspec/changes/ 2>/dev/null\` for umbrella changes; otherwise run \`openspec list --json\`. Aggregate all results and use the **AskUserQuestion tool** to let the user select, showing which scope each change belongs to
|
|
200
200
|
|
|
201
201
|
Always announce: "Using change: <name>" and how to override (e.g., \`/opsx:apply <other>\`).
|
|
202
202
|
|
|
@@ -10,8 +10,24 @@ export function getArchiveChangeSkillTemplate() {
|
|
|
10
10
|
|
|
11
11
|
1. **If no change name provided, prompt for selection**
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
Check for a workspace manifest:
|
|
14
|
+
\`\`\`bash
|
|
15
|
+
cat openspec/workspace.yaml 2>/dev/null
|
|
16
|
+
\`\`\`
|
|
17
|
+
|
|
18
|
+
If workspace.yaml exists, list changes across all scopes:
|
|
19
|
+
\`\`\`bash
|
|
20
|
+
(cd <scope.path> && openspec list --json) # for each scope in workspace.yaml
|
|
21
|
+
ls openspec/changes/ 2>/dev/null # umbrella changes at root
|
|
22
|
+
\`\`\`
|
|
23
|
+
|
|
24
|
+
Otherwise:
|
|
25
|
+
\`\`\`bash
|
|
26
|
+
openspec list --json
|
|
27
|
+
\`\`\`
|
|
14
28
|
|
|
29
|
+
Aggregate all results. Use the **AskUserQuestion tool** to let the user select.
|
|
30
|
+
In workspace mode, show which scope each change belongs to.
|
|
15
31
|
Show only active changes (not already archived).
|
|
16
32
|
Include the schema used for each change if available.
|
|
17
33
|
|
|
@@ -154,8 +170,24 @@ export function getOpsxArchiveCommandTemplate() {
|
|
|
154
170
|
|
|
155
171
|
1. **If no change name provided, prompt for selection**
|
|
156
172
|
|
|
157
|
-
|
|
173
|
+
Check for a workspace manifest:
|
|
174
|
+
\`\`\`bash
|
|
175
|
+
cat openspec/workspace.yaml 2>/dev/null
|
|
176
|
+
\`\`\`
|
|
177
|
+
|
|
178
|
+
If workspace.yaml exists, list changes across all scopes:
|
|
179
|
+
\`\`\`bash
|
|
180
|
+
(cd <scope.path> && openspec list --json) # for each scope in workspace.yaml
|
|
181
|
+
ls openspec/changes/ 2>/dev/null # umbrella changes at root
|
|
182
|
+
\`\`\`
|
|
183
|
+
|
|
184
|
+
Otherwise:
|
|
185
|
+
\`\`\`bash
|
|
186
|
+
openspec list --json
|
|
187
|
+
\`\`\`
|
|
158
188
|
|
|
189
|
+
Aggregate all results. Use the **AskUserQuestion tool** to let the user select.
|
|
190
|
+
In workspace mode, show which scope each change belongs to.
|
|
159
191
|
Show only active changes (not already archived).
|
|
160
192
|
Include the schema used for each change if available.
|
|
161
193
|
|
|
@@ -10,7 +10,23 @@ export function getContinueChangeSkillTemplate() {
|
|
|
10
10
|
|
|
11
11
|
1. **If no change name provided, prompt for selection**
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
Check for a workspace manifest:
|
|
14
|
+
\`\`\`bash
|
|
15
|
+
cat openspec/workspace.yaml 2>/dev/null
|
|
16
|
+
\`\`\`
|
|
17
|
+
|
|
18
|
+
If workspace.yaml exists, list changes across all scopes:
|
|
19
|
+
\`\`\`bash
|
|
20
|
+
(cd <scope.path> && openspec list --json) # for each scope in workspace.yaml
|
|
21
|
+
ls openspec/changes/ 2>/dev/null # umbrella changes at root
|
|
22
|
+
\`\`\`
|
|
23
|
+
|
|
24
|
+
Otherwise:
|
|
25
|
+
\`\`\`bash
|
|
26
|
+
openspec list --json
|
|
27
|
+
\`\`\`
|
|
28
|
+
|
|
29
|
+
Aggregate all results sorted by most recently modified. Use the **AskUserQuestion tool** to let the user select which change to work on.
|
|
14
30
|
|
|
15
31
|
Present the top 3-4 most recently modified changes as options, showing:
|
|
16
32
|
- Change name
|
|
@@ -128,7 +144,23 @@ export function getOpsxContinueCommandTemplate() {
|
|
|
128
144
|
|
|
129
145
|
1. **If no change name provided, prompt for selection**
|
|
130
146
|
|
|
131
|
-
|
|
147
|
+
Check for a workspace manifest:
|
|
148
|
+
\`\`\`bash
|
|
149
|
+
cat openspec/workspace.yaml 2>/dev/null
|
|
150
|
+
\`\`\`
|
|
151
|
+
|
|
152
|
+
If workspace.yaml exists, list changes across all scopes:
|
|
153
|
+
\`\`\`bash
|
|
154
|
+
(cd <scope.path> && openspec list --json) # for each scope in workspace.yaml
|
|
155
|
+
ls openspec/changes/ 2>/dev/null # umbrella changes at root
|
|
156
|
+
\`\`\`
|
|
157
|
+
|
|
158
|
+
Otherwise:
|
|
159
|
+
\`\`\`bash
|
|
160
|
+
openspec list --json
|
|
161
|
+
\`\`\`
|
|
162
|
+
|
|
163
|
+
Aggregate all results sorted by most recently modified. Use the **AskUserQuestion tool** to let the user select which change to work on.
|
|
132
164
|
|
|
133
165
|
Present the top 3-4 most recently modified changes as options, showing:
|
|
134
166
|
- Change name
|
package/dist/core/update.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 { 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
|
|
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
|
|
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,19 @@
|
|
|
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, after `&&`, or at line start),
|
|
21
|
+
* not path references 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 status --change "x"') // 'pnpm exec openspec status --change "x"' (line-start)
|
|
30
|
+
* t('openspec/changes/foo') // 'openspec/changes/foo' (path unchanged)
|
|
31
|
+
*/
|
|
32
|
+
export declare function createCliTransformer(cli: string | undefined): ((text: string) => string) | undefined;
|
|
18
33
|
//# sourceMappingURL=command-references.d.ts.map
|
|
@@ -17,4 +17,26 @@
|
|
|
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, after `&&`, or at line start),
|
|
23
|
+
* not path references 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 status --change "x"') // 'pnpm exec openspec status --change "x"' (line-start)
|
|
32
|
+
* t('openspec/changes/foo') // 'openspec/changes/foo' (path unchanged)
|
|
33
|
+
*/
|
|
34
|
+
export function createCliTransformer(cli) {
|
|
35
|
+
if (!cli)
|
|
36
|
+
return undefined;
|
|
37
|
+
return (text) => text
|
|
38
|
+
.replace(/`openspec\s/g, `\`${cli} `)
|
|
39
|
+
.replace(/&& openspec\s/g, `&& ${cli} `)
|
|
40
|
+
.replace(/^(\s*)openspec\s/gm, `$1${cli} `);
|
|
41
|
+
}
|
|
20
42
|
//# sourceMappingURL=command-references.js.map
|