@fission-ai/openspec 1.13.2 → 1.14.1
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/commands/config.d.ts +8 -0
- package/dist/cli/commands/config.js +88 -0
- package/dist/cli/commands/context.d.ts +3 -0
- package/dist/cli/commands/context.js +20 -0
- package/dist/cli/commands/doctor.d.ts +3 -0
- package/dist/cli/commands/doctor.js +18 -0
- package/dist/cli/commands/schema.d.ts +6 -0
- package/dist/cli/commands/schema.js +57 -0
- package/dist/cli/commands/spec.d.ts +3 -0
- package/dist/cli/commands/spec.js +42 -0
- package/dist/cli/commands/store.d.ts +3 -0
- package/dist/cli/commands/store.js +130 -0
- package/dist/cli/commands/workset.d.ts +3 -0
- package/dist/cli/commands/workset.js +96 -0
- package/dist/cli/index.js +106 -43
- package/dist/commands/config.d.ts +16 -7
- package/dist/commands/config.js +342 -395
- package/dist/commands/context.d.ts +8 -2
- package/dist/commands/context.js +22 -37
- package/dist/commands/doctor.d.ts +6 -7
- package/dist/commands/doctor.js +14 -27
- package/dist/commands/schema.d.ts +19 -5
- package/dist/commands/schema.js +662 -702
- package/dist/commands/spec.d.ts +13 -4
- package/dist/commands/spec.js +92 -120
- package/dist/commands/store.d.ts +27 -2
- package/dist/commands/store.js +1 -120
- package/dist/commands/workflow/default-schema.d.ts +2 -0
- package/dist/commands/workflow/default-schema.js +4 -0
- package/dist/commands/workflow/instructions.js +32 -12
- package/dist/commands/workflow/shared.d.ts +3 -1
- package/dist/commands/workflow/shared.js +1 -1
- package/dist/commands/workflow/status.js +15 -2
- package/dist/commands/workset.d.ts +22 -2
- package/dist/commands/workset.js +1 -88
- package/dist/core/archive.js +28 -7
- 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/completion-tip.js +3 -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-schema.d.ts +1 -1
- 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 +9 -5
- package/dist/core/templates/workflows/archive-change.js +26 -4
- package/dist/core/templates/workflows/bulk-archive-change.js +6 -2
- package/dist/core/templates/workflows/continue-change.js +17 -0
- package/dist/core/templates/workflows/verify-change.js +12 -12
- package/dist/core/validation/constants.d.ts +1 -1
- package/dist/core/validation/constants.js +1 -1
- package/dist/core/validation/validator.js +21 -2
- 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 +36 -2
- 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 +29 -4
- package/schemas/spec-driven/templates/proposal.md +6 -3
package/dist/commands/workset.js
CHANGED
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import * as os from 'node:os';
|
|
10
10
|
import { createRequire } from 'node:module';
|
|
11
|
-
import { Option } from 'commander';
|
|
12
11
|
import { buildWorksetCodeWorkspaceJson, getWorkset, getWorksetCodeWorkspacePath, listWorksets, readWorksetsState, removeWorkset, updateWorksetsState, validateWorksetName, withWorkset, withWorksetsLock, worksetNotFoundError, } from '../core/worksets.js';
|
|
13
12
|
import { buildLaunchCommand, findOpener, isOpenerCommandAvailable, isOpenerEnabled, listOpenerChoices, mergeOpenerTable, } from '../core/openers.js';
|
|
14
13
|
import { pathIsDirectory, writeFileAtomically } from '../core/file-state.js';
|
|
@@ -18,7 +17,6 @@ import { isInteractive } from '../utils/interactive.js';
|
|
|
18
17
|
import { asErrorMessage, emitFailure, isPromptCancellationError, printJson, } from './shared-output.js';
|
|
19
18
|
import { finalizeWorkset, firstInstalledAlternative, formatMemberRows, noToolInstalledError, resolveMemberFlags, toolUnavailableError, toolUnknownError, } from './workset-input.js';
|
|
20
19
|
import { composeInteractively, confirmRemoveInteractively, promptOpenNow, promptToolFromChoices, } from './workset-prompts.js';
|
|
21
|
-
import { COMMAND_REGISTRY } from '../core/completions/command-registry.js';
|
|
22
20
|
// cross-spawn is CJS with no types and only `workset open` needs it -
|
|
23
21
|
// loaded lazily so every other CLI invocation skips its module graph.
|
|
24
22
|
let cachedSpawn;
|
|
@@ -93,7 +91,7 @@ export function exitCodeForLaunch(result) {
|
|
|
93
91
|
}
|
|
94
92
|
return result.code ?? 0;
|
|
95
93
|
}
|
|
96
|
-
class WorksetCommand {
|
|
94
|
+
export class WorksetCommand {
|
|
97
95
|
async create(name, options = {}) {
|
|
98
96
|
try {
|
|
99
97
|
const interactive = !options.json && isInteractive();
|
|
@@ -358,89 +356,4 @@ class WorksetCommand {
|
|
|
358
356
|
}
|
|
359
357
|
}
|
|
360
358
|
}
|
|
361
|
-
function collectMember(value, previous) {
|
|
362
|
-
return [...previous, value];
|
|
363
|
-
}
|
|
364
|
-
export function registerWorksetCommand(program) {
|
|
365
|
-
const worksetCommand = new WorksetCommand();
|
|
366
|
-
const groupDescription = COMMAND_REGISTRY.find((entry) => entry.name === 'workset')?.description ??
|
|
367
|
-
'Compose, keep, and open personal working views (purely local)';
|
|
368
|
-
const workset = program.command('workset').description(groupDescription);
|
|
369
|
-
// Parsed at the group level so `openspec workset --json` keeps the
|
|
370
|
-
// one-JSON-document contract instead of a raw Commander error. The
|
|
371
|
-
// parent option matches anywhere; actions read optsWithGlobals().
|
|
372
|
-
workset.addOption(new Option('--json', 'Output as JSON').hideHelp());
|
|
373
|
-
workset
|
|
374
|
-
.command('create [name]')
|
|
375
|
-
.description('Compose and save a named working view of folders you choose')
|
|
376
|
-
.option('--member <member>', 'Member folder as <path> or <name>=<path>; repeatable, first is the primary', collectMember, [])
|
|
377
|
-
.option('--tool <id>', 'Preferred tool to open this workset with')
|
|
378
|
-
.option('--json', 'Output as JSON')
|
|
379
|
-
.action(async (name, _options, command) => {
|
|
380
|
-
await worksetCommand.create(name, command.optsWithGlobals());
|
|
381
|
-
});
|
|
382
|
-
workset
|
|
383
|
-
.command('list')
|
|
384
|
-
.alias('ls')
|
|
385
|
-
.description('Show saved worksets with their members')
|
|
386
|
-
.option('--json', 'Output as JSON')
|
|
387
|
-
.action(async (_options, command) => {
|
|
388
|
-
await worksetCommand.list(command.optsWithGlobals());
|
|
389
|
-
});
|
|
390
|
-
workset
|
|
391
|
-
.command('open <name>')
|
|
392
|
-
.description('Open a saved workset in your tool (editor window or agent session)')
|
|
393
|
-
.option('--tool <id>', 'Open with this tool just this once')
|
|
394
|
-
.addOption(
|
|
395
|
-
// Parsed so Commander never owns the error; rejected in the
|
|
396
|
-
// action with one JSON document. Hidden because help should not
|
|
397
|
-
// advertise a mode that only rejects.
|
|
398
|
-
new Option('--json', 'Not supported for open').hideHelp())
|
|
399
|
-
.action(async (name, _options, command) => {
|
|
400
|
-
await worksetCommand.open(name, command.optsWithGlobals());
|
|
401
|
-
});
|
|
402
|
-
workset
|
|
403
|
-
.command('remove <name>')
|
|
404
|
-
.description('Delete a saved workset (member folders are never touched)')
|
|
405
|
-
.option('--yes', 'Confirm removal non-interactively')
|
|
406
|
-
.option('--json', 'Output as JSON')
|
|
407
|
-
.action(async (name, _options, command) => {
|
|
408
|
-
await worksetCommand.remove(name, command.optsWithGlobals());
|
|
409
|
-
});
|
|
410
|
-
const subcommandsLine = workset.commands
|
|
411
|
-
.map((subcommand) => {
|
|
412
|
-
const aliases = subcommand.aliases();
|
|
413
|
-
return aliases.length > 0
|
|
414
|
-
? `${subcommand.name()} (${aliases.join(', ')})`
|
|
415
|
-
: subcommand.name();
|
|
416
|
-
})
|
|
417
|
-
.join(', ');
|
|
418
|
-
// One handler owns missing AND unknown subcommands: known
|
|
419
|
-
// subcommands dispatch above; everything else lands in this action
|
|
420
|
-
// (allowExcessArguments routes the unknown operand here), keeping
|
|
421
|
-
// the one-JSON-document contract for `--json` probes.
|
|
422
|
-
workset.allowExcessArguments(true);
|
|
423
|
-
workset.action(() => {
|
|
424
|
-
const attempted = workset.args.filter((operand) => !operand.startsWith('-'));
|
|
425
|
-
const message = attempted.length > 0
|
|
426
|
-
? `Unknown command '${attempted[0]}' for 'openspec workset'. Workset subcommands: ${subcommandsLine}.`
|
|
427
|
-
: `Missing subcommand for 'openspec workset'. Workset subcommands: ${subcommandsLine}.`;
|
|
428
|
-
if (workset.opts().json) {
|
|
429
|
-
printJson({
|
|
430
|
-
status: [
|
|
431
|
-
{
|
|
432
|
-
severity: 'error',
|
|
433
|
-
code: 'unknown_workset_subcommand',
|
|
434
|
-
message,
|
|
435
|
-
fix: 'Run one of the workset subcommands.',
|
|
436
|
-
},
|
|
437
|
-
],
|
|
438
|
-
});
|
|
439
|
-
}
|
|
440
|
-
else {
|
|
441
|
-
console.error(`Error: ${message}`);
|
|
442
|
-
}
|
|
443
|
-
process.exitCode = 1;
|
|
444
|
-
});
|
|
445
|
-
}
|
|
446
359
|
//# sourceMappingURL=workset.js.map
|
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';
|
|
@@ -247,6 +247,14 @@ function toArchiveDiagnostic(error) {
|
|
|
247
247
|
if (isRootSelectionError(error)) {
|
|
248
248
|
return error.diagnostic;
|
|
249
249
|
}
|
|
250
|
+
if (error instanceof RetirementCleanupError) {
|
|
251
|
+
return {
|
|
252
|
+
severity: 'error',
|
|
253
|
+
code: 'archive_retirement_cleanup_failed',
|
|
254
|
+
message: error.message,
|
|
255
|
+
fix: 'Inspect the archived change and all recovery paths in this diagnostic; preserve any needed content before cleanup.',
|
|
256
|
+
};
|
|
257
|
+
}
|
|
250
258
|
return {
|
|
251
259
|
severity: 'error',
|
|
252
260
|
code: 'archive_error',
|
|
@@ -379,7 +387,7 @@ async function assertCopiedDirectoryUnchanged(stagedSource, destination, expecte
|
|
|
379
387
|
*/
|
|
380
388
|
class MoveDestinationRetainedError extends Error {
|
|
381
389
|
}
|
|
382
|
-
class
|
|
390
|
+
class RetirementCleanupError extends Error {
|
|
383
391
|
}
|
|
384
392
|
function isFallbackRenameCode(code) {
|
|
385
393
|
return code === 'EPERM' || code === 'EXDEV';
|
|
@@ -1012,12 +1020,12 @@ async function finalizeRetirementBackups(snapshots, mainSpecsDir) {
|
|
|
1012
1020
|
snapshot.displacedPath = undefined;
|
|
1013
1021
|
}
|
|
1014
1022
|
catch (error) {
|
|
1015
|
-
errors.push(`Could not
|
|
1023
|
+
errors.push(`Could not finalize the retirement backup at ${displacedPath} ` +
|
|
1016
1024
|
`(${error instanceof Error ? error.message : String(error)}).`);
|
|
1017
1025
|
}
|
|
1018
1026
|
}
|
|
1019
1027
|
if (errors.length > 0) {
|
|
1020
|
-
throw new
|
|
1028
|
+
throw new RetirementCleanupError(`${errors.join(' ')} The change was archived, but retirement cleanup did not complete. Inspect all reported recovery paths before recovery or cleanup.`);
|
|
1021
1029
|
}
|
|
1022
1030
|
}
|
|
1023
1031
|
export class ArchiveCommand {
|
|
@@ -1125,6 +1133,13 @@ export class ArchiveCommand {
|
|
|
1125
1133
|
if (nested) {
|
|
1126
1134
|
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
1135
|
}
|
|
1136
|
+
const unknownMetadataKeys = readUnknownChangeMetadataKeys(changeDir);
|
|
1137
|
+
const unknownMetadataWarning = unknownMetadataKeys.length > 0
|
|
1138
|
+
? formatUnknownChangeMetadataKeysMessage(unknownMetadataKeys)
|
|
1139
|
+
: undefined;
|
|
1140
|
+
if (unknownMetadataWarning && !json) {
|
|
1141
|
+
console.warn(chalk.yellow(unknownMetadataWarning));
|
|
1142
|
+
}
|
|
1128
1143
|
const skipValidation = options.validate === false || options.noValidate === true;
|
|
1129
1144
|
// Validate specs and change before archiving
|
|
1130
1145
|
if (!skipValidation) {
|
|
@@ -1770,11 +1785,11 @@ export class ArchiveCommand {
|
|
|
1770
1785
|
await finalizeRetirementBackups(specSnapshots, mainSpecsDir);
|
|
1771
1786
|
}
|
|
1772
1787
|
catch (cleanupError) {
|
|
1773
|
-
throw new
|
|
1788
|
+
throw new RetirementCleanupError(`${error.message} ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`);
|
|
1774
1789
|
}
|
|
1775
1790
|
throw error;
|
|
1776
1791
|
}
|
|
1777
|
-
if (error instanceof
|
|
1792
|
+
if (error instanceof RetirementCleanupError)
|
|
1778
1793
|
throw error;
|
|
1779
1794
|
const rollbackErrors = [];
|
|
1780
1795
|
try {
|
|
@@ -1827,7 +1842,13 @@ export class ArchiveCommand {
|
|
|
1827
1842
|
path: archivePath,
|
|
1828
1843
|
specsUpdated,
|
|
1829
1844
|
...(totals ? { totals } : {}),
|
|
1830
|
-
...(specWarnings.length > 0
|
|
1845
|
+
...(specWarnings.length > 0 || unknownMetadataWarning
|
|
1846
|
+
? {
|
|
1847
|
+
warnings: unknownMetadataWarning
|
|
1848
|
+
? [...specWarnings, unknownMetadataWarning]
|
|
1849
|
+
: specWarnings,
|
|
1850
|
+
}
|
|
1851
|
+
: {}),
|
|
1831
1852
|
};
|
|
1832
1853
|
}
|
|
1833
1854
|
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
|
|
@@ -0,0 +1,31 @@
|
|
|
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 path from 'path';
|
|
11
|
+
import { escapeYamlValue } from '../yaml.js';
|
|
12
|
+
/**
|
|
13
|
+
* GigaCode adapter for command generation.
|
|
14
|
+
* File path: .gigacode/commands/opsx-<id>.md
|
|
15
|
+
* Format: Markdown with description frontmatter
|
|
16
|
+
*/
|
|
17
|
+
export const gigacodeAdapter = {
|
|
18
|
+
toolId: 'gigacode',
|
|
19
|
+
getFilePath(commandId) {
|
|
20
|
+
return path.join('.gigacode', 'commands', `opsx-${commandId}.md`);
|
|
21
|
+
},
|
|
22
|
+
formatFile(content) {
|
|
23
|
+
return `---
|
|
24
|
+
description: ${escapeYamlValue(content.description)}
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
${content.body}
|
|
28
|
+
`;
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
//# sourceMappingURL=gigacode.js.map
|