@fission-ai/openspec 0.20.0 → 0.22.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.
- package/README.md +35 -0
- package/dist/cli/index.js +19 -0
- package/dist/commands/artifact-workflow.js +155 -22
- package/dist/commands/feedback.d.ts +9 -0
- package/dist/commands/feedback.js +183 -0
- package/dist/commands/schema.d.ts +6 -0
- package/dist/commands/schema.js +869 -0
- package/dist/core/artifact-graph/instruction-loader.d.ts +11 -2
- package/dist/core/artifact-graph/instruction-loader.js +59 -7
- package/dist/core/artifact-graph/resolver.d.ts +32 -12
- package/dist/core/artifact-graph/resolver.js +88 -18
- package/dist/core/completions/command-registry.js +88 -0
- package/dist/core/completions/types.d.ts +2 -1
- package/dist/core/config-prompts.d.ts +36 -0
- package/dist/core/config-prompts.js +151 -0
- package/dist/core/project-config.d.ts +64 -0
- package/dist/core/project-config.js +223 -0
- package/dist/core/templates/skill-templates.d.ts +5 -0
- package/dist/core/templates/skill-templates.js +165 -87
- package/dist/utils/change-metadata.d.ts +8 -4
- package/dist/utils/change-metadata.js +27 -10
- package/dist/utils/change-utils.d.ts +14 -3
- package/dist/utils/change-utils.js +27 -6
- package/package.json +1 -1
|
@@ -21,6 +21,8 @@ export interface ChangeContext {
|
|
|
21
21
|
changeName: string;
|
|
22
22
|
/** Path to the change directory */
|
|
23
23
|
changeDir: string;
|
|
24
|
+
/** Project root directory */
|
|
25
|
+
projectRoot: string;
|
|
24
26
|
}
|
|
25
27
|
/**
|
|
26
28
|
* Enriched instructions for creating an artifact.
|
|
@@ -93,10 +95,11 @@ export interface ChangeStatus {
|
|
|
93
95
|
*
|
|
94
96
|
* @param schemaName - Schema name (e.g., "spec-driven")
|
|
95
97
|
* @param templatePath - Relative path within the templates directory (e.g., "proposal.md")
|
|
98
|
+
* @param projectRoot - Optional project root for project-local schema resolution
|
|
96
99
|
* @returns The template content
|
|
97
100
|
* @throws TemplateLoadError if the template cannot be loaded
|
|
98
101
|
*/
|
|
99
|
-
export declare function loadTemplate(schemaName: string, templatePath: string): string;
|
|
102
|
+
export declare function loadTemplate(schemaName: string, templatePath: string, projectRoot?: string): string;
|
|
100
103
|
/**
|
|
101
104
|
* Loads change context combining graph and completion state.
|
|
102
105
|
*
|
|
@@ -114,12 +117,18 @@ export declare function loadChangeContext(projectRoot: string, changeName: strin
|
|
|
114
117
|
/**
|
|
115
118
|
* Generates enriched instructions for creating an artifact.
|
|
116
119
|
*
|
|
120
|
+
* Instruction injection order:
|
|
121
|
+
* 1. <context> - Project context from config (if present)
|
|
122
|
+
* 2. <rules> - Artifact-specific rules from config (if present)
|
|
123
|
+
* 3. <template> - Schema's template content
|
|
124
|
+
*
|
|
117
125
|
* @param context - Change context
|
|
118
126
|
* @param artifactId - Artifact ID to generate instructions for
|
|
127
|
+
* @param projectRoot - Project root directory (for reading config)
|
|
119
128
|
* @returns Enriched artifact instructions
|
|
120
129
|
* @throws Error if artifact not found
|
|
121
130
|
*/
|
|
122
|
-
export declare function generateInstructions(context: ChangeContext, artifactId: string): ArtifactInstructions;
|
|
131
|
+
export declare function generateInstructions(context: ChangeContext, artifactId: string, projectRoot?: string): ArtifactInstructions;
|
|
123
132
|
/**
|
|
124
133
|
* Formats the status of all artifacts in a change.
|
|
125
134
|
*
|
|
@@ -4,6 +4,9 @@ import { getSchemaDir, resolveSchema } from './resolver.js';
|
|
|
4
4
|
import { ArtifactGraph } from './graph.js';
|
|
5
5
|
import { detectCompleted } from './state.js';
|
|
6
6
|
import { resolveSchemaForChange } from '../../utils/change-metadata.js';
|
|
7
|
+
import { readProjectConfig, validateConfigRules } from '../project-config.js';
|
|
8
|
+
// Session-level cache for validation warnings (avoid repeating same warnings)
|
|
9
|
+
const shownWarnings = new Set();
|
|
7
10
|
/**
|
|
8
11
|
* Error thrown when loading a template fails.
|
|
9
12
|
*/
|
|
@@ -20,11 +23,12 @@ export class TemplateLoadError extends Error {
|
|
|
20
23
|
*
|
|
21
24
|
* @param schemaName - Schema name (e.g., "spec-driven")
|
|
22
25
|
* @param templatePath - Relative path within the templates directory (e.g., "proposal.md")
|
|
26
|
+
* @param projectRoot - Optional project root for project-local schema resolution
|
|
23
27
|
* @returns The template content
|
|
24
28
|
* @throws TemplateLoadError if the template cannot be loaded
|
|
25
29
|
*/
|
|
26
|
-
export function loadTemplate(schemaName, templatePath) {
|
|
27
|
-
const schemaDir = getSchemaDir(schemaName);
|
|
30
|
+
export function loadTemplate(schemaName, templatePath, projectRoot) {
|
|
31
|
+
const schemaDir = getSchemaDir(schemaName, projectRoot);
|
|
28
32
|
if (!schemaDir) {
|
|
29
33
|
throw new TemplateLoadError(`Schema '${schemaName}' not found`, templatePath);
|
|
30
34
|
}
|
|
@@ -57,7 +61,7 @@ export function loadChangeContext(projectRoot, changeName, schemaName) {
|
|
|
57
61
|
const changeDir = path.join(projectRoot, 'openspec', 'changes', changeName);
|
|
58
62
|
// Resolve schema: explicit > metadata > default
|
|
59
63
|
const resolvedSchemaName = resolveSchemaForChange(changeDir, schemaName);
|
|
60
|
-
const schema = resolveSchema(resolvedSchemaName);
|
|
64
|
+
const schema = resolveSchema(resolvedSchemaName, projectRoot);
|
|
61
65
|
const graph = ArtifactGraph.fromSchema(schema);
|
|
62
66
|
const completed = detectCompleted(graph, changeDir);
|
|
63
67
|
return {
|
|
@@ -66,24 +70,72 @@ export function loadChangeContext(projectRoot, changeName, schemaName) {
|
|
|
66
70
|
schemaName: resolvedSchemaName,
|
|
67
71
|
changeName,
|
|
68
72
|
changeDir,
|
|
73
|
+
projectRoot,
|
|
69
74
|
};
|
|
70
75
|
}
|
|
71
76
|
/**
|
|
72
77
|
* Generates enriched instructions for creating an artifact.
|
|
73
78
|
*
|
|
79
|
+
* Instruction injection order:
|
|
80
|
+
* 1. <context> - Project context from config (if present)
|
|
81
|
+
* 2. <rules> - Artifact-specific rules from config (if present)
|
|
82
|
+
* 3. <template> - Schema's template content
|
|
83
|
+
*
|
|
74
84
|
* @param context - Change context
|
|
75
85
|
* @param artifactId - Artifact ID to generate instructions for
|
|
86
|
+
* @param projectRoot - Project root directory (for reading config)
|
|
76
87
|
* @returns Enriched artifact instructions
|
|
77
88
|
* @throws Error if artifact not found
|
|
78
89
|
*/
|
|
79
|
-
export function generateInstructions(context, artifactId) {
|
|
90
|
+
export function generateInstructions(context, artifactId, projectRoot) {
|
|
80
91
|
const artifact = context.graph.getArtifact(artifactId);
|
|
81
92
|
if (!artifact) {
|
|
82
93
|
throw new Error(`Artifact '${artifactId}' not found in schema '${context.schemaName}'`);
|
|
83
94
|
}
|
|
84
|
-
const
|
|
95
|
+
const templateContent = loadTemplate(context.schemaName, artifact.template, context.projectRoot);
|
|
85
96
|
const dependencies = getDependencyInfo(artifact, context.graph, context.completed);
|
|
86
97
|
const unlocks = getUnlockedArtifacts(context.graph, artifactId);
|
|
98
|
+
// Build enriched template with project config injections
|
|
99
|
+
let enrichedTemplate = '';
|
|
100
|
+
let projectConfig = null;
|
|
101
|
+
// Use projectRoot from context if not explicitly provided
|
|
102
|
+
const effectiveProjectRoot = projectRoot ?? context.projectRoot;
|
|
103
|
+
// Try to read project config
|
|
104
|
+
if (effectiveProjectRoot) {
|
|
105
|
+
try {
|
|
106
|
+
projectConfig = readProjectConfig(effectiveProjectRoot);
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
// If config read fails, continue without config
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// Validate rules artifact IDs if config has rules (only once per session)
|
|
113
|
+
if (projectConfig?.rules) {
|
|
114
|
+
const validArtifactIds = new Set(context.graph.getAllArtifacts().map((a) => a.id));
|
|
115
|
+
const warnings = validateConfigRules(projectConfig.rules, validArtifactIds, context.schemaName);
|
|
116
|
+
// Show each unique warning only once per session
|
|
117
|
+
for (const warning of warnings) {
|
|
118
|
+
if (!shownWarnings.has(warning)) {
|
|
119
|
+
console.warn(warning);
|
|
120
|
+
shownWarnings.add(warning);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// 1. Add context (all artifacts)
|
|
125
|
+
if (projectConfig?.context) {
|
|
126
|
+
enrichedTemplate += `<context>\n${projectConfig.context}\n</context>\n\n`;
|
|
127
|
+
}
|
|
128
|
+
// 2. Add rules (only for matching artifact)
|
|
129
|
+
const rulesForArtifact = projectConfig?.rules?.[artifactId];
|
|
130
|
+
if (rulesForArtifact && rulesForArtifact.length > 0) {
|
|
131
|
+
enrichedTemplate += `<rules>\n`;
|
|
132
|
+
for (const rule of rulesForArtifact) {
|
|
133
|
+
enrichedTemplate += `- ${rule}\n`;
|
|
134
|
+
}
|
|
135
|
+
enrichedTemplate += `</rules>\n\n`;
|
|
136
|
+
}
|
|
137
|
+
// 3. Add original template (without wrapper - CLI handles XML structure)
|
|
138
|
+
enrichedTemplate += templateContent;
|
|
87
139
|
return {
|
|
88
140
|
changeName: context.changeName,
|
|
89
141
|
artifactId: artifact.id,
|
|
@@ -92,7 +144,7 @@ export function generateInstructions(context, artifactId) {
|
|
|
92
144
|
outputPath: artifact.generates,
|
|
93
145
|
description: artifact.description,
|
|
94
146
|
instruction: artifact.instruction,
|
|
95
|
-
template,
|
|
147
|
+
template: enrichedTemplate,
|
|
96
148
|
dependencies,
|
|
97
149
|
unlocks,
|
|
98
150
|
};
|
|
@@ -131,7 +183,7 @@ function getUnlockedArtifacts(graph, artifactId) {
|
|
|
131
183
|
*/
|
|
132
184
|
export function formatChangeStatus(context) {
|
|
133
185
|
// Load schema to get apply phase configuration
|
|
134
|
-
const schema = resolveSchema(context.schemaName);
|
|
186
|
+
const schema = resolveSchema(context.schemaName, context.projectRoot);
|
|
135
187
|
const applyRequires = schema.apply?.requires ?? schema.artifacts.map(a => a.id);
|
|
136
188
|
const artifacts = context.graph.getAllArtifacts();
|
|
137
189
|
const ready = new Set(context.graph.getNextArtifacts(context.completed));
|
|
@@ -16,34 +16,52 @@ export declare function getPackageSchemasDir(): string;
|
|
|
16
16
|
* Gets the user's schema override directory path.
|
|
17
17
|
*/
|
|
18
18
|
export declare function getUserSchemasDir(): string;
|
|
19
|
+
/**
|
|
20
|
+
* Gets the project-local schemas directory path.
|
|
21
|
+
* @param projectRoot - The project root directory
|
|
22
|
+
* @returns The path to the project's schemas directory
|
|
23
|
+
*/
|
|
24
|
+
export declare function getProjectSchemasDir(projectRoot: string): string;
|
|
19
25
|
/**
|
|
20
26
|
* Resolves a schema name to its directory path.
|
|
21
27
|
*
|
|
22
|
-
* Resolution order:
|
|
23
|
-
* 1.
|
|
24
|
-
* 2.
|
|
28
|
+
* Resolution order (when projectRoot is provided):
|
|
29
|
+
* 1. Project-local: <projectRoot>/openspec/schemas/<name>/schema.yaml
|
|
30
|
+
* 2. User override: ${XDG_DATA_HOME}/openspec/schemas/<name>/schema.yaml
|
|
31
|
+
* 3. Package built-in: <package>/schemas/<name>/schema.yaml
|
|
32
|
+
*
|
|
33
|
+
* When projectRoot is not provided, only user override and package built-in are checked
|
|
34
|
+
* (backward compatible behavior).
|
|
25
35
|
*
|
|
26
36
|
* @param name - Schema name (e.g., "spec-driven")
|
|
37
|
+
* @param projectRoot - Optional project root directory for project-local schema resolution
|
|
27
38
|
* @returns The path to the schema directory, or null if not found
|
|
28
39
|
*/
|
|
29
|
-
export declare function getSchemaDir(name: string): string | null;
|
|
40
|
+
export declare function getSchemaDir(name: string, projectRoot?: string): string | null;
|
|
30
41
|
/**
|
|
31
42
|
* Resolves a schema name to a SchemaYaml object.
|
|
32
43
|
*
|
|
33
|
-
* Resolution order:
|
|
34
|
-
* 1.
|
|
35
|
-
* 2.
|
|
44
|
+
* Resolution order (when projectRoot is provided):
|
|
45
|
+
* 1. Project-local: <projectRoot>/openspec/schemas/<name>/schema.yaml
|
|
46
|
+
* 2. User override: ${XDG_DATA_HOME}/openspec/schemas/<name>/schema.yaml
|
|
47
|
+
* 3. Package built-in: <package>/schemas/<name>/schema.yaml
|
|
48
|
+
*
|
|
49
|
+
* When projectRoot is not provided, only user override and package built-in are checked
|
|
50
|
+
* (backward compatible behavior).
|
|
36
51
|
*
|
|
37
52
|
* @param name - Schema name (e.g., "spec-driven")
|
|
53
|
+
* @param projectRoot - Optional project root directory for project-local schema resolution
|
|
38
54
|
* @returns The resolved schema object
|
|
39
55
|
* @throws Error if schema is not found in any location
|
|
40
56
|
*/
|
|
41
|
-
export declare function resolveSchema(name: string): SchemaYaml;
|
|
57
|
+
export declare function resolveSchema(name: string, projectRoot?: string): SchemaYaml;
|
|
42
58
|
/**
|
|
43
59
|
* Lists all available schema names.
|
|
44
|
-
* Combines user override and package built-in schemas.
|
|
60
|
+
* Combines project-local, user override, and package built-in schemas.
|
|
61
|
+
*
|
|
62
|
+
* @param projectRoot - Optional project root directory for project-local schema resolution
|
|
45
63
|
*/
|
|
46
|
-
export declare function listSchemas(): string[];
|
|
64
|
+
export declare function listSchemas(projectRoot?: string): string[];
|
|
47
65
|
/**
|
|
48
66
|
* Schema info with metadata (name, description, artifacts).
|
|
49
67
|
*/
|
|
@@ -51,11 +69,13 @@ export interface SchemaInfo {
|
|
|
51
69
|
name: string;
|
|
52
70
|
description: string;
|
|
53
71
|
artifacts: string[];
|
|
54
|
-
source: '
|
|
72
|
+
source: 'project' | 'user' | 'package';
|
|
55
73
|
}
|
|
56
74
|
/**
|
|
57
75
|
* Lists all available schemas with their descriptions and artifact lists.
|
|
58
76
|
* Useful for agent skills to present schema selection to users.
|
|
77
|
+
*
|
|
78
|
+
* @param projectRoot - Optional project root directory for project-local schema resolution
|
|
59
79
|
*/
|
|
60
|
-
export declare function listSchemasWithInfo(): SchemaInfo[];
|
|
80
|
+
export declare function listSchemasWithInfo(projectRoot?: string): SchemaInfo[];
|
|
61
81
|
//# sourceMappingURL=resolver.d.ts.map
|
|
@@ -31,24 +31,45 @@ export function getPackageSchemasDir() {
|
|
|
31
31
|
export function getUserSchemasDir() {
|
|
32
32
|
return path.join(getGlobalDataDir(), 'schemas');
|
|
33
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Gets the project-local schemas directory path.
|
|
36
|
+
* @param projectRoot - The project root directory
|
|
37
|
+
* @returns The path to the project's schemas directory
|
|
38
|
+
*/
|
|
39
|
+
export function getProjectSchemasDir(projectRoot) {
|
|
40
|
+
return path.join(projectRoot, 'openspec', 'schemas');
|
|
41
|
+
}
|
|
34
42
|
/**
|
|
35
43
|
* Resolves a schema name to its directory path.
|
|
36
44
|
*
|
|
37
|
-
* Resolution order:
|
|
38
|
-
* 1.
|
|
39
|
-
* 2.
|
|
45
|
+
* Resolution order (when projectRoot is provided):
|
|
46
|
+
* 1. Project-local: <projectRoot>/openspec/schemas/<name>/schema.yaml
|
|
47
|
+
* 2. User override: ${XDG_DATA_HOME}/openspec/schemas/<name>/schema.yaml
|
|
48
|
+
* 3. Package built-in: <package>/schemas/<name>/schema.yaml
|
|
49
|
+
*
|
|
50
|
+
* When projectRoot is not provided, only user override and package built-in are checked
|
|
51
|
+
* (backward compatible behavior).
|
|
40
52
|
*
|
|
41
53
|
* @param name - Schema name (e.g., "spec-driven")
|
|
54
|
+
* @param projectRoot - Optional project root directory for project-local schema resolution
|
|
42
55
|
* @returns The path to the schema directory, or null if not found
|
|
43
56
|
*/
|
|
44
|
-
export function getSchemaDir(name) {
|
|
45
|
-
// 1. Check
|
|
57
|
+
export function getSchemaDir(name, projectRoot) {
|
|
58
|
+
// 1. Check project-local directory (if projectRoot provided)
|
|
59
|
+
if (projectRoot) {
|
|
60
|
+
const projectDir = path.join(getProjectSchemasDir(projectRoot), name);
|
|
61
|
+
const projectSchemaPath = path.join(projectDir, 'schema.yaml');
|
|
62
|
+
if (fs.existsSync(projectSchemaPath)) {
|
|
63
|
+
return projectDir;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// 2. Check user override directory
|
|
46
67
|
const userDir = path.join(getUserSchemasDir(), name);
|
|
47
68
|
const userSchemaPath = path.join(userDir, 'schema.yaml');
|
|
48
69
|
if (fs.existsSync(userSchemaPath)) {
|
|
49
70
|
return userDir;
|
|
50
71
|
}
|
|
51
|
-
//
|
|
72
|
+
// 3. Check package built-in directory
|
|
52
73
|
const packageDir = path.join(getPackageSchemasDir(), name);
|
|
53
74
|
const packageSchemaPath = path.join(packageDir, 'schema.yaml');
|
|
54
75
|
if (fs.existsSync(packageSchemaPath)) {
|
|
@@ -59,20 +80,25 @@ export function getSchemaDir(name) {
|
|
|
59
80
|
/**
|
|
60
81
|
* Resolves a schema name to a SchemaYaml object.
|
|
61
82
|
*
|
|
62
|
-
* Resolution order:
|
|
63
|
-
* 1.
|
|
64
|
-
* 2.
|
|
83
|
+
* Resolution order (when projectRoot is provided):
|
|
84
|
+
* 1. Project-local: <projectRoot>/openspec/schemas/<name>/schema.yaml
|
|
85
|
+
* 2. User override: ${XDG_DATA_HOME}/openspec/schemas/<name>/schema.yaml
|
|
86
|
+
* 3. Package built-in: <package>/schemas/<name>/schema.yaml
|
|
87
|
+
*
|
|
88
|
+
* When projectRoot is not provided, only user override and package built-in are checked
|
|
89
|
+
* (backward compatible behavior).
|
|
65
90
|
*
|
|
66
91
|
* @param name - Schema name (e.g., "spec-driven")
|
|
92
|
+
* @param projectRoot - Optional project root directory for project-local schema resolution
|
|
67
93
|
* @returns The resolved schema object
|
|
68
94
|
* @throws Error if schema is not found in any location
|
|
69
95
|
*/
|
|
70
|
-
export function resolveSchema(name) {
|
|
96
|
+
export function resolveSchema(name, projectRoot) {
|
|
71
97
|
// Normalize name (remove .yaml extension if provided)
|
|
72
98
|
const normalizedName = name.replace(/\.ya?ml$/, '');
|
|
73
|
-
const schemaDir = getSchemaDir(normalizedName);
|
|
99
|
+
const schemaDir = getSchemaDir(normalizedName, projectRoot);
|
|
74
100
|
if (!schemaDir) {
|
|
75
|
-
const availableSchemas = listSchemas();
|
|
101
|
+
const availableSchemas = listSchemas(projectRoot);
|
|
76
102
|
throw new Error(`Schema '${normalizedName}' not found. Available schemas: ${availableSchemas.join(', ')}`);
|
|
77
103
|
}
|
|
78
104
|
const schemaPath = path.join(schemaDir, 'schema.yaml');
|
|
@@ -98,9 +124,11 @@ export function resolveSchema(name) {
|
|
|
98
124
|
}
|
|
99
125
|
/**
|
|
100
126
|
* Lists all available schema names.
|
|
101
|
-
* Combines user override and package built-in schemas.
|
|
127
|
+
* Combines project-local, user override, and package built-in schemas.
|
|
128
|
+
*
|
|
129
|
+
* @param projectRoot - Optional project root directory for project-local schema resolution
|
|
102
130
|
*/
|
|
103
|
-
export function listSchemas() {
|
|
131
|
+
export function listSchemas(projectRoot) {
|
|
104
132
|
const schemas = new Set();
|
|
105
133
|
// Add package built-in schemas
|
|
106
134
|
const packageDir = getPackageSchemasDir();
|
|
@@ -126,20 +154,62 @@ export function listSchemas() {
|
|
|
126
154
|
}
|
|
127
155
|
}
|
|
128
156
|
}
|
|
157
|
+
// Add project-local schemas (if projectRoot provided)
|
|
158
|
+
if (projectRoot) {
|
|
159
|
+
const projectDir = getProjectSchemasDir(projectRoot);
|
|
160
|
+
if (fs.existsSync(projectDir)) {
|
|
161
|
+
for (const entry of fs.readdirSync(projectDir, { withFileTypes: true })) {
|
|
162
|
+
if (entry.isDirectory()) {
|
|
163
|
+
const schemaPath = path.join(projectDir, entry.name, 'schema.yaml');
|
|
164
|
+
if (fs.existsSync(schemaPath)) {
|
|
165
|
+
schemas.add(entry.name);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
129
171
|
return Array.from(schemas).sort();
|
|
130
172
|
}
|
|
131
173
|
/**
|
|
132
174
|
* Lists all available schemas with their descriptions and artifact lists.
|
|
133
175
|
* Useful for agent skills to present schema selection to users.
|
|
176
|
+
*
|
|
177
|
+
* @param projectRoot - Optional project root directory for project-local schema resolution
|
|
134
178
|
*/
|
|
135
|
-
export function listSchemasWithInfo() {
|
|
179
|
+
export function listSchemasWithInfo(projectRoot) {
|
|
136
180
|
const schemas = [];
|
|
137
181
|
const seenNames = new Set();
|
|
138
|
-
// Add
|
|
182
|
+
// Add project-local schemas first (highest priority, if projectRoot provided)
|
|
183
|
+
if (projectRoot) {
|
|
184
|
+
const projectDir = getProjectSchemasDir(projectRoot);
|
|
185
|
+
if (fs.existsSync(projectDir)) {
|
|
186
|
+
for (const entry of fs.readdirSync(projectDir, { withFileTypes: true })) {
|
|
187
|
+
if (entry.isDirectory()) {
|
|
188
|
+
const schemaPath = path.join(projectDir, entry.name, 'schema.yaml');
|
|
189
|
+
if (fs.existsSync(schemaPath)) {
|
|
190
|
+
try {
|
|
191
|
+
const schema = parseSchema(fs.readFileSync(schemaPath, 'utf-8'));
|
|
192
|
+
schemas.push({
|
|
193
|
+
name: entry.name,
|
|
194
|
+
description: schema.description || '',
|
|
195
|
+
artifacts: schema.artifacts.map((a) => a.id),
|
|
196
|
+
source: 'project',
|
|
197
|
+
});
|
|
198
|
+
seenNames.add(entry.name);
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
// Skip invalid schemas
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
// Add user override schemas (if not overridden by project)
|
|
139
209
|
const userDir = getUserSchemasDir();
|
|
140
210
|
if (fs.existsSync(userDir)) {
|
|
141
211
|
for (const entry of fs.readdirSync(userDir, { withFileTypes: true })) {
|
|
142
|
-
if (entry.isDirectory()) {
|
|
212
|
+
if (entry.isDirectory() && !seenNames.has(entry.name)) {
|
|
143
213
|
const schemaPath = path.join(userDir, entry.name, 'schema.yaml');
|
|
144
214
|
if (fs.existsSync(schemaPath)) {
|
|
145
215
|
try {
|
|
@@ -159,7 +229,7 @@ export function listSchemasWithInfo() {
|
|
|
159
229
|
}
|
|
160
230
|
}
|
|
161
231
|
}
|
|
162
|
-
// Add package built-in schemas (if not overridden)
|
|
232
|
+
// Add package built-in schemas (if not overridden by project or user)
|
|
163
233
|
const packageDir = getPackageSchemasDir();
|
|
164
234
|
if (fs.existsSync(packageDir)) {
|
|
165
235
|
for (const entry of fs.readdirSync(packageDir, { withFileTypes: true })) {
|
|
@@ -152,6 +152,18 @@ export const COMMAND_REGISTRY = [
|
|
|
152
152
|
},
|
|
153
153
|
],
|
|
154
154
|
},
|
|
155
|
+
{
|
|
156
|
+
name: 'feedback',
|
|
157
|
+
description: 'Submit feedback about OpenSpec',
|
|
158
|
+
acceptsPositional: true,
|
|
159
|
+
flags: [
|
|
160
|
+
{
|
|
161
|
+
name: 'body',
|
|
162
|
+
description: 'Detailed description for the feedback',
|
|
163
|
+
takesValue: true,
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
},
|
|
155
167
|
{
|
|
156
168
|
name: 'change',
|
|
157
169
|
description: 'Manage OpenSpec change proposals (deprecated)',
|
|
@@ -364,5 +376,81 @@ export const COMMAND_REGISTRY = [
|
|
|
364
376
|
},
|
|
365
377
|
],
|
|
366
378
|
},
|
|
379
|
+
{
|
|
380
|
+
name: 'schema',
|
|
381
|
+
description: 'Manage workflow schemas',
|
|
382
|
+
flags: [],
|
|
383
|
+
subcommands: [
|
|
384
|
+
{
|
|
385
|
+
name: 'which',
|
|
386
|
+
description: 'Show where a schema resolves from',
|
|
387
|
+
acceptsPositional: true,
|
|
388
|
+
positionalType: 'schema-name',
|
|
389
|
+
flags: [
|
|
390
|
+
COMMON_FLAGS.json,
|
|
391
|
+
{
|
|
392
|
+
name: 'all',
|
|
393
|
+
description: 'List all schemas with their resolution sources',
|
|
394
|
+
},
|
|
395
|
+
],
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
name: 'validate',
|
|
399
|
+
description: 'Validate a schema structure and templates',
|
|
400
|
+
acceptsPositional: true,
|
|
401
|
+
positionalType: 'schema-name',
|
|
402
|
+
flags: [
|
|
403
|
+
COMMON_FLAGS.json,
|
|
404
|
+
{
|
|
405
|
+
name: 'verbose',
|
|
406
|
+
description: 'Show detailed validation steps',
|
|
407
|
+
},
|
|
408
|
+
],
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
name: 'fork',
|
|
412
|
+
description: 'Copy an existing schema to project for customization',
|
|
413
|
+
acceptsPositional: true,
|
|
414
|
+
positionalType: 'schema-name',
|
|
415
|
+
flags: [
|
|
416
|
+
COMMON_FLAGS.json,
|
|
417
|
+
{
|
|
418
|
+
name: 'force',
|
|
419
|
+
description: 'Overwrite existing destination',
|
|
420
|
+
},
|
|
421
|
+
],
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
name: 'init',
|
|
425
|
+
description: 'Create a new project-local schema',
|
|
426
|
+
acceptsPositional: true,
|
|
427
|
+
flags: [
|
|
428
|
+
COMMON_FLAGS.json,
|
|
429
|
+
{
|
|
430
|
+
name: 'description',
|
|
431
|
+
description: 'Schema description',
|
|
432
|
+
takesValue: true,
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
name: 'artifacts',
|
|
436
|
+
description: 'Comma-separated artifact IDs',
|
|
437
|
+
takesValue: true,
|
|
438
|
+
},
|
|
439
|
+
{
|
|
440
|
+
name: 'default',
|
|
441
|
+
description: 'Set as project default schema',
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
name: 'no-default',
|
|
445
|
+
description: 'Do not prompt to set as default',
|
|
446
|
+
},
|
|
447
|
+
{
|
|
448
|
+
name: 'force',
|
|
449
|
+
description: 'Overwrite existing schema',
|
|
450
|
+
},
|
|
451
|
+
],
|
|
452
|
+
},
|
|
453
|
+
],
|
|
454
|
+
},
|
|
367
455
|
];
|
|
368
456
|
//# sourceMappingURL=command-registry.js.map
|
|
@@ -55,9 +55,10 @@ export interface CommandDefinition {
|
|
|
55
55
|
* - 'change-or-spec-id': Complete with both changes and specs
|
|
56
56
|
* - 'path': Complete with file paths
|
|
57
57
|
* - 'shell': Complete with supported shell names
|
|
58
|
+
* - 'schema-name': Complete with available schema names
|
|
58
59
|
* - undefined: No specific completion
|
|
59
60
|
*/
|
|
60
|
-
positionalType?: 'change-id' | 'spec-id' | 'change-or-spec-id' | 'path' | 'shell';
|
|
61
|
+
positionalType?: 'change-id' | 'spec-id' | 'change-or-spec-id' | 'path' | 'shell' | 'schema-name';
|
|
61
62
|
}
|
|
62
63
|
/**
|
|
63
64
|
* Interface for shell-specific completion script generators
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { ProjectConfig } from './project-config.js';
|
|
2
|
+
/**
|
|
3
|
+
* Check if an error is an ExitPromptError (user cancelled with Ctrl+C).
|
|
4
|
+
* Used instead of instanceof check since @inquirer modules use dynamic imports.
|
|
5
|
+
*/
|
|
6
|
+
export declare function isExitPromptError(error: unknown): boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Result of interactive config creation prompts.
|
|
9
|
+
*/
|
|
10
|
+
export interface ConfigPromptResult {
|
|
11
|
+
/** Whether to create config file */
|
|
12
|
+
createConfig: boolean;
|
|
13
|
+
/** Selected schema name */
|
|
14
|
+
schema?: string;
|
|
15
|
+
/** Project context (optional) */
|
|
16
|
+
context?: string;
|
|
17
|
+
/** Per-artifact rules (optional) */
|
|
18
|
+
rules?: Record<string, string[]>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Prompt user to create project config interactively.
|
|
22
|
+
* Used by experimental setup command.
|
|
23
|
+
*
|
|
24
|
+
* @param projectRoot - Optional project root for project-local schema resolution
|
|
25
|
+
* @returns Config prompt result
|
|
26
|
+
* @throws ExitPromptError if user cancels (Ctrl+C)
|
|
27
|
+
*/
|
|
28
|
+
export declare function promptForConfig(projectRoot?: string): Promise<ConfigPromptResult>;
|
|
29
|
+
/**
|
|
30
|
+
* Serialize config to YAML string with proper multi-line formatting.
|
|
31
|
+
*
|
|
32
|
+
* @param config - Partial config object (schema required, context/rules optional)
|
|
33
|
+
* @returns YAML string ready to write to file
|
|
34
|
+
*/
|
|
35
|
+
export declare function serializeConfig(config: Partial<ProjectConfig>): string;
|
|
36
|
+
//# sourceMappingURL=config-prompts.d.ts.map
|