@dynamicworks/br-openspec 1.3.1 → 2.0.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.
Files changed (72) hide show
  1. package/LICENSE +22 -22
  2. package/README.md +210 -210
  3. package/README.pt-BR.md +212 -212
  4. package/bin/openspec.js +2 -2
  5. package/dist/commands/feedback.js +4 -4
  6. package/dist/commands/schema.js +60 -60
  7. package/dist/core/artifact-graph/instruction-loader.js +4 -4
  8. package/dist/core/artifact-graph/schema.js +5 -4
  9. package/dist/core/command-generation/adapters/amazon-q.js +5 -5
  10. package/dist/core/command-generation/adapters/antigravity.js +5 -5
  11. package/dist/core/command-generation/adapters/auggie.js +6 -6
  12. package/dist/core/command-generation/adapters/bob.js +6 -6
  13. package/dist/core/command-generation/adapters/claude.js +8 -8
  14. package/dist/core/command-generation/adapters/cline.js +5 -5
  15. package/dist/core/command-generation/adapters/codebuddy.js +7 -7
  16. package/dist/core/command-generation/adapters/codex.js +6 -6
  17. package/dist/core/command-generation/adapters/continue.js +7 -7
  18. package/dist/core/command-generation/adapters/costrict.js +6 -6
  19. package/dist/core/command-generation/adapters/crush.js +8 -8
  20. package/dist/core/command-generation/adapters/cursor.js +8 -8
  21. package/dist/core/command-generation/adapters/factory.js +6 -6
  22. package/dist/core/command-generation/adapters/gemini.js +5 -5
  23. package/dist/core/command-generation/adapters/github-copilot.js +5 -5
  24. package/dist/core/command-generation/adapters/iflow.js +8 -8
  25. package/dist/core/command-generation/adapters/junie.js +5 -5
  26. package/dist/core/command-generation/adapters/kilocode.js +1 -1
  27. package/dist/core/command-generation/adapters/kiro.js +5 -5
  28. package/dist/core/command-generation/adapters/lingma.js +8 -8
  29. package/dist/core/command-generation/adapters/opencode.js +5 -5
  30. package/dist/core/command-generation/adapters/pi.js +5 -5
  31. package/dist/core/command-generation/adapters/qoder.js +8 -8
  32. package/dist/core/command-generation/adapters/qwen.js +5 -5
  33. package/dist/core/command-generation/adapters/roocode.js +5 -5
  34. package/dist/core/command-generation/adapters/windsurf.js +8 -8
  35. package/dist/core/completions/factory.js +3 -2
  36. package/dist/core/completions/generators/bash-generator.js +41 -41
  37. package/dist/core/completions/generators/fish-generator.js +7 -7
  38. package/dist/core/completions/generators/powershell-generator.js +29 -29
  39. package/dist/core/completions/generators/zsh-generator.js +33 -33
  40. package/dist/core/completions/installers/fish-installer.js +13 -12
  41. package/dist/core/completions/installers/powershell-installer.js +16 -17
  42. package/dist/core/completions/installers/zsh-installer.js +1 -1
  43. package/dist/core/completions/templates/bash-templates.js +18 -18
  44. package/dist/core/completions/templates/fish-templates.js +32 -32
  45. package/dist/core/completions/templates/powershell-templates.js +19 -19
  46. package/dist/core/completions/templates/zsh-templates.js +30 -30
  47. package/dist/core/parsers/change-parser.js +7 -6
  48. package/dist/core/project-config.js +12 -13
  49. package/dist/core/shared/skill-generation.js +12 -12
  50. package/dist/core/specs-apply.js +37 -38
  51. package/dist/core/templates/workflows/apply-change.js +288 -288
  52. package/dist/core/templates/workflows/archive-change.js +251 -251
  53. package/dist/core/templates/workflows/bulk-archive-change.js +472 -472
  54. package/dist/core/templates/workflows/continue-change.js +212 -212
  55. package/dist/core/templates/workflows/explore.js +443 -443
  56. package/dist/core/templates/workflows/feedback.js +97 -97
  57. package/dist/core/templates/workflows/ff-change.js +178 -178
  58. package/dist/core/templates/workflows/propose.js +196 -196
  59. package/dist/core/templates/workflows/sync-specs.js +252 -252
  60. package/dist/core/templates/workflows/upstream-sync.js +93 -93
  61. package/dist/core/tools-manager.js +2 -2
  62. package/dist/messages/index.d.ts +111 -0
  63. package/dist/messages/index.js +1115 -977
  64. package/dist/utils/change-metadata.js +8 -7
  65. package/dist/utils/change-utils.js +12 -11
  66. package/package.json +82 -84
  67. package/schemas/spec-driven/schema.yaml +153 -153
  68. package/schemas/spec-driven/templates/design.md +19 -19
  69. package/schemas/spec-driven/templates/proposal.md +23 -23
  70. package/schemas/spec-driven/templates/spec.md +8 -8
  71. package/schemas/spec-driven/templates/tasks.md +9 -9
  72. package/scripts/postinstall.js +83 -83
@@ -1,6 +1,7 @@
1
1
  import { MarkdownParser } from './markdown-parser.js';
2
2
  import path from 'path';
3
3
  import { promises as fs } from 'fs';
4
+ import { CHANGE_PARSER_MESSAGES } from '../../messages/index.js';
4
5
  export class ChangeParser extends MarkdownParser {
5
6
  changeDir;
6
7
  constructor(content, changeDir) {
@@ -12,10 +13,10 @@ export class ChangeParser extends MarkdownParser {
12
13
  const why = this.findSection(sections, 'Why')?.content || '';
13
14
  const whatChanges = this.findSection(sections, 'What Changes')?.content || '';
14
15
  if (!why) {
15
- throw new Error('Change must have a Why section');
16
+ throw new Error(CHANGE_PARSER_MESSAGES.mustHaveWhySection);
16
17
  }
17
18
  if (!whatChanges) {
18
- throw new Error('Change must have a What Changes section');
19
+ throw new Error(CHANGE_PARSER_MESSAGES.mustHaveWhatChangesSection);
19
20
  }
20
21
  // Parse deltas from the What Changes section (simple format)
21
22
  const simpleDeltas = this.parseDeltas(whatChanges);
@@ -72,7 +73,7 @@ export class ChangeParser extends MarkdownParser {
72
73
  deltas.push({
73
74
  spec: specName,
74
75
  operation: 'ADDED',
75
- description: `Add requirement: ${req.text}`,
76
+ description: CHANGE_PARSER_MESSAGES.addRequirement(req.text),
76
77
  // Provide both single and plural forms for compatibility
77
78
  requirement: req,
78
79
  requirements: [req],
@@ -87,7 +88,7 @@ export class ChangeParser extends MarkdownParser {
87
88
  deltas.push({
88
89
  spec: specName,
89
90
  operation: 'MODIFIED',
90
- description: `Modify requirement: ${req.text}`,
91
+ description: CHANGE_PARSER_MESSAGES.modifyRequirement(req.text),
91
92
  requirement: req,
92
93
  requirements: [req],
93
94
  });
@@ -101,7 +102,7 @@ export class ChangeParser extends MarkdownParser {
101
102
  deltas.push({
102
103
  spec: specName,
103
104
  operation: 'REMOVED',
104
- description: `Remove requirement: ${req.text}`,
105
+ description: CHANGE_PARSER_MESSAGES.removeRequirement(req.text),
105
106
  requirement: req,
106
107
  requirements: [req],
107
108
  });
@@ -115,7 +116,7 @@ export class ChangeParser extends MarkdownParser {
115
116
  deltas.push({
116
117
  spec: specName,
117
118
  operation: 'RENAMED',
118
- description: `Rename requirement from "${rename.from}" to "${rename.to}"`,
119
+ description: CHANGE_PARSER_MESSAGES.renameRequirement(rename.from, rename.to),
119
120
  rename,
120
121
  });
121
122
  });
@@ -1,4 +1,4 @@
1
- import { PROJECT_CONFIG_MESSAGES } from '../messages/index.js';
1
+ import { PROJECT_CONFIG_MESSAGES, PROJECT_CONFIG_SUGGEST_MESSAGES } from '../messages/index.js';
2
2
  import { existsSync, readFileSync } from 'fs';
3
3
  import path from 'path';
4
4
  import { parse as parseYaml } from 'yaml';
@@ -69,7 +69,7 @@ export function readProjectConfig(projectRoot) {
69
69
  const content = readFileSync(configPath, 'utf-8');
70
70
  const raw = parseYaml(content);
71
71
  if (!raw || typeof raw !== 'object') {
72
- console.warn(`openspec/config.yaml is not a valid YAML object`);
72
+ console.warn(PROJECT_CONFIG_SUGGEST_MESSAGES.configNotValidYaml);
73
73
  return null;
74
74
  }
75
75
  const config = {};
@@ -136,7 +136,7 @@ export function readProjectConfig(projectRoot) {
136
136
  return Object.keys(config).length > 0 ? config : null;
137
137
  }
138
138
  catch (error) {
139
- console.warn(`Failed to parse openspec/config.yaml:`, error);
139
+ console.warn(PROJECT_CONFIG_SUGGEST_MESSAGES.configFailedToParse, error);
140
140
  return null;
141
141
  }
142
142
  }
@@ -155,8 +155,7 @@ export function validateConfigRules(rules, validArtifactIds, schemaName) {
155
155
  for (const artifactId of Object.keys(rules)) {
156
156
  if (!validArtifactIds.has(artifactId)) {
157
157
  const validIds = Array.from(validArtifactIds).sort().join(', ');
158
- warnings.push(`Unknown artifact ID in rules: "${artifactId}". ` +
159
- `Valid IDs for schema "${schemaName}": ${validIds}`);
158
+ warnings.push(PROJECT_CONFIG_SUGGEST_MESSAGES.unknownArtifactId(artifactId, schemaName, validIds));
160
159
  }
161
160
  }
162
161
  return warnings;
@@ -199,26 +198,26 @@ export function suggestSchemas(invalidSchemaName, availableSchemas) {
199
198
  .slice(0, 3);
200
199
  const builtIn = availableSchemas.filter((s) => s.isBuiltIn).map((s) => s.name);
201
200
  const projectLocal = availableSchemas.filter((s) => !s.isBuiltIn).map((s) => s.name);
202
- let message = `Schema '${invalidSchemaName}' not found in openspec/config.yaml\n\n`;
201
+ let message = PROJECT_CONFIG_SUGGEST_MESSAGES.schemaNotFound(invalidSchemaName);
203
202
  if (suggestions.length > 0) {
204
- message += `Did you mean one of these?\n`;
203
+ message += PROJECT_CONFIG_SUGGEST_MESSAGES.didYouMean;
205
204
  suggestions.forEach((s) => {
206
- const type = s.isBuiltIn ? 'built-in' : 'project-local';
205
+ const type = PROJECT_CONFIG_SUGGEST_MESSAGES.schemaType(s.isBuiltIn);
207
206
  message += ` - ${s.name} (${type})\n`;
208
207
  });
209
208
  message += '\n';
210
209
  }
211
- message += `Available schemas:\n`;
210
+ message += PROJECT_CONFIG_SUGGEST_MESSAGES.availableSchemas;
212
211
  if (builtIn.length > 0) {
213
- message += ` Built-in: ${builtIn.join(', ')}\n`;
212
+ message += PROJECT_CONFIG_SUGGEST_MESSAGES.builtInSchemas(builtIn.join(', '));
214
213
  }
215
214
  if (projectLocal.length > 0) {
216
- message += ` Project-local: ${projectLocal.join(', ')}\n`;
215
+ message += PROJECT_CONFIG_SUGGEST_MESSAGES.projectLocalSchemas(projectLocal.join(', '));
217
216
  }
218
217
  else {
219
- message += ` Project-local: (none found)\n`;
218
+ message += PROJECT_CONFIG_SUGGEST_MESSAGES.noProjectLocalSchemas;
220
219
  }
221
- message += `\nFix: Edit openspec/config.yaml and change 'schema: ${invalidSchemaName}' to a valid schema name`;
220
+ message += PROJECT_CONFIG_SUGGEST_MESSAGES.fixSuggestion(invalidSchemaName);
222
221
  return message;
223
222
  }
224
223
  //# sourceMappingURL=project-config.js.map
@@ -79,18 +79,18 @@ export function generateSkillContent(template, generatedByVersion, transformInst
79
79
  const instructions = transformInstructions
80
80
  ? transformInstructions(template.instructions)
81
81
  : template.instructions;
82
- return `---
83
- name: ${template.name}
84
- description: ${template.description}
85
- license: ${template.license || 'MIT'}
86
- compatibility: ${template.compatibility || 'Requires openspec CLI.'}
87
- metadata:
88
- author: ${template.metadata?.author || 'openspec'}
89
- version: "${template.metadata?.version || '1.0'}"
90
- generatedBy: "${generatedByVersion}"
91
- ---
92
-
93
- ${instructions}
82
+ return `---
83
+ name: ${template.name}
84
+ description: ${template.description}
85
+ license: ${template.license || 'MIT'}
86
+ compatibility: ${template.compatibility || 'Requires openspec CLI.'}
87
+ metadata:
88
+ author: ${template.metadata?.author || 'openspec'}
89
+ version: "${template.metadata?.version || '1.0'}"
90
+ generatedBy: "${generatedByVersion}"
91
+ ---
92
+
93
+ ${instructions}
94
94
  `;
95
95
  }
96
96
  //# sourceMappingURL=skill-generation.js.map
@@ -10,7 +10,7 @@ import chalk from 'chalk';
10
10
  import { extractRequirementsSection, parseDeltaSpec, normalizeRequirementName, } from './parsers/requirement-blocks.js';
11
11
  import { findMainSpecStructureIssues } from './parsers/spec-structure.js';
12
12
  import { Validator } from './validation/validator.js';
13
- import { ARCHIVE_MESSAGES } from '../messages/index.js';
13
+ import { ARCHIVE_MESSAGES, SPECS_APPLY_MESSAGES } from '../messages/index.js';
14
14
  // -----------------------------------------------------------------------------
15
15
  // Public API
16
16
  // -----------------------------------------------------------------------------
@@ -69,7 +69,7 @@ export async function buildUpdatedSpec(update, changeName) {
69
69
  for (const add of plan.added) {
70
70
  const name = normalizeRequirementName(add.name);
71
71
  if (addedNames.has(name)) {
72
- throw new Error(`${specName} validation failed - duplicate requirement in ADDED for header "### Requirement: ${add.name}"`);
72
+ throw new Error(SPECS_APPLY_MESSAGES.duplicateInSection(specName, 'ADDED', add.name));
73
73
  }
74
74
  addedNames.add(name);
75
75
  }
@@ -77,7 +77,7 @@ export async function buildUpdatedSpec(update, changeName) {
77
77
  for (const mod of plan.modified) {
78
78
  const name = normalizeRequirementName(mod.name);
79
79
  if (modifiedNames.has(name)) {
80
- throw new Error(`${specName} validation failed - duplicate requirement in MODIFIED for header "### Requirement: ${mod.name}"`);
80
+ throw new Error(SPECS_APPLY_MESSAGES.duplicateInSection(specName, 'MODIFIED', mod.name));
81
81
  }
82
82
  modifiedNames.add(name);
83
83
  }
@@ -85,7 +85,7 @@ export async function buildUpdatedSpec(update, changeName) {
85
85
  for (const rem of plan.removed) {
86
86
  const name = normalizeRequirementName(rem);
87
87
  if (removedNamesSet.has(name)) {
88
- throw new Error(`${specName} validation failed - duplicate requirement in REMOVED for header "### Requirement: ${rem}"`);
88
+ throw new Error(SPECS_APPLY_MESSAGES.duplicateInSection(specName, 'REMOVED', rem));
89
89
  }
90
90
  removedNamesSet.add(name);
91
91
  }
@@ -95,10 +95,10 @@ export async function buildUpdatedSpec(update, changeName) {
95
95
  const fromNorm = normalizeRequirementName(from);
96
96
  const toNorm = normalizeRequirementName(to);
97
97
  if (renamedFromSet.has(fromNorm)) {
98
- throw new Error(`${specName} validation failed - duplicate FROM in RENAMED for header "### Requirement: ${from}"`);
98
+ throw new Error(SPECS_APPLY_MESSAGES.duplicateFromInRenamed(specName, from));
99
99
  }
100
100
  if (renamedToSet.has(toNorm)) {
101
- throw new Error(`${specName} validation failed - duplicate TO in RENAMED for header "### Requirement: ${to}"`);
101
+ throw new Error(SPECS_APPLY_MESSAGES.duplicateToInRenamed(specName, to));
102
102
  }
103
103
  renamedFromSet.add(fromNorm);
104
104
  renamedToSet.add(toNorm);
@@ -120,21 +120,20 @@ export async function buildUpdatedSpec(update, changeName) {
120
120
  const fromNorm = normalizeRequirementName(from);
121
121
  const toNorm = normalizeRequirementName(to);
122
122
  if (modifiedNames.has(fromNorm)) {
123
- throw new Error(`${specName} validation failed - when a rename exists, MODIFIED must reference the NEW header "### Requirement: ${to}"`);
123
+ throw new Error(SPECS_APPLY_MESSAGES.renamedModifiedMustReferenceNew(specName, to));
124
124
  }
125
125
  // Detect ADDED colliding with a RENAMED TO
126
126
  if (addedNames.has(toNorm)) {
127
- throw new Error(`${specName} validation failed - RENAMED TO header collides with ADDED for "### Requirement: ${to}"`);
127
+ throw new Error(SPECS_APPLY_MESSAGES.renamedToCollidesWithAdded(specName, to));
128
128
  }
129
129
  }
130
130
  if (conflicts.length > 0) {
131
131
  const c = conflicts[0];
132
- throw new Error(`${specName} validation failed - requirement present in multiple sections (${c.a} and ${c.b}) for header "### Requirement: ${c.name}"`);
132
+ throw new Error(SPECS_APPLY_MESSAGES.requirementInMultipleSections(specName, c.a, c.b, c.name));
133
133
  }
134
134
  const hasAnyDelta = plan.added.length + plan.modified.length + plan.removed.length + plan.renamed.length > 0;
135
135
  if (!hasAnyDelta) {
136
- throw new Error(`Delta parsing found no operations for ${path.basename(path.dirname(update.source))}. ` +
137
- `Provide ADDED/MODIFIED/REMOVED/RENAMED sections in change spec.`);
136
+ throw new Error(SPECS_APPLY_MESSAGES.noDeltaOperations(path.basename(path.dirname(update.source))));
138
137
  }
139
138
  // Load or create base target content
140
139
  let targetContent;
@@ -146,7 +145,7 @@ export async function buildUpdatedSpec(update, changeName) {
146
145
  // Target spec does not exist; MODIFIED and RENAMED are not allowed for new specs
147
146
  // REMOVED will be ignored with a warning since there's nothing to remove
148
147
  if (plan.modified.length > 0 || plan.renamed.length > 0) {
149
- throw new Error(`${specName}: target spec does not exist; only ADDED requirements are allowed for new specs. MODIFIED and RENAMED operations require an existing spec.`);
148
+ throw new Error(SPECS_APPLY_MESSAGES.targetSpecNotExists(specName));
150
149
  }
151
150
  // Warn about REMOVED requirements being ignored for new specs
152
151
  if (plan.removed.length > 0) {
@@ -160,7 +159,7 @@ export async function buildUpdatedSpec(update, changeName) {
160
159
  const details = structureIssues
161
160
  .map(issue => `line ${issue.line}: ${issue.message}`)
162
161
  .join('\n');
163
- throw new Error(`${specName}: target spec is structurally invalid and cannot be updated until fixed:\n${details}`);
162
+ throw new Error(SPECS_APPLY_MESSAGES.targetSpecStructurallyInvalid(specName, details));
164
163
  }
165
164
  // Extract requirements section and build name->block map
166
165
  const parts = extractRequirementsSection(targetContent);
@@ -174,10 +173,10 @@ export async function buildUpdatedSpec(update, changeName) {
174
173
  const from = normalizeRequirementName(r.from);
175
174
  const to = normalizeRequirementName(r.to);
176
175
  if (!nameToBlock.has(from)) {
177
- throw new Error(`${specName} RENAMED failed for header "### Requirement: ${r.from}" - source not found`);
176
+ throw new Error(SPECS_APPLY_MESSAGES.renamedFailedSourceNotFound(specName, r.from));
178
177
  }
179
178
  if (nameToBlock.has(to)) {
180
- throw new Error(`${specName} RENAMED failed for header "### Requirement: ${r.to}" - target already exists`);
179
+ throw new Error(SPECS_APPLY_MESSAGES.renamedFailedTargetExists(specName, r.to));
181
180
  }
182
181
  const block = nameToBlock.get(from);
183
182
  const newHeader = `### Requirement: ${to}`;
@@ -198,7 +197,7 @@ export async function buildUpdatedSpec(update, changeName) {
198
197
  // For new specs, REMOVED requirements are already warned about and ignored
199
198
  // For existing specs, missing requirements are an error
200
199
  if (!isNewSpec) {
201
- throw new Error(`${specName} REMOVED failed for header "### Requirement: ${name}" - not found`);
200
+ throw new Error(SPECS_APPLY_MESSAGES.removedFailedNotFound(specName, name));
202
201
  }
203
202
  // Skip removal for new specs (already warned above)
204
203
  continue;
@@ -209,12 +208,12 @@ export async function buildUpdatedSpec(update, changeName) {
209
208
  for (const mod of plan.modified) {
210
209
  const key = normalizeRequirementName(mod.name);
211
210
  if (!nameToBlock.has(key)) {
212
- throw new Error(`${specName} MODIFIED failed for header "### Requirement: ${mod.name}" - not found`);
211
+ throw new Error(SPECS_APPLY_MESSAGES.modifiedFailedNotFound(specName, mod.name));
213
212
  }
214
213
  // Replace block with provided raw (ensure header line matches key)
215
214
  const modHeaderMatch = mod.raw.split('\n')[0].match(/^###\s*Requirement:\s*(.+)\s*$/);
216
215
  if (!modHeaderMatch || normalizeRequirementName(modHeaderMatch[1]) !== key) {
217
- throw new Error(`${specName} MODIFIED failed for header "### Requirement: ${mod.name}" - header mismatch in content`);
216
+ throw new Error(SPECS_APPLY_MESSAGES.modifiedFailedHeaderMismatch(specName, mod.name));
218
217
  }
219
218
  nameToBlock.set(key, mod);
220
219
  }
@@ -222,7 +221,7 @@ export async function buildUpdatedSpec(update, changeName) {
222
221
  for (const add of plan.added) {
223
222
  const key = normalizeRequirementName(add.name);
224
223
  if (nameToBlock.has(key)) {
225
- throw new Error(`${specName} ADDED failed for header "### Requirement: ${add.name}" - already exists`);
224
+ throw new Error(SPECS_APPLY_MESSAGES.addedFailedAlreadyExists(specName, add.name));
226
225
  }
227
226
  nameToBlock.set(key, add);
228
227
  }
@@ -272,22 +271,22 @@ export async function writeUpdatedSpec(update, rebuilt, counts) {
272
271
  await fs.mkdir(targetDir, { recursive: true });
273
272
  await fs.writeFile(update.target, rebuilt);
274
273
  const specName = path.basename(path.dirname(update.target));
275
- console.log(`Applying changes to openspec/specs/${specName}/spec.md:`);
274
+ console.log(SPECS_APPLY_MESSAGES.applyingChangesTo(specName));
276
275
  if (counts.added)
277
- console.log(` + ${counts.added} added`);
276
+ console.log(SPECS_APPLY_MESSAGES.countAdded(counts.added));
278
277
  if (counts.modified)
279
- console.log(` ~ ${counts.modified} modified`);
278
+ console.log(SPECS_APPLY_MESSAGES.countModified(counts.modified));
280
279
  if (counts.removed)
281
- console.log(` - ${counts.removed} removed`);
280
+ console.log(SPECS_APPLY_MESSAGES.countRemoved(counts.removed));
282
281
  if (counts.renamed)
283
- console.log(` → ${counts.renamed} renamed`);
282
+ console.log(SPECS_APPLY_MESSAGES.countRenamed(counts.renamed));
284
283
  }
285
284
  /**
286
285
  * Build a skeleton spec for new capabilities.
287
286
  */
288
287
  export function buildSpecSkeleton(specFolderName, changeName) {
289
288
  const titleBase = specFolderName;
290
- return `# ${titleBase} Specification\n\n## Purpose\nTBD - created by archiving change ${changeName}. Update Purpose after archive.\n\n## Requirements\n`;
289
+ return `# ${titleBase} Specification\n\n## Purpose\n${SPECS_APPLY_MESSAGES.skeletonPurpose(changeName)}\n\n## Requirements\n`;
291
290
  }
292
291
  /**
293
292
  * Apply all delta specs from a change to main specs.
@@ -304,11 +303,11 @@ export async function applySpecs(projectRoot, changeName, options = {}) {
304
303
  try {
305
304
  const stat = await fs.stat(changeDir);
306
305
  if (!stat.isDirectory()) {
307
- throw new Error(`Change '${changeName}' not found.`);
306
+ throw new Error(SPECS_APPLY_MESSAGES.changeNotFound(changeName));
308
307
  }
309
308
  }
310
309
  catch {
311
- throw new Error(`Change '${changeName}' not found.`);
310
+ throw new Error(SPECS_APPLY_MESSAGES.changeNotFound(changeName));
312
311
  }
313
312
  // Find specs to update
314
313
  const specUpdates = await findSpecUpdates(changeDir, mainSpecsDir);
@@ -337,7 +336,7 @@ export async function applySpecs(projectRoot, changeName, options = {}) {
337
336
  .filter((i) => i.level === 'ERROR')
338
337
  .map((i) => ` ✗ ${i.message}`)
339
338
  .join('\n');
340
- throw new Error(`Validation errors in rebuilt spec for ${specName}:\n${errors}`);
339
+ throw new Error(SPECS_APPLY_MESSAGES.validationErrorsInRebuiltSpec(specName, errors));
341
340
  }
342
341
  }
343
342
  }
@@ -352,27 +351,27 @@ export async function applySpecs(projectRoot, changeName, options = {}) {
352
351
  await fs.mkdir(targetDir, { recursive: true });
353
352
  await fs.writeFile(p.update.target, p.rebuilt);
354
353
  if (!options.silent) {
355
- console.log(`Applying changes to openspec/specs/${capability}/spec.md:`);
354
+ console.log(SPECS_APPLY_MESSAGES.applyingChangesTo(capability));
356
355
  if (p.counts.added)
357
- console.log(` + ${p.counts.added} added`);
356
+ console.log(SPECS_APPLY_MESSAGES.countAdded(p.counts.added));
358
357
  if (p.counts.modified)
359
- console.log(` ~ ${p.counts.modified} modified`);
358
+ console.log(SPECS_APPLY_MESSAGES.countModified(p.counts.modified));
360
359
  if (p.counts.removed)
361
- console.log(` - ${p.counts.removed} removed`);
360
+ console.log(SPECS_APPLY_MESSAGES.countRemoved(p.counts.removed));
362
361
  if (p.counts.renamed)
363
- console.log(` → ${p.counts.renamed} renamed`);
362
+ console.log(SPECS_APPLY_MESSAGES.countRenamed(p.counts.renamed));
364
363
  }
365
364
  }
366
365
  else if (!options.silent) {
367
- console.log(`Would apply changes to openspec/specs/${capability}/spec.md:`);
366
+ console.log(SPECS_APPLY_MESSAGES.wouldApplyChangesTo(capability));
368
367
  if (p.counts.added)
369
- console.log(` + ${p.counts.added} added`);
368
+ console.log(SPECS_APPLY_MESSAGES.countAdded(p.counts.added));
370
369
  if (p.counts.modified)
371
- console.log(` ~ ${p.counts.modified} modified`);
370
+ console.log(SPECS_APPLY_MESSAGES.countModified(p.counts.modified));
372
371
  if (p.counts.removed)
373
- console.log(` - ${p.counts.removed} removed`);
372
+ console.log(SPECS_APPLY_MESSAGES.countRemoved(p.counts.removed));
374
373
  if (p.counts.renamed)
375
- console.log(` → ${p.counts.renamed} renamed`);
374
+ console.log(SPECS_APPLY_MESSAGES.countRenamed(p.counts.renamed));
376
375
  }
377
376
  capabilities.push({
378
377
  capability,