@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.
Files changed (65) hide show
  1. package/dist/cli/index.js +39 -1
  2. package/dist/commands/spec.js +1 -0
  3. package/dist/commands/workflow/instructions.js +31 -11
  4. package/dist/commands/workflow/shared.d.ts +2 -0
  5. package/dist/commands/workflow/status.js +15 -2
  6. package/dist/core/archive.js +15 -2
  7. package/dist/core/artifact-graph/instruction-loader.d.ts +7 -0
  8. package/dist/core/artifact-graph/instruction-loader.js +18 -1
  9. package/dist/core/change-metadata/schema.d.ts +3 -0
  10. package/dist/core/change-metadata/schema.js +10 -0
  11. package/dist/core/change-status-policy.d.ts +8 -0
  12. package/dist/core/change-status-policy.js +33 -2
  13. package/dist/core/command-generation/adapters/atomcode.d.ts +24 -0
  14. package/dist/core/command-generation/adapters/atomcode.js +46 -0
  15. package/dist/core/command-generation/adapters/codestudio.d.ts +13 -0
  16. package/dist/core/command-generation/adapters/codestudio.js +27 -0
  17. package/dist/core/command-generation/adapters/easycode.d.ts +17 -0
  18. package/dist/core/command-generation/adapters/easycode.js +28 -0
  19. package/dist/core/command-generation/adapters/gemini.js +1 -35
  20. package/dist/core/command-generation/adapters/gigacode.d.ts +17 -0
  21. package/dist/core/command-generation/adapters/gigacode.js +31 -0
  22. package/dist/core/command-generation/adapters/index.d.ts +4 -0
  23. package/dist/core/command-generation/adapters/index.js +4 -0
  24. package/dist/core/command-generation/registry.js +8 -0
  25. package/dist/core/command-generation/toml.d.ts +19 -0
  26. package/dist/core/command-generation/toml.js +39 -0
  27. package/dist/core/command-surface.js +1 -1
  28. package/dist/core/completions/command-registry.js +19 -0
  29. package/dist/core/completions/installers/zsh-installer.js +4 -2
  30. package/dist/core/config-prompts.js +6 -6
  31. package/dist/core/config.js +11 -1
  32. package/dist/core/init.js +35 -11
  33. package/dist/core/legacy-cleanup.js +11 -7
  34. package/dist/core/list.d.ts +2 -0
  35. package/dist/core/list.js +49 -23
  36. package/dist/core/parsers/markdown-parser.js +5 -0
  37. package/dist/core/parsers/requirement-blocks.d.ts +6 -0
  38. package/dist/core/parsers/requirement-blocks.js +9 -2
  39. package/dist/core/project-config.js +45 -1
  40. package/dist/core/references.js +3 -1
  41. package/dist/core/root-selection.d.ts +6 -0
  42. package/dist/core/root-selection.js +16 -0
  43. package/dist/core/schemas/base.schema.d.ts +3 -0
  44. package/dist/core/schemas/base.schema.js +6 -0
  45. package/dist/core/schemas/change.schema.d.ts +8 -0
  46. package/dist/core/schemas/spec.schema.d.ts +2 -0
  47. package/dist/core/templates/workflows/apply-change.js +5 -2
  48. package/dist/core/templates/workflows/archive-change.js +26 -4
  49. package/dist/core/templates/workflows/bulk-archive-change.js +6 -2
  50. package/dist/core/validation/constants.d.ts +1 -1
  51. package/dist/core/validation/constants.js +1 -1
  52. package/dist/core/validation/validator.js +9 -1
  53. package/dist/core/version-check.d.ts +23 -1
  54. package/dist/core/version-check.js +112 -26
  55. package/dist/core/view.d.ts +2 -0
  56. package/dist/core/view.js +62 -4
  57. package/dist/utils/change-metadata.d.ts +20 -1
  58. package/dist/utils/change-metadata.js +57 -1
  59. package/dist/utils/command-references.d.ts +1 -1
  60. package/dist/utils/command-references.js +2 -2
  61. package/dist/utils/index.d.ts +1 -1
  62. package/dist/utils/index.js +1 -1
  63. package/package.json +1 -1
  64. package/schemas/spec-driven/schema.yaml +2 -1
  65. 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
  }
@@ -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
- // Apply Instructions Command
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(...parseTaskLines(tasksContent));
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 = await collectApplyWarnings({
487
- state,
488
- schema,
489
- changeDir,
490
- changeName,
491
- skippedArtifacts: context.skippedArtifacts,
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,
@@ -16,6 +16,8 @@ export interface TaskItem {
16
16
  id: string;
17
17
  description: string;
18
18
  done: boolean;
19
+ sourcePath: string;
20
+ line: number;
19
21
  }
20
22
  export interface ApplyInstructions {
21
23
  changeName: string;
@@ -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
- }), storeOptions);
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
  }
@@ -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 ? { warnings: specWarnings } : {}),
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: [input.projectRoot],
20
+ allowedEditRoots: scope.allowedEditRoots,
19
21
  requiresAffectedAreaSelection: false,
20
- constraints: ['Repo-local change artifacts and implementation edits are scoped to this project.'],
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