@fission-ai/openspec 1.13.2 → 1.14.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/dist/cli/index.js +39 -1
- package/dist/commands/spec.js +1 -0
- package/dist/commands/workflow/instructions.js +31 -11
- package/dist/commands/workflow/shared.d.ts +2 -0
- package/dist/commands/workflow/status.js +15 -2
- package/dist/core/archive.js +15 -2
- package/dist/core/artifact-graph/instruction-loader.d.ts +7 -0
- package/dist/core/artifact-graph/instruction-loader.js +18 -1
- package/dist/core/change-metadata/schema.d.ts +3 -0
- package/dist/core/change-metadata/schema.js +10 -0
- package/dist/core/change-status-policy.d.ts +8 -0
- package/dist/core/change-status-policy.js +33 -2
- package/dist/core/command-generation/adapters/atomcode.d.ts +24 -0
- package/dist/core/command-generation/adapters/atomcode.js +46 -0
- package/dist/core/command-generation/adapters/codestudio.d.ts +13 -0
- package/dist/core/command-generation/adapters/codestudio.js +27 -0
- package/dist/core/command-generation/adapters/easycode.d.ts +17 -0
- package/dist/core/command-generation/adapters/easycode.js +28 -0
- package/dist/core/command-generation/adapters/gemini.js +1 -35
- package/dist/core/command-generation/adapters/gigacode.d.ts +17 -0
- package/dist/core/command-generation/adapters/gigacode.js +31 -0
- package/dist/core/command-generation/adapters/index.d.ts +4 -0
- package/dist/core/command-generation/adapters/index.js +4 -0
- package/dist/core/command-generation/registry.js +8 -0
- package/dist/core/command-generation/toml.d.ts +19 -0
- package/dist/core/command-generation/toml.js +39 -0
- package/dist/core/command-surface.js +1 -1
- package/dist/core/completions/command-registry.js +19 -0
- package/dist/core/completions/installers/zsh-installer.js +4 -2
- package/dist/core/config-prompts.js +6 -6
- package/dist/core/config.js +11 -1
- package/dist/core/init.js +35 -11
- package/dist/core/legacy-cleanup.js +11 -7
- package/dist/core/list.d.ts +2 -0
- package/dist/core/list.js +49 -23
- package/dist/core/parsers/markdown-parser.js +5 -0
- package/dist/core/parsers/requirement-blocks.d.ts +6 -0
- package/dist/core/parsers/requirement-blocks.js +9 -2
- package/dist/core/project-config.js +45 -1
- package/dist/core/references.js +3 -1
- package/dist/core/root-selection.d.ts +6 -0
- package/dist/core/root-selection.js +16 -0
- package/dist/core/schemas/base.schema.d.ts +3 -0
- package/dist/core/schemas/base.schema.js +6 -0
- package/dist/core/schemas/change.schema.d.ts +8 -0
- package/dist/core/schemas/spec.schema.d.ts +2 -0
- package/dist/core/templates/workflows/apply-change.js +5 -2
- package/dist/core/templates/workflows/archive-change.js +26 -4
- package/dist/core/templates/workflows/bulk-archive-change.js +6 -2
- package/dist/core/validation/constants.d.ts +1 -1
- package/dist/core/validation/constants.js +1 -1
- package/dist/core/validation/validator.js +9 -1
- package/dist/core/version-check.d.ts +23 -1
- package/dist/core/version-check.js +112 -26
- package/dist/core/view.d.ts +2 -0
- package/dist/core/view.js +62 -4
- package/dist/utils/change-metadata.d.ts +20 -1
- package/dist/utils/change-metadata.js +57 -1
- package/dist/utils/command-references.d.ts +1 -1
- package/dist/utils/command-references.js +2 -2
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.js +1 -1
- package/package.json +1 -1
- package/schemas/spec-driven/schema.yaml +2 -1
- package/schemas/spec-driven/templates/proposal.md +6 -3
package/dist/cli/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import { fileURLToPath } from 'url';
|
|
|
7
7
|
import { existsSync, promises as fs } from 'fs';
|
|
8
8
|
import { AI_TOOLS, TOOL_ID_ALIASES } from '../core/config.js';
|
|
9
9
|
import { UpdateCommand } from '../core/update.js';
|
|
10
|
-
import { getAvailableCliUpdate, displayCliUpdateNote, shouldOfferUpgrade, getInstallDir, offerCliUpgrade, rerunUpdateWithUpgradedCli, displayUpgradeCommand, isSourceCheckout, } from '../core/version-check.js';
|
|
10
|
+
import { getAvailableCliUpdate, displayCliUpdateNote, shouldOfferUpgrade, getInstallDir, offerCliUpgrade, rerunUpdateWithUpgradedCli, displayUpgradeCommand, isSourceCheckout, checkForCliUpdate, getCliInstallInfo, getCliUpdateCommand, canSelfUpgrade, buildVersionReportLines, } from '../core/version-check.js';
|
|
11
11
|
import { ListCommand } from '../core/list.js';
|
|
12
12
|
import { ArchiveCommand } from '../core/archive.js';
|
|
13
13
|
import { ViewCommand } from '../core/view.js';
|
|
@@ -118,6 +118,37 @@ program
|
|
|
118
118
|
.version(version);
|
|
119
119
|
// Global options
|
|
120
120
|
program.option('--no-color', 'Disable color output');
|
|
121
|
+
program
|
|
122
|
+
.command('version')
|
|
123
|
+
.description('Report the installed OpenSpec version and update availability')
|
|
124
|
+
.option('--json', 'Output as JSON')
|
|
125
|
+
.option('--check', 'Check the registry for a newer version')
|
|
126
|
+
.action(async (options) => {
|
|
127
|
+
const install = getCliInstallInfo();
|
|
128
|
+
const update = options.check ? await checkForCliUpdate() : undefined;
|
|
129
|
+
const command = update?.status === 'available' ? getCliUpdateCommand(install) : null;
|
|
130
|
+
const output = {
|
|
131
|
+
schemaVersion: 1,
|
|
132
|
+
version,
|
|
133
|
+
install,
|
|
134
|
+
...(update
|
|
135
|
+
? {
|
|
136
|
+
update: {
|
|
137
|
+
...update,
|
|
138
|
+
command,
|
|
139
|
+
canSelfUpgrade: update.status === 'available'
|
|
140
|
+
? canSelfUpgrade(install.location, process.cwd())
|
|
141
|
+
: false,
|
|
142
|
+
},
|
|
143
|
+
}
|
|
144
|
+
: {}),
|
|
145
|
+
};
|
|
146
|
+
if (options.json) {
|
|
147
|
+
console.log(JSON.stringify(output, null, 2));
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
console.log(buildVersionReportLines(version, install, update, command).join('\n'));
|
|
151
|
+
});
|
|
121
152
|
// Apply global flags and telemetry before any command runs
|
|
122
153
|
// Note: preAction receives (thisCommand, actionCommand) where:
|
|
123
154
|
// - thisCommand: the command where hook was added (root program)
|
|
@@ -296,12 +327,17 @@ program
|
|
|
296
327
|
.description('List items (changes by default). Use --specs to list specs.')
|
|
297
328
|
.option('--specs', 'List specs instead of changes')
|
|
298
329
|
.option('--changes', 'List changes explicitly (default)')
|
|
330
|
+
.option('--archived', 'Show only archived changes')
|
|
331
|
+
.option('--all', 'Show both active and archived changes')
|
|
299
332
|
.option('--sort <order>', 'Sort order: "recent" (default) or "name"', 'recent')
|
|
300
333
|
.option('--json', 'Output as JSON (for programmatic use)')
|
|
301
334
|
.option('--store <id>', STORE_OPTION_DESCRIPTION)
|
|
302
335
|
.addOption(hiddenStorePathOption())
|
|
303
336
|
.action(async (options) => {
|
|
304
337
|
try {
|
|
338
|
+
if (options?.specs && (options.archived || options.all)) {
|
|
339
|
+
throw new Error('--archived and --all can only be used when listing changes.');
|
|
340
|
+
}
|
|
305
341
|
const root = await resolveRootForCommand(options ?? {}, {
|
|
306
342
|
json: options?.json,
|
|
307
343
|
failurePayload: options?.specs ? { specs: [], root: null } : { changes: [], root: null },
|
|
@@ -318,6 +354,8 @@ program
|
|
|
318
354
|
await listCommand.execute(root.path, mode, {
|
|
319
355
|
sort,
|
|
320
356
|
json: options?.json,
|
|
357
|
+
archived: options?.archived,
|
|
358
|
+
all: options?.all,
|
|
321
359
|
...(options?.json ? { root: toRootOutput(root) } : {}),
|
|
322
360
|
});
|
|
323
361
|
}
|
package/dist/commands/spec.js
CHANGED
|
@@ -45,6 +45,7 @@ function filterSpec(spec, options) {
|
|
|
45
45
|
const filteredRequirements = (requirementIndex !== undefined
|
|
46
46
|
? [spec.requirements[requirementIndex]]
|
|
47
47
|
: spec.requirements).map(req => ({
|
|
48
|
+
name: req.name,
|
|
48
49
|
text: req.text,
|
|
49
50
|
scenarios: includeScenarios ? req.scenarios : [],
|
|
50
51
|
}));
|
|
@@ -103,6 +103,14 @@ export function printInstructionsText(instructions, isBlocked) {
|
|
|
103
103
|
` change="${escapeEnvelopeAttribute(changeName)}"` +
|
|
104
104
|
` schema="${escapeEnvelopeAttribute(schemaName)}">`);
|
|
105
105
|
console.log();
|
|
106
|
+
if (instructions.warnings) {
|
|
107
|
+
for (const warning of instructions.warnings) {
|
|
108
|
+
console.log('<warning>');
|
|
109
|
+
console.log(escapeEnvelopeTags(warning));
|
|
110
|
+
console.log('</warning>');
|
|
111
|
+
console.log();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
106
114
|
// Artifacts skipped via skip_specs get no creation directive: emitting the
|
|
107
115
|
// task/template anyway would prompt an agent to write spec files that
|
|
108
116
|
// validate then rejects as conflicting with the marker.
|
|
@@ -215,9 +223,16 @@ export function printInstructionsText(instructions, isBlocked) {
|
|
|
215
223
|
// Closing tag
|
|
216
224
|
console.log('</artifact>');
|
|
217
225
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
226
|
+
/** Adds one-based source locations to parsed tasks without changing task parsing. */
|
|
227
|
+
function parseLocatedTasks(content, sourcePath) {
|
|
228
|
+
const tasks = [];
|
|
229
|
+
for (const [index, line] of content.split('\n').entries()) {
|
|
230
|
+
const [task] = parseTaskLines(line);
|
|
231
|
+
if (task)
|
|
232
|
+
tasks.push({ ...task, sourcePath, line: index + 1 });
|
|
233
|
+
}
|
|
234
|
+
return tasks;
|
|
235
|
+
}
|
|
221
236
|
/**
|
|
222
237
|
* Turns parsed task lines into the listed task items.
|
|
223
238
|
*
|
|
@@ -238,6 +253,8 @@ function toTaskItems(parsed) {
|
|
|
238
253
|
id: `${tasks.length + 1}`,
|
|
239
254
|
description: task.description,
|
|
240
255
|
done: task.done,
|
|
256
|
+
sourcePath: task.sourcePath,
|
|
257
|
+
line: task.line,
|
|
241
258
|
});
|
|
242
259
|
}
|
|
243
260
|
return tasks;
|
|
@@ -406,7 +423,7 @@ export async function generateApplyInstructions(projectRoot, changeName, schemaN
|
|
|
406
423
|
for (const tracksPath of tracksPaths) {
|
|
407
424
|
try {
|
|
408
425
|
const tasksContent = await fs.promises.readFile(tracksPath, 'utf-8');
|
|
409
|
-
parsedTasks.push(...
|
|
426
|
+
parsedTasks.push(...parseLocatedTasks(tasksContent, tracksPath));
|
|
410
427
|
}
|
|
411
428
|
catch (error) {
|
|
412
429
|
const code = error?.code;
|
|
@@ -483,13 +500,16 @@ export async function generateApplyInstructions(projectRoot, changeName, schemaN
|
|
|
483
500
|
.join('\n');
|
|
484
501
|
instruction += `\nTask completion is not verified because tracking evidence was unavailable:\n${unavailableDetails}`;
|
|
485
502
|
}
|
|
486
|
-
const warnings =
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
503
|
+
const warnings = [
|
|
504
|
+
...(context.warnings ?? []),
|
|
505
|
+
...(await collectApplyWarnings({
|
|
506
|
+
state,
|
|
507
|
+
schema,
|
|
508
|
+
changeDir,
|
|
509
|
+
changeName,
|
|
510
|
+
skippedArtifacts: context.skippedArtifacts,
|
|
511
|
+
})),
|
|
512
|
+
];
|
|
493
513
|
return {
|
|
494
514
|
changeName,
|
|
495
515
|
changeDir,
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import ora from 'ora';
|
|
7
7
|
import chalk from 'chalk';
|
|
8
8
|
import { getChangeDir } from '../../core/planning-home.js';
|
|
9
|
-
import { resolveRootForCommand, toPlanningHome, toRootOutput, withStoreFlag, isStoreSelectedRoot, } from '../../core/root-selection.js';
|
|
9
|
+
import { resolveRootForCommand, toPlanningHome, toRootOutput, withStoreFlag, isStoreSelectedRoot, findDeclaringProjectRoot, } from '../../core/root-selection.js';
|
|
10
10
|
import { loadChangeContext, formatChangeStatus, } from '../../core/artifact-graph/index.js';
|
|
11
11
|
import { resolveNextStep } from '../../core/change-status-policy.js';
|
|
12
12
|
import { asStatus } from '../shared-output.js';
|
|
@@ -43,12 +43,20 @@ export async function statusCommand(options) {
|
|
|
43
43
|
// `Next:` line, so a store-selected root can never carry `--store` in one
|
|
44
44
|
// and drop it from the other.
|
|
45
45
|
const storeOptions = isStoreSelectedRoot(root) ? { storeId: root.storeId } : {};
|
|
46
|
+
// A store holds planning artifacts only; the project declaring it is
|
|
47
|
+
// where implementation edits go (#2013).
|
|
48
|
+
const implementationRoot = isStoreSelectedRoot(root)
|
|
49
|
+
? findDeclaringProjectRoot(root.storeId)
|
|
50
|
+
: null;
|
|
51
|
+
const statusOptions = implementationRoot
|
|
52
|
+
? { ...storeOptions, implementationRoot }
|
|
53
|
+
: storeOptions;
|
|
46
54
|
// Single definition of "load one change's status" so the batch and
|
|
47
55
|
// single-change payloads can never drift apart.
|
|
48
56
|
const loadStatus = (changeName) => formatChangeStatus(loadChangeContext(projectRoot, changeName, options.schema, {
|
|
49
57
|
changeDir: getChangeDir(planningHome, changeName),
|
|
50
58
|
planningHome,
|
|
51
|
-
}),
|
|
59
|
+
}), statusOptions);
|
|
52
60
|
// Handle no-changes case gracefully — status is informational,
|
|
53
61
|
// so "no changes" is a valid state, not an error.
|
|
54
62
|
if (!options.change) {
|
|
@@ -157,6 +165,11 @@ export function printStatusText(status, options = {}) {
|
|
|
157
165
|
const total = status.artifacts.length - skippedCount;
|
|
158
166
|
console.log(`Change: ${status.changeName}`);
|
|
159
167
|
console.log(`Schema: ${status.schemaName}`);
|
|
168
|
+
if (status.warnings) {
|
|
169
|
+
for (const warning of status.warnings) {
|
|
170
|
+
console.log(chalk.yellow(`Warning: ${warning}`));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
160
173
|
if (status.changeRoot) {
|
|
161
174
|
console.log(`Change root: ${status.changeRoot}`);
|
|
162
175
|
}
|
package/dist/core/archive.js
CHANGED
|
@@ -9,7 +9,7 @@ import chalk from 'chalk';
|
|
|
9
9
|
import { emitStoreRootBanner, isRootSelectionError, resolveOpenSpecRoot, toRootOutput, withStoreFlag, isStoreSelectedRoot, } from './root-selection.js';
|
|
10
10
|
import { findSpecUpdates, buildUpdatedSpec, writeUpdatedSpec, retireSpec, finalizeRetiredSpec, pruneEmptyDirs, } from './specs-apply.js';
|
|
11
11
|
import { discoverSpecFiles, findUnreadDeltaFiles, hasAnyFileUnder } from '../utils/spec-discovery.js';
|
|
12
|
-
import { METADATA_FILENAME, readRetireCapabilitiesMarker, readSkipSpecsMarker } from '../utils/change-metadata.js';
|
|
12
|
+
import { METADATA_FILENAME, formatUnknownChangeMetadataKeysMessage, readRetireCapabilitiesMarker, readSkipSpecsMarker, readUnknownChangeMetadataKeys, } from '../utils/change-metadata.js';
|
|
13
13
|
import { confirmPrompt, isNonInteractivePromptError } from '../utils/interactive.js';
|
|
14
14
|
import { FileSystemUtils } from '../utils/file-system.js';
|
|
15
15
|
import { folderStyleNameProblem } from './id.js';
|
|
@@ -1125,6 +1125,13 @@ export class ArchiveCommand {
|
|
|
1125
1125
|
if (nested) {
|
|
1126
1126
|
throw new ArchiveBlockedError('archive_change_is_namespace_folder', `Cannot archive '${changeName}': ${describeNestedChange(nested)}`, `Rename openspec/changes/${nested.nested[0]}/ to a flat change directory, then archive it.`);
|
|
1127
1127
|
}
|
|
1128
|
+
const unknownMetadataKeys = readUnknownChangeMetadataKeys(changeDir);
|
|
1129
|
+
const unknownMetadataWarning = unknownMetadataKeys.length > 0
|
|
1130
|
+
? formatUnknownChangeMetadataKeysMessage(unknownMetadataKeys)
|
|
1131
|
+
: undefined;
|
|
1132
|
+
if (unknownMetadataWarning && !json) {
|
|
1133
|
+
console.warn(chalk.yellow(unknownMetadataWarning));
|
|
1134
|
+
}
|
|
1128
1135
|
const skipValidation = options.validate === false || options.noValidate === true;
|
|
1129
1136
|
// Validate specs and change before archiving
|
|
1130
1137
|
if (!skipValidation) {
|
|
@@ -1827,7 +1834,13 @@ export class ArchiveCommand {
|
|
|
1827
1834
|
path: archivePath,
|
|
1828
1835
|
specsUpdated,
|
|
1829
1836
|
...(totals ? { totals } : {}),
|
|
1830
|
-
...(specWarnings.length > 0
|
|
1837
|
+
...(specWarnings.length > 0 || unknownMetadataWarning
|
|
1838
|
+
? {
|
|
1839
|
+
warnings: unknownMetadataWarning
|
|
1840
|
+
? [...specWarnings, unknownMetadataWarning]
|
|
1841
|
+
: specWarnings,
|
|
1842
|
+
}
|
|
1843
|
+
: {}),
|
|
1831
1844
|
};
|
|
1832
1845
|
}
|
|
1833
1846
|
finally {
|
|
@@ -32,6 +32,8 @@ export interface ChangeContext {
|
|
|
32
32
|
planningHome?: PlanningHome;
|
|
33
33
|
/** Parsed change metadata, when present */
|
|
34
34
|
metadata?: ChangeMetadata;
|
|
35
|
+
/** Non-fatal metadata diagnostics for text and JSON command surfaces */
|
|
36
|
+
warnings?: string[];
|
|
35
37
|
/**
|
|
36
38
|
* Artifact IDs counted as complete only because the change declares
|
|
37
39
|
* skip_specs, not because their files exist. Kept separate so status can
|
|
@@ -85,6 +87,8 @@ export interface ArtifactInstructions {
|
|
|
85
87
|
skipped?: boolean;
|
|
86
88
|
/** Present only when skipped: tells the consumer not to create the artifact */
|
|
87
89
|
warning?: string;
|
|
90
|
+
/** Non-fatal metadata diagnostics */
|
|
91
|
+
warnings?: string[];
|
|
88
92
|
}
|
|
89
93
|
/**
|
|
90
94
|
* Warning attached to instructions for an artifact skipped via skip_specs.
|
|
@@ -152,6 +156,8 @@ export interface ChangeStatus {
|
|
|
152
156
|
applyRequires: string[];
|
|
153
157
|
/** Status of each artifact */
|
|
154
158
|
artifacts: ArtifactStatus[];
|
|
159
|
+
/** Non-fatal metadata diagnostics */
|
|
160
|
+
warnings?: string[];
|
|
155
161
|
}
|
|
156
162
|
export interface ArtifactPathSummary {
|
|
157
163
|
outputPath: string;
|
|
@@ -211,5 +217,6 @@ export declare function generateInstructions(context: ChangeContext, artifactId:
|
|
|
211
217
|
*/
|
|
212
218
|
export declare function formatChangeStatus(context: ChangeContext, options?: {
|
|
213
219
|
storeId?: string;
|
|
220
|
+
implementationRoot?: string;
|
|
214
221
|
}): ChangeStatus;
|
|
215
222
|
//# sourceMappingURL=instruction-loader.d.ts.map
|
|
@@ -4,7 +4,7 @@ import { getSchemaDir, resolveSchema, listSchemasWithInfo } from './resolver.js'
|
|
|
4
4
|
import { ArtifactGraph } from './graph.js';
|
|
5
5
|
import { detectCompleted } from './state.js';
|
|
6
6
|
import { isSpecsArtifactPath, resolveArtifactOutputPath, resolveArtifactOutputs, } from './outputs.js';
|
|
7
|
-
import { readChangeMetadata, resolveSchemaForChange } from '../../utils/change-metadata.js';
|
|
7
|
+
import { formatUnknownChangeMetadataKeysMessage, readChangeMetadata, readUnknownChangeMetadataKeys, resolveSchemaForChange, } from '../../utils/change-metadata.js';
|
|
8
8
|
import { FileSystemUtils } from '../../utils/file-system.js';
|
|
9
9
|
import { buildActionContext, buildNextSteps, summarizePlanningHome, } from '../change-status-policy.js';
|
|
10
10
|
import { readProjectConfig, validateConfigRules } from '../project-config.js';
|
|
@@ -78,6 +78,10 @@ export function loadTemplate(schemaName, templatePath, projectRoot) {
|
|
|
78
78
|
export function loadChangeContext(projectRoot, changeName, schemaName, options = {}) {
|
|
79
79
|
const changeDir = FileSystemUtils.canonicalizeExistingPath(options.changeDir ?? path.join(projectRoot, 'openspec', 'changes', changeName));
|
|
80
80
|
const metadata = readChangeMetadata(changeDir, projectRoot) ?? undefined;
|
|
81
|
+
const unknownMetadataKeys = readUnknownChangeMetadataKeys(changeDir);
|
|
82
|
+
const warnings = unknownMetadataKeys.length > 0
|
|
83
|
+
? [formatUnknownChangeMetadataKeysMessage(unknownMetadataKeys)]
|
|
84
|
+
: [];
|
|
81
85
|
const resolvedSchemaName = resolveSchemaForChange(changeDir, schemaName, projectRoot, {
|
|
82
86
|
metadata: metadata ?? null,
|
|
83
87
|
projectConfig: options.projectConfig,
|
|
@@ -107,6 +111,7 @@ export function loadChangeContext(projectRoot, changeName, schemaName, options =
|
|
|
107
111
|
projectRoot,
|
|
108
112
|
...(options.planningHome ? { planningHome: options.planningHome } : {}),
|
|
109
113
|
...(metadata ? { metadata } : {}),
|
|
114
|
+
...(warnings.length > 0 ? { warnings } : {}),
|
|
110
115
|
...(skippedArtifacts.size > 0 ? { skippedArtifacts } : {}),
|
|
111
116
|
};
|
|
112
117
|
}
|
|
@@ -164,6 +169,7 @@ export function generateInstructions(context, artifactId, projectRoot, options =
|
|
|
164
169
|
context: configContext,
|
|
165
170
|
rules: configRules,
|
|
166
171
|
...(options.references !== undefined ? { references: options.references } : {}),
|
|
172
|
+
...(context.warnings ? { warnings: context.warnings } : {}),
|
|
167
173
|
...(context.skippedArtifacts?.has(artifact.id)
|
|
168
174
|
? { skipped: true, warning: SKIP_SPECS_INSTRUCTIONS_WARNING }
|
|
169
175
|
: {}),
|
|
@@ -279,8 +285,19 @@ export function formatChangeStatus(context, options = {}) {
|
|
|
279
285
|
actionContext: buildActionContext({
|
|
280
286
|
projectRoot: context.projectRoot,
|
|
281
287
|
artifactIds,
|
|
288
|
+
...(options.storeId
|
|
289
|
+
? {
|
|
290
|
+
store: {
|
|
291
|
+
id: options.storeId,
|
|
292
|
+
...(options.implementationRoot
|
|
293
|
+
? { implementationRoot: options.implementationRoot }
|
|
294
|
+
: {}),
|
|
295
|
+
},
|
|
296
|
+
}
|
|
297
|
+
: {}),
|
|
282
298
|
}),
|
|
283
299
|
artifacts: artifactStatuses,
|
|
300
|
+
...(context.warnings ? { warnings: context.warnings } : {}),
|
|
284
301
|
};
|
|
285
302
|
}
|
|
286
303
|
//# sourceMappingURL=instruction-loader.js.map
|
|
@@ -5,6 +5,9 @@ export declare const InitiativeLinkSchema: z.ZodObject<{
|
|
|
5
5
|
id: z.ZodString;
|
|
6
6
|
}, z.core.$strict>;
|
|
7
7
|
export type InitiativeLink = z.infer<typeof InitiativeLinkSchema>;
|
|
8
|
+
/** Top-level keys ChangeMetadataSchema recognizes. Anything else is ignored. */
|
|
9
|
+
export declare const CHANGE_METADATA_KNOWN_KEYS: readonly ["schema", "created", "goal", "affected_areas", "initiative", "skip_specs", "retire_capabilities"];
|
|
10
|
+
export type ChangeMetadataKnownKey = (typeof CHANGE_METADATA_KNOWN_KEYS)[number];
|
|
8
11
|
export declare const ChangeMetadataSchema: z.ZodObject<{
|
|
9
12
|
schema: z.ZodString;
|
|
10
13
|
created: z.ZodOptional<z.ZodString>;
|
|
@@ -13,6 +13,16 @@ export const InitiativeLinkSchema = z.object({
|
|
|
13
13
|
store: KebabIdentifierSchema('Store id'),
|
|
14
14
|
id: KebabIdentifierSchema('Initiative id'),
|
|
15
15
|
}).strict();
|
|
16
|
+
/** Top-level keys ChangeMetadataSchema recognizes. Anything else is ignored. */
|
|
17
|
+
export const CHANGE_METADATA_KNOWN_KEYS = [
|
|
18
|
+
'schema',
|
|
19
|
+
'created',
|
|
20
|
+
'goal',
|
|
21
|
+
'affected_areas',
|
|
22
|
+
'initiative',
|
|
23
|
+
'skip_specs',
|
|
24
|
+
'retire_capabilities',
|
|
25
|
+
];
|
|
16
26
|
// Per-change metadata schema. The schema field is validated against available
|
|
17
27
|
// workflow schemas when metadata is read or written.
|
|
18
28
|
export const ChangeMetadataSchema = z.object({
|
|
@@ -30,6 +30,14 @@ export interface ChangeNextStepsInput {
|
|
|
30
30
|
export interface ActionContextInput {
|
|
31
31
|
projectRoot: string;
|
|
32
32
|
artifactIds: string[];
|
|
33
|
+
/**
|
|
34
|
+
* Set when the root is a store: the store holds the planning artifacts,
|
|
35
|
+
* and `implementationRoot` is the project that declares it, if any.
|
|
36
|
+
*/
|
|
37
|
+
store?: {
|
|
38
|
+
id: string;
|
|
39
|
+
implementationRoot?: string;
|
|
40
|
+
};
|
|
33
41
|
}
|
|
34
42
|
export declare function summarizePlanningHome(planningHome: PlanningHome | undefined): PlanningHomeSummary | undefined;
|
|
35
43
|
export declare function buildActionContext(input: ActionContextInput): ActionContext;
|
|
@@ -10,14 +10,45 @@ export function summarizePlanningHome(planningHome) {
|
|
|
10
10
|
};
|
|
11
11
|
}
|
|
12
12
|
export function buildActionContext(input) {
|
|
13
|
+
const scope = editScope(input);
|
|
14
|
+
// Keys stay in the published contract order.
|
|
13
15
|
return {
|
|
14
16
|
mode: 'repo-local',
|
|
15
17
|
sourceOfTruth: 'repo',
|
|
16
18
|
planningArtifacts: input.artifactIds,
|
|
17
19
|
linkedContext: [],
|
|
18
|
-
allowedEditRoots:
|
|
20
|
+
allowedEditRoots: scope.allowedEditRoots,
|
|
19
21
|
requiresAffectedAreaSelection: false,
|
|
20
|
-
constraints:
|
|
22
|
+
constraints: scope.constraints,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* A store holds planning artifacts only. The CLI does not route tasks to
|
|
27
|
+
* repos, so it names the declaring project on the current path as the edit
|
|
28
|
+
* root and has the agent ask before going anywhere else (#2013).
|
|
29
|
+
*/
|
|
30
|
+
function editScope(input) {
|
|
31
|
+
if (!input.store) {
|
|
32
|
+
return {
|
|
33
|
+
allowedEditRoots: [input.projectRoot],
|
|
34
|
+
constraints: ['Repo-local change artifacts and implementation edits are scoped to this project.'],
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
const planning = `Change artifacts live in store '${input.store.id}' (${input.projectRoot}).`;
|
|
38
|
+
const { implementationRoot } = input.store;
|
|
39
|
+
if (implementationRoot) {
|
|
40
|
+
return {
|
|
41
|
+
allowedEditRoots: [implementationRoot, input.projectRoot],
|
|
42
|
+
constraints: [
|
|
43
|
+
`${planning} Implementation edits go in ${implementationRoot}, the project on the current path that declares this store; ask the user before editing any other repository.`,
|
|
44
|
+
],
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
allowedEditRoots: [input.projectRoot],
|
|
49
|
+
constraints: [
|
|
50
|
+
`${planning} OpenSpec could not determine which repository implements this change; ask the user which repository to edit, and make implementation edits there.`,
|
|
51
|
+
],
|
|
21
52
|
};
|
|
22
53
|
}
|
|
23
54
|
export function resolveNextStep(input) {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AtomCode Command Adapter
|
|
3
|
+
*
|
|
4
|
+
* Formats project commands for AtomCode.
|
|
5
|
+
* https://github.com/atomgit-atomcode/atomcode#custom-commands
|
|
6
|
+
*/
|
|
7
|
+
import type { ToolCommandAdapter } from '../types.js';
|
|
8
|
+
/**
|
|
9
|
+
* AtomCode adapter for command generation.
|
|
10
|
+
* File path: .atomcode/commands/opsx-<id>.md
|
|
11
|
+
* Frontmatter: name, description, args
|
|
12
|
+
*
|
|
13
|
+
* AtomCode's custom-command parser reads name and args literally without YAML
|
|
14
|
+
* unquoting, so these controlled identifiers must stay unquoted.
|
|
15
|
+
* The command name matches the filename.
|
|
16
|
+
*
|
|
17
|
+
* `args` mirrors what the workflow actually accepts. AtomCode executes an
|
|
18
|
+
* `args: none` command straight from the slash menu, while `optional` completes
|
|
19
|
+
* to `/name ` and waits for a second Enter. Advertising arguments a workflow
|
|
20
|
+
* never reads would cost every user that extra keystroke, so only workflows
|
|
21
|
+
* carrying an `**Input**:` contract declare `optional` and receive $ARGUMENTS.
|
|
22
|
+
*/
|
|
23
|
+
export declare const atomcodeAdapter: ToolCommandAdapter;
|
|
24
|
+
//# sourceMappingURL=atomcode.d.ts.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AtomCode Command Adapter
|
|
3
|
+
*
|
|
4
|
+
* Formats project commands for AtomCode.
|
|
5
|
+
* https://github.com/atomgit-atomcode/atomcode#custom-commands
|
|
6
|
+
*/
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import { stringify } from 'yaml';
|
|
9
|
+
/** A workflow declares its invocation input with an `**Input**:` heading. */
|
|
10
|
+
const INPUT_HEADING = /^\*\*Input\*\*:/m;
|
|
11
|
+
/**
|
|
12
|
+
* AtomCode adapter for command generation.
|
|
13
|
+
* File path: .atomcode/commands/opsx-<id>.md
|
|
14
|
+
* Frontmatter: name, description, args
|
|
15
|
+
*
|
|
16
|
+
* AtomCode's custom-command parser reads name and args literally without YAML
|
|
17
|
+
* unquoting, so these controlled identifiers must stay unquoted.
|
|
18
|
+
* The command name matches the filename.
|
|
19
|
+
*
|
|
20
|
+
* `args` mirrors what the workflow actually accepts. AtomCode executes an
|
|
21
|
+
* `args: none` command straight from the slash menu, while `optional` completes
|
|
22
|
+
* to `/name ` and waits for a second Enter. Advertising arguments a workflow
|
|
23
|
+
* never reads would cost every user that extra keystroke, so only workflows
|
|
24
|
+
* carrying an `**Input**:` contract declare `optional` and receive $ARGUMENTS.
|
|
25
|
+
*/
|
|
26
|
+
export const atomcodeAdapter = {
|
|
27
|
+
toolId: 'atomcode',
|
|
28
|
+
getFilePath(commandId) {
|
|
29
|
+
return path.join('.atomcode', 'commands', `opsx-${commandId}.md`);
|
|
30
|
+
},
|
|
31
|
+
formatFile(content) {
|
|
32
|
+
// Keep ordinary descriptions plain for the literal custom-command parser,
|
|
33
|
+
// while keeping special values valid YAML for frontmatter consumers.
|
|
34
|
+
const description = stringify({ description: content.description }, { lineWidth: 0, blockQuote: false });
|
|
35
|
+
const acceptsInput = INPUT_HEADING.test(content.body);
|
|
36
|
+
const argumentsBlock = acceptsInput ? '\n**Provided arguments**: $ARGUMENTS\n' : '';
|
|
37
|
+
return `---
|
|
38
|
+
name: opsx-${content.id}
|
|
39
|
+
${description}args: ${acceptsInput ? 'optional' : 'none'}
|
|
40
|
+
---
|
|
41
|
+
${argumentsBlock}
|
|
42
|
+
${content.body}
|
|
43
|
+
`;
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
//# sourceMappingURL=atomcode.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code Studio Command Adapter
|
|
3
|
+
*
|
|
4
|
+
* Formats commands for Syncfusion Code Studio following its .prompt.md specification.
|
|
5
|
+
*/
|
|
6
|
+
import type { ToolCommandAdapter } from '../types.js';
|
|
7
|
+
/**
|
|
8
|
+
* Code Studio adapter for command generation.
|
|
9
|
+
* File path: .codestudio/prompts/opsx-<id>.prompt.md
|
|
10
|
+
* Frontmatter: description
|
|
11
|
+
*/
|
|
12
|
+
export declare const codeStudioAdapter: ToolCommandAdapter;
|
|
13
|
+
//# sourceMappingURL=codestudio.d.ts.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code Studio Command Adapter
|
|
3
|
+
*
|
|
4
|
+
* Formats commands for Syncfusion Code Studio following its .prompt.md specification.
|
|
5
|
+
*/
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { escapeYamlValue } from '../yaml.js';
|
|
8
|
+
/**
|
|
9
|
+
* Code Studio adapter for command generation.
|
|
10
|
+
* File path: .codestudio/prompts/opsx-<id>.prompt.md
|
|
11
|
+
* Frontmatter: description
|
|
12
|
+
*/
|
|
13
|
+
export const codeStudioAdapter = {
|
|
14
|
+
toolId: 'codestudio',
|
|
15
|
+
getFilePath(commandId) {
|
|
16
|
+
return path.join('.codestudio', 'prompts', `opsx-${commandId}.prompt.md`);
|
|
17
|
+
},
|
|
18
|
+
formatFile(content) {
|
|
19
|
+
return `---
|
|
20
|
+
description: ${escapeYamlValue(content.description)}
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
${content.body}
|
|
24
|
+
`;
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
//# sourceMappingURL=codestudio.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EasyCode Command Adapter
|
|
3
|
+
*
|
|
4
|
+
* Formats commands for OrionStarAI/EasyCode using its TOML command format.
|
|
5
|
+
* https://github.com/OrionStarAI/EasyCode
|
|
6
|
+
*/
|
|
7
|
+
import type { ToolCommandAdapter } from '../types.js';
|
|
8
|
+
/**
|
|
9
|
+
* EasyCode adapter for command generation.
|
|
10
|
+
* File path: .easycode/commands/opsx/<id>.toml
|
|
11
|
+
*
|
|
12
|
+
* Format:
|
|
13
|
+
* description = "<basic-string>" single-line, backslash/quote-safe
|
|
14
|
+
* prompt = """<multiline-string>""" multiline, backslash/triple-quote-safe
|
|
15
|
+
*/
|
|
16
|
+
export declare const easycodeAdapter: ToolCommandAdapter;
|
|
17
|
+
//# sourceMappingURL=easycode.d.ts.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EasyCode Command Adapter
|
|
3
|
+
*
|
|
4
|
+
* Formats commands for OrionStarAI/EasyCode using its TOML command format.
|
|
5
|
+
* https://github.com/OrionStarAI/EasyCode
|
|
6
|
+
*/
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import { escapeTomlBasicString, escapeTomlMultilineBasicString } from '../toml.js';
|
|
9
|
+
/**
|
|
10
|
+
* EasyCode adapter for command generation.
|
|
11
|
+
* File path: .easycode/commands/opsx/<id>.toml
|
|
12
|
+
*
|
|
13
|
+
* Format:
|
|
14
|
+
* description = "<basic-string>" single-line, backslash/quote-safe
|
|
15
|
+
* prompt = """<multiline-string>""" multiline, backslash/triple-quote-safe
|
|
16
|
+
*/
|
|
17
|
+
export const easycodeAdapter = {
|
|
18
|
+
toolId: 'easycode',
|
|
19
|
+
getFilePath(commandId) {
|
|
20
|
+
return path.join('.easycode', 'commands', 'opsx', `${commandId}.toml`);
|
|
21
|
+
},
|
|
22
|
+
formatFile(content) {
|
|
23
|
+
const safeDesc = escapeTomlBasicString(content.description);
|
|
24
|
+
const safeBody = escapeTomlMultilineBasicString(content.body);
|
|
25
|
+
return `description = "${safeDesc}"\n\nprompt = """\n${safeBody}\n"""\n`;
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
//# sourceMappingURL=easycode.js.map
|
|
@@ -4,41 +4,7 @@
|
|
|
4
4
|
* Formats commands for Gemini CLI following its TOML specification.
|
|
5
5
|
*/
|
|
6
6
|
import path from 'path';
|
|
7
|
-
|
|
8
|
-
* Control characters (C0 except tab/newline/carriage return, plus DEL) are
|
|
9
|
-
* invalid inside TOML strings and must be written as escapes.
|
|
10
|
-
*/
|
|
11
|
-
const TOML_CONTROL_CHARS = new RegExp('[\\u0000-\\u0008\\u000b\\u000c\\u000e-\\u001f\\u007f]', 'g');
|
|
12
|
-
/**
|
|
13
|
-
* TOML basic strings are escape-active: a backslash or double quote in the
|
|
14
|
-
* value breaks the file if written raw. Newlines cannot appear in a
|
|
15
|
-
* single-line basic string at all, so they are escaped too.
|
|
16
|
-
*/
|
|
17
|
-
function escapeTomlBasicString(value) {
|
|
18
|
-
return value
|
|
19
|
-
.replace(/\\/g, '\\\\')
|
|
20
|
-
.replace(/"/g, '\\"')
|
|
21
|
-
.replace(/\n/g, '\\n')
|
|
22
|
-
.replace(/\r/g, '\\r')
|
|
23
|
-
.replace(/\t/g, '\\t')
|
|
24
|
-
.replace(TOML_CONTROL_CHARS, (c) => `\\u${c.charCodeAt(0).toString(16).padStart(4, '0')}`);
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Multiline basic strings keep raw newlines and tabs, but backslashes are
|
|
28
|
-
* still escape-active, any run of three quotes would end the string, and the
|
|
29
|
-
* same control characters are invalid as in single-line basic strings — a
|
|
30
|
-
* lone carriage return included (only LF and CRLF may appear raw; CRLF is
|
|
31
|
-
* normalized away so the emitted file is single-convention). Escapes are
|
|
32
|
-
* introduced after backslash-doubling so they are not re-doubled.
|
|
33
|
-
*/
|
|
34
|
-
function escapeTomlMultilineBasicString(value) {
|
|
35
|
-
return value
|
|
36
|
-
.replace(/\r\n/g, '\n')
|
|
37
|
-
.replace(/\\/g, '\\\\')
|
|
38
|
-
.replace(/"""/g, '""\\"')
|
|
39
|
-
.replace(/\r/g, '\\r')
|
|
40
|
-
.replace(TOML_CONTROL_CHARS, (c) => `\\u${c.charCodeAt(0).toString(16).padStart(4, '0')}`);
|
|
41
|
-
}
|
|
7
|
+
import { escapeTomlBasicString, escapeTomlMultilineBasicString } from '../toml.js';
|
|
42
8
|
/**
|
|
43
9
|
* Gemini adapter for command generation.
|
|
44
10
|
* File path: .gemini/commands/opsx/<id>.toml
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GigaCode Command Adapter
|
|
3
|
+
*
|
|
4
|
+
* Formats commands for GigaCode using its Markdown custom command format.
|
|
5
|
+
* Project commands live in `.gigacode/commands/` and accept an optional
|
|
6
|
+
* `description` field in YAML frontmatter.
|
|
7
|
+
*
|
|
8
|
+
* @see https://gitverse.ru/docs/ai/ai-assistant-gigacode/gigacode-cli/commands
|
|
9
|
+
*/
|
|
10
|
+
import type { ToolCommandAdapter } from '../types.js';
|
|
11
|
+
/**
|
|
12
|
+
* GigaCode adapter for command generation.
|
|
13
|
+
* File path: .gigacode/commands/opsx-<id>.md
|
|
14
|
+
* Format: Markdown with description frontmatter
|
|
15
|
+
*/
|
|
16
|
+
export declare const gigacodeAdapter: ToolCommandAdapter;
|
|
17
|
+
//# sourceMappingURL=gigacode.d.ts.map
|