@dynamicworks/br-openspec 2.0.0 → 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.
- package/dist/core/artifact-graph/instruction-loader.js +4 -4
- package/dist/core/artifact-graph/schema.js +5 -4
- package/dist/core/completions/factory.js +3 -2
- package/dist/core/completions/installers/fish-installer.js +13 -12
- package/dist/core/completions/installers/powershell-installer.js +16 -17
- package/dist/core/completions/installers/zsh-installer.js +1 -1
- package/dist/core/parsers/change-parser.js +7 -6
- package/dist/core/project-config.js +12 -13
- package/dist/core/specs-apply.js +37 -38
- package/dist/core/tools-manager.js +2 -2
- package/dist/messages/index.d.ts +111 -0
- package/dist/messages/index.js +138 -0
- package/dist/utils/change-metadata.js +8 -7
- package/dist/utils/change-utils.js +12 -11
- package/package.json +1 -1
|
@@ -4,7 +4,7 @@ import { getSchemaDir, resolveSchema } from './resolver.js';
|
|
|
4
4
|
import { ArtifactGraph } from './graph.js';
|
|
5
5
|
import { detectCompleted } from './state.js';
|
|
6
6
|
import { resolveSchemaForChange } from '../../utils/change-metadata.js';
|
|
7
|
-
import { WORKFLOW_MESSAGES } from '../../messages/index.js';
|
|
7
|
+
import { WORKFLOW_MESSAGES, ARTIFACT_GRAPH_MESSAGES } from '../../messages/index.js';
|
|
8
8
|
import { FileSystemUtils } from '../../utils/file-system.js';
|
|
9
9
|
import { readProjectConfig, validateConfigRules } from '../project-config.js';
|
|
10
10
|
// Session-level cache for validation warnings (avoid repeating same warnings)
|
|
@@ -36,7 +36,7 @@ export function loadTemplate(schemaName, templatePath, projectRoot) {
|
|
|
36
36
|
}
|
|
37
37
|
const templatePathOnDisk = path.join(schemaDir, 'templates', templatePath);
|
|
38
38
|
if (!fs.existsSync(templatePathOnDisk)) {
|
|
39
|
-
throw new TemplateLoadError(
|
|
39
|
+
throw new TemplateLoadError(ARTIFACT_GRAPH_MESSAGES.templateNotFound(templatePathOnDisk), templatePathOnDisk);
|
|
40
40
|
}
|
|
41
41
|
const fullPath = FileSystemUtils.canonicalizeExistingPath(templatePathOnDisk);
|
|
42
42
|
try {
|
|
@@ -44,7 +44,7 @@ export function loadTemplate(schemaName, templatePath, projectRoot) {
|
|
|
44
44
|
}
|
|
45
45
|
catch (err) {
|
|
46
46
|
const ioError = err instanceof Error ? err : new Error(String(err));
|
|
47
|
-
throw new TemplateLoadError(
|
|
47
|
+
throw new TemplateLoadError(ARTIFACT_GRAPH_MESSAGES.failedToReadTemplate(ioError.message), fullPath);
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
/**
|
|
@@ -93,7 +93,7 @@ export function loadChangeContext(projectRoot, changeName, schemaName) {
|
|
|
93
93
|
export function generateInstructions(context, artifactId, projectRoot) {
|
|
94
94
|
const artifact = context.graph.getArtifact(artifactId);
|
|
95
95
|
if (!artifact) {
|
|
96
|
-
throw new Error(
|
|
96
|
+
throw new Error(ARTIFACT_GRAPH_MESSAGES.artifactNotFound(artifactId, context.schemaName));
|
|
97
97
|
}
|
|
98
98
|
const templateContent = loadTemplate(context.schemaName, artifact.template, context.projectRoot);
|
|
99
99
|
const dependencies = getDependencyInfo(artifact, context.graph, context.completed);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as fs from 'node:fs';
|
|
2
2
|
import { parse as parseYaml } from 'yaml';
|
|
3
3
|
import { SchemaYamlSchema } from './types.js';
|
|
4
|
+
import { ARTIFACT_GRAPH_MESSAGES } from '../../messages/index.js';
|
|
4
5
|
export class SchemaValidationError extends Error {
|
|
5
6
|
constructor(message) {
|
|
6
7
|
super(message);
|
|
@@ -23,7 +24,7 @@ export function parseSchema(yamlContent) {
|
|
|
23
24
|
const result = SchemaYamlSchema.safeParse(parsed);
|
|
24
25
|
if (!result.success) {
|
|
25
26
|
const errors = result.error.issues.map(e => `${e.path.join('.')}: ${e.message}`).join(', ');
|
|
26
|
-
throw new SchemaValidationError(
|
|
27
|
+
throw new SchemaValidationError(ARTIFACT_GRAPH_MESSAGES.invalidSchema(errors));
|
|
27
28
|
}
|
|
28
29
|
const schema = result.data;
|
|
29
30
|
// Check for duplicate artifact IDs
|
|
@@ -41,7 +42,7 @@ function validateNoDuplicateIds(artifacts) {
|
|
|
41
42
|
const seen = new Set();
|
|
42
43
|
for (const artifact of artifacts) {
|
|
43
44
|
if (seen.has(artifact.id)) {
|
|
44
|
-
throw new SchemaValidationError(
|
|
45
|
+
throw new SchemaValidationError(ARTIFACT_GRAPH_MESSAGES.duplicateArtifactId(artifact.id));
|
|
45
46
|
}
|
|
46
47
|
seen.add(artifact.id);
|
|
47
48
|
}
|
|
@@ -54,7 +55,7 @@ function validateRequiresReferences(artifacts) {
|
|
|
54
55
|
for (const artifact of artifacts) {
|
|
55
56
|
for (const req of artifact.requires) {
|
|
56
57
|
if (!validIds.has(req)) {
|
|
57
|
-
throw new SchemaValidationError(
|
|
58
|
+
throw new SchemaValidationError(ARTIFACT_GRAPH_MESSAGES.invalidDependencyReference(artifact.id, req));
|
|
58
59
|
}
|
|
59
60
|
}
|
|
60
61
|
}
|
|
@@ -100,7 +101,7 @@ function validateNoCycles(artifacts) {
|
|
|
100
101
|
if (!visited.has(artifact.id)) {
|
|
101
102
|
const cycle = dfs(artifact.id);
|
|
102
103
|
if (cycle) {
|
|
103
|
-
throw new SchemaValidationError(
|
|
104
|
+
throw new SchemaValidationError(ARTIFACT_GRAPH_MESSAGES.cyclicDependency(cycle));
|
|
104
105
|
}
|
|
105
106
|
}
|
|
106
107
|
}
|
|
@@ -6,6 +6,7 @@ import { ZshInstaller } from './installers/zsh-installer.js';
|
|
|
6
6
|
import { BashInstaller } from './installers/bash-installer.js';
|
|
7
7
|
import { FishInstaller } from './installers/fish-installer.js';
|
|
8
8
|
import { PowerShellInstaller } from './installers/powershell-installer.js';
|
|
9
|
+
import { COMPLETIONS_FACTORY_MESSAGES } from '../../messages/index.js';
|
|
9
10
|
/**
|
|
10
11
|
* Factory for creating completion generators and installers
|
|
11
12
|
* This design makes it easy to add support for additional shells
|
|
@@ -30,7 +31,7 @@ export class CompletionFactory {
|
|
|
30
31
|
case 'powershell':
|
|
31
32
|
return new PowerShellGenerator();
|
|
32
33
|
default:
|
|
33
|
-
throw new Error(
|
|
34
|
+
throw new Error(COMPLETIONS_FACTORY_MESSAGES.unsupportedShell(shell));
|
|
34
35
|
}
|
|
35
36
|
}
|
|
36
37
|
/**
|
|
@@ -51,7 +52,7 @@ export class CompletionFactory {
|
|
|
51
52
|
case 'powershell':
|
|
52
53
|
return new PowerShellInstaller();
|
|
53
54
|
default:
|
|
54
|
-
throw new Error(
|
|
55
|
+
throw new Error(COMPLETIONS_FACTORY_MESSAGES.unsupportedShell(shell));
|
|
55
56
|
}
|
|
56
57
|
}
|
|
57
58
|
/**
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { promises as fs } from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import os from 'os';
|
|
4
|
+
import { COMPLETION_MESSAGES } from '../../../messages/index.js';
|
|
4
5
|
/**
|
|
5
6
|
* Installer for Fish completion scripts.
|
|
6
7
|
* Fish automatically loads completions from ~/.config/fish/completions/
|
|
@@ -56,10 +57,10 @@ export class FishInstaller {
|
|
|
56
57
|
return {
|
|
57
58
|
success: true,
|
|
58
59
|
installedPath: targetPath,
|
|
59
|
-
message:
|
|
60
|
+
message: COMPLETION_MESSAGES.fishAlreadyInstalled,
|
|
60
61
|
instructions: [
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
COMPLETION_MESSAGES.fishAlreadyInstalledDetail,
|
|
63
|
+
COMPLETION_MESSAGES.fishAutoLoadsHint,
|
|
63
64
|
],
|
|
64
65
|
};
|
|
65
66
|
}
|
|
@@ -81,11 +82,11 @@ export class FishInstaller {
|
|
|
81
82
|
let message;
|
|
82
83
|
if (isUpdate) {
|
|
83
84
|
message = backupPath
|
|
84
|
-
?
|
|
85
|
-
:
|
|
85
|
+
? COMPLETION_MESSAGES.fishUpdatedWithBackup
|
|
86
|
+
: COMPLETION_MESSAGES.fishUpdated;
|
|
86
87
|
}
|
|
87
88
|
else {
|
|
88
|
-
message =
|
|
89
|
+
message = COMPLETION_MESSAGES.fishInstalled;
|
|
89
90
|
}
|
|
90
91
|
return {
|
|
91
92
|
success: true,
|
|
@@ -93,15 +94,15 @@ export class FishInstaller {
|
|
|
93
94
|
backupPath,
|
|
94
95
|
message,
|
|
95
96
|
instructions: [
|
|
96
|
-
|
|
97
|
-
|
|
97
|
+
COMPLETION_MESSAGES.fishAutoLoadsDir,
|
|
98
|
+
COMPLETION_MESSAGES.fishAvailableImmediately,
|
|
98
99
|
],
|
|
99
100
|
};
|
|
100
101
|
}
|
|
101
102
|
catch (error) {
|
|
102
103
|
return {
|
|
103
104
|
success: false,
|
|
104
|
-
message:
|
|
105
|
+
message: COMPLETION_MESSAGES.fishFailedToInstall(error instanceof Error ? error.message : String(error)),
|
|
105
106
|
};
|
|
106
107
|
}
|
|
107
108
|
}
|
|
@@ -122,20 +123,20 @@ export class FishInstaller {
|
|
|
122
123
|
catch {
|
|
123
124
|
return {
|
|
124
125
|
success: false,
|
|
125
|
-
message:
|
|
126
|
+
message: COMPLETION_MESSAGES.fishNotInstalled,
|
|
126
127
|
};
|
|
127
128
|
}
|
|
128
129
|
// Remove the completion script
|
|
129
130
|
await fs.unlink(targetPath);
|
|
130
131
|
return {
|
|
131
132
|
success: true,
|
|
132
|
-
message:
|
|
133
|
+
message: COMPLETION_MESSAGES.fishUninstalled,
|
|
133
134
|
};
|
|
134
135
|
}
|
|
135
136
|
catch (error) {
|
|
136
137
|
return {
|
|
137
138
|
success: false,
|
|
138
|
-
message:
|
|
139
|
+
message: COMPLETION_MESSAGES.fishFailedToUninstall(error instanceof Error ? error.message : String(error)),
|
|
139
140
|
};
|
|
140
141
|
}
|
|
141
142
|
}
|
|
@@ -29,8 +29,7 @@ export class PowerShellInstaller {
|
|
|
29
29
|
}
|
|
30
30
|
// UTF-16 BE BOM: FE FF — not natively supported by Node
|
|
31
31
|
if (buffer.length >= 2 && buffer[0] === 0xfe && buffer[1] === 0xff) {
|
|
32
|
-
throw new Error(
|
|
33
|
-
'Please re-save as UTF-8 or UTF-16 LE, then retry.');
|
|
32
|
+
throw new Error(COMPLETION_MESSAGES.powershellUtf16BEUnsupported);
|
|
34
33
|
}
|
|
35
34
|
// UTF-8 BOM: EF BB BF
|
|
36
35
|
if (buffer.length >= 3 && buffer[0] === 0xef && buffer[1] === 0xbb && buffer[2] === 0xbf) {
|
|
@@ -274,10 +273,10 @@ export class PowerShellInstaller {
|
|
|
274
273
|
return {
|
|
275
274
|
success: true,
|
|
276
275
|
installedPath: targetPath,
|
|
277
|
-
message:
|
|
276
|
+
message: COMPLETION_MESSAGES.powershellAlreadyInstalled,
|
|
278
277
|
instructions: [
|
|
279
|
-
|
|
280
|
-
|
|
278
|
+
COMPLETION_MESSAGES.powershellAlreadyInstalledDetail,
|
|
279
|
+
COMPLETION_MESSAGES.powershellAlreadyInstalledHint,
|
|
281
280
|
],
|
|
282
281
|
};
|
|
283
282
|
}
|
|
@@ -315,13 +314,13 @@ export class PowerShellInstaller {
|
|
|
315
314
|
let message;
|
|
316
315
|
if (isUpdate) {
|
|
317
316
|
message = backupPath
|
|
318
|
-
?
|
|
319
|
-
:
|
|
317
|
+
? COMPLETION_MESSAGES.powershellUpdatedWithBackup
|
|
318
|
+
: COMPLETION_MESSAGES.powershellUpdated;
|
|
320
319
|
}
|
|
321
320
|
else {
|
|
322
321
|
message = profileConfigured
|
|
323
|
-
?
|
|
324
|
-
:
|
|
322
|
+
? COMPLETION_MESSAGES.powershellInstalledWithProfile
|
|
323
|
+
: COMPLETION_MESSAGES.powershellInstalled;
|
|
325
324
|
}
|
|
326
325
|
return {
|
|
327
326
|
success: true,
|
|
@@ -335,7 +334,7 @@ export class PowerShellInstaller {
|
|
|
335
334
|
catch (error) {
|
|
336
335
|
return {
|
|
337
336
|
success: false,
|
|
338
|
-
message:
|
|
337
|
+
message: COMPLETION_MESSAGES.powershellFailedToInstall(error instanceof Error ? error.message : String(error)),
|
|
339
338
|
};
|
|
340
339
|
}
|
|
341
340
|
}
|
|
@@ -348,16 +347,16 @@ export class PowerShellInstaller {
|
|
|
348
347
|
generateInstructions(installedPath) {
|
|
349
348
|
const profilePath = this.getProfilePath();
|
|
350
349
|
return [
|
|
351
|
-
|
|
350
|
+
COMPLETION_MESSAGES.powershellScriptInstalled,
|
|
352
351
|
'',
|
|
353
|
-
|
|
352
|
+
COMPLETION_MESSAGES.powershellEnableCompletions(profilePath),
|
|
354
353
|
'',
|
|
355
|
-
|
|
354
|
+
` ${COMPLETION_MESSAGES.powershellSourceComment}`,
|
|
356
355
|
` if (Test-Path "${installedPath}") {`,
|
|
357
356
|
` . "${installedPath}"`,
|
|
358
357
|
' }',
|
|
359
358
|
'',
|
|
360
|
-
|
|
359
|
+
COMPLETION_MESSAGES.powershellThenRestart,
|
|
361
360
|
];
|
|
362
361
|
}
|
|
363
362
|
/**
|
|
@@ -377,7 +376,7 @@ export class PowerShellInstaller {
|
|
|
377
376
|
catch {
|
|
378
377
|
return {
|
|
379
378
|
success: false,
|
|
380
|
-
message:
|
|
379
|
+
message: COMPLETION_MESSAGES.powershellNotInstalled,
|
|
381
380
|
};
|
|
382
381
|
}
|
|
383
382
|
// Remove the completion script
|
|
@@ -386,13 +385,13 @@ export class PowerShellInstaller {
|
|
|
386
385
|
await this.removeProfileConfig();
|
|
387
386
|
return {
|
|
388
387
|
success: true,
|
|
389
|
-
message:
|
|
388
|
+
message: COMPLETION_MESSAGES.powershellUninstalled,
|
|
390
389
|
};
|
|
391
390
|
}
|
|
392
391
|
catch (error) {
|
|
393
392
|
return {
|
|
394
393
|
success: false,
|
|
395
|
-
message:
|
|
394
|
+
message: COMPLETION_MESSAGES.powershellFailedToUninstall(error instanceof Error ? error.message : String(error)),
|
|
396
395
|
};
|
|
397
396
|
}
|
|
398
397
|
}
|
|
@@ -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(
|
|
16
|
+
throw new Error(CHANGE_PARSER_MESSAGES.mustHaveWhySection);
|
|
16
17
|
}
|
|
17
18
|
if (!whatChanges) {
|
|
18
|
-
throw new Error(
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
201
|
+
let message = PROJECT_CONFIG_SUGGEST_MESSAGES.schemaNotFound(invalidSchemaName);
|
|
203
202
|
if (suggestions.length > 0) {
|
|
204
|
-
message +=
|
|
203
|
+
message += PROJECT_CONFIG_SUGGEST_MESSAGES.didYouMean;
|
|
205
204
|
suggestions.forEach((s) => {
|
|
206
|
-
const type = s.isBuiltIn
|
|
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 +=
|
|
210
|
+
message += PROJECT_CONFIG_SUGGEST_MESSAGES.availableSchemas;
|
|
212
211
|
if (builtIn.length > 0) {
|
|
213
|
-
message +=
|
|
212
|
+
message += PROJECT_CONFIG_SUGGEST_MESSAGES.builtInSchemas(builtIn.join(', '));
|
|
214
213
|
}
|
|
215
214
|
if (projectLocal.length > 0) {
|
|
216
|
-
message +=
|
|
215
|
+
message += PROJECT_CONFIG_SUGGEST_MESSAGES.projectLocalSchemas(projectLocal.join(', '));
|
|
217
216
|
}
|
|
218
217
|
else {
|
|
219
|
-
message +=
|
|
218
|
+
message += PROJECT_CONFIG_SUGGEST_MESSAGES.noProjectLocalSchemas;
|
|
220
219
|
}
|
|
221
|
-
message +=
|
|
220
|
+
message += PROJECT_CONFIG_SUGGEST_MESSAGES.fixSuggestion(invalidSchemaName);
|
|
222
221
|
return message;
|
|
223
222
|
}
|
|
224
223
|
//# sourceMappingURL=project-config.js.map
|
package/dist/core/specs-apply.js
CHANGED
|
@@ -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(
|
|
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(
|
|
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(
|
|
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(
|
|
98
|
+
throw new Error(SPECS_APPLY_MESSAGES.duplicateFromInRenamed(specName, from));
|
|
99
99
|
}
|
|
100
100
|
if (renamedToSet.has(toNorm)) {
|
|
101
|
-
throw new Error(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
176
|
+
throw new Error(SPECS_APPLY_MESSAGES.renamedFailedSourceNotFound(specName, r.from));
|
|
178
177
|
}
|
|
179
178
|
if (nameToBlock.has(to)) {
|
|
180
|
-
throw new Error(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
274
|
+
console.log(SPECS_APPLY_MESSAGES.applyingChangesTo(specName));
|
|
276
275
|
if (counts.added)
|
|
277
|
-
console.log(
|
|
276
|
+
console.log(SPECS_APPLY_MESSAGES.countAdded(counts.added));
|
|
278
277
|
if (counts.modified)
|
|
279
|
-
console.log(
|
|
278
|
+
console.log(SPECS_APPLY_MESSAGES.countModified(counts.modified));
|
|
280
279
|
if (counts.removed)
|
|
281
|
-
console.log(
|
|
280
|
+
console.log(SPECS_APPLY_MESSAGES.countRemoved(counts.removed));
|
|
282
281
|
if (counts.renamed)
|
|
283
|
-
console.log(
|
|
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\
|
|
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(
|
|
306
|
+
throw new Error(SPECS_APPLY_MESSAGES.changeNotFound(changeName));
|
|
308
307
|
}
|
|
309
308
|
}
|
|
310
309
|
catch {
|
|
311
|
-
throw new Error(
|
|
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(
|
|
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(
|
|
354
|
+
console.log(SPECS_APPLY_MESSAGES.applyingChangesTo(capability));
|
|
356
355
|
if (p.counts.added)
|
|
357
|
-
console.log(
|
|
356
|
+
console.log(SPECS_APPLY_MESSAGES.countAdded(p.counts.added));
|
|
358
357
|
if (p.counts.modified)
|
|
359
|
-
console.log(
|
|
358
|
+
console.log(SPECS_APPLY_MESSAGES.countModified(p.counts.modified));
|
|
360
359
|
if (p.counts.removed)
|
|
361
|
-
console.log(
|
|
360
|
+
console.log(SPECS_APPLY_MESSAGES.countRemoved(p.counts.removed));
|
|
362
361
|
if (p.counts.renamed)
|
|
363
|
-
console.log(
|
|
362
|
+
console.log(SPECS_APPLY_MESSAGES.countRenamed(p.counts.renamed));
|
|
364
363
|
}
|
|
365
364
|
}
|
|
366
365
|
else if (!options.silent) {
|
|
367
|
-
console.log(
|
|
366
|
+
console.log(SPECS_APPLY_MESSAGES.wouldApplyChangesTo(capability));
|
|
368
367
|
if (p.counts.added)
|
|
369
|
-
console.log(
|
|
368
|
+
console.log(SPECS_APPLY_MESSAGES.countAdded(p.counts.added));
|
|
370
369
|
if (p.counts.modified)
|
|
371
|
-
console.log(
|
|
370
|
+
console.log(SPECS_APPLY_MESSAGES.countModified(p.counts.modified));
|
|
372
371
|
if (p.counts.removed)
|
|
373
|
-
console.log(
|
|
372
|
+
console.log(SPECS_APPLY_MESSAGES.countRemoved(p.counts.removed));
|
|
374
373
|
if (p.counts.renamed)
|
|
375
|
-
console.log(
|
|
374
|
+
console.log(SPECS_APPLY_MESSAGES.countRenamed(p.counts.renamed));
|
|
376
375
|
}
|
|
377
376
|
capabilities.push({
|
|
378
377
|
capability,
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Encapsulates adding and removing IDE/Code Agent OpenSpec configuration files.
|
|
5
5
|
* Shared by `openspec init` (via InitCommand) and `openspec tools`.
|
|
6
6
|
*/
|
|
7
|
-
import { TOOLS_MESSAGES } from '../messages/index.js';
|
|
7
|
+
import { TOOLS_MESSAGES, TOOLS_MANAGER_MESSAGES } from '../messages/index.js';
|
|
8
8
|
import path from 'path';
|
|
9
9
|
import * as fs from 'fs';
|
|
10
10
|
import { createRequire } from 'module';
|
|
@@ -100,7 +100,7 @@ export async function removeOpenSpecCommandFiles(projectPath, toolId) {
|
|
|
100
100
|
*/
|
|
101
101
|
export async function addTool(projectPath, tool) {
|
|
102
102
|
if (!tool.skillsDir) {
|
|
103
|
-
throw new Error(
|
|
103
|
+
throw new Error(TOOLS_MANAGER_MESSAGES.toolDoesNotSupportSkills(tool.value));
|
|
104
104
|
}
|
|
105
105
|
const globalConfig = getGlobalConfig();
|
|
106
106
|
const profile = globalConfig.profile ?? 'core';
|
package/dist/messages/index.d.ts
CHANGED
|
@@ -607,6 +607,35 @@ export declare const COMPLETION_MESSAGES: {
|
|
|
607
607
|
warningCouldNotRemoveLegacy: (path: string, err: string) => string;
|
|
608
608
|
powershellCompletionHeader: string;
|
|
609
609
|
powershellCompletionNote: string;
|
|
610
|
+
fishAlreadyInstalled: string;
|
|
611
|
+
fishAlreadyInstalledDetail: string;
|
|
612
|
+
fishAutoLoadsHint: string;
|
|
613
|
+
fishUpdatedWithBackup: string;
|
|
614
|
+
fishUpdated: string;
|
|
615
|
+
fishInstalled: string;
|
|
616
|
+
fishAutoLoadsDir: string;
|
|
617
|
+
fishAvailableImmediately: string;
|
|
618
|
+
fishFailedToInstall: (error: string) => string;
|
|
619
|
+
fishNotInstalled: string;
|
|
620
|
+
fishUninstalled: string;
|
|
621
|
+
fishFailedToUninstall: (error: string) => string;
|
|
622
|
+
powershellUtf16BEUnsupported: string;
|
|
623
|
+
powershellAlreadyInstalled: string;
|
|
624
|
+
powershellAlreadyInstalledDetail: string;
|
|
625
|
+
powershellAlreadyInstalledHint: string;
|
|
626
|
+
powershellUpdatedWithBackup: string;
|
|
627
|
+
powershellUpdated: string;
|
|
628
|
+
powershellInstalledWithProfile: string;
|
|
629
|
+
powershellInstalled: string;
|
|
630
|
+
powershellFailedToInstall: (error: string) => string;
|
|
631
|
+
powershellScriptInstalled: string;
|
|
632
|
+
powershellEnableCompletions: (profilePath: string) => string;
|
|
633
|
+
powershellSourceComment: string;
|
|
634
|
+
powershellThenRestart: string;
|
|
635
|
+
powershellNotInstalled: string;
|
|
636
|
+
powershellUninstalled: string;
|
|
637
|
+
powershellFailedToUninstall: (error: string) => string;
|
|
638
|
+
zshNotInstalled: string;
|
|
610
639
|
};
|
|
611
640
|
export declare const FEEDBACK_MESSAGES: {
|
|
612
641
|
submitFeedback: string;
|
|
@@ -864,4 +893,86 @@ export declare const VERIFY_CHANGE_TEMPLATE_MESSAGES: {
|
|
|
864
893
|
export declare const TELEMETRY_MESSAGES: {
|
|
865
894
|
firstRunNotice: string;
|
|
866
895
|
};
|
|
896
|
+
export declare const CHANGE_PARSER_MESSAGES: {
|
|
897
|
+
mustHaveWhySection: string;
|
|
898
|
+
mustHaveWhatChangesSection: string;
|
|
899
|
+
addRequirement: (text: string) => string;
|
|
900
|
+
modifyRequirement: (text: string) => string;
|
|
901
|
+
removeRequirement: (text: string) => string;
|
|
902
|
+
renameRequirement: (from: string, to: string) => string;
|
|
903
|
+
};
|
|
904
|
+
export declare const SPECS_APPLY_MESSAGES: {
|
|
905
|
+
duplicateInSection: (specName: string, section: string, reqName: string) => string;
|
|
906
|
+
duplicateFromInRenamed: (specName: string, reqName: string) => string;
|
|
907
|
+
duplicateToInRenamed: (specName: string, reqName: string) => string;
|
|
908
|
+
renamedModifiedMustReferenceNew: (specName: string, toName: string) => string;
|
|
909
|
+
renamedToCollidesWithAdded: (specName: string, toName: string) => string;
|
|
910
|
+
requirementInMultipleSections: (specName: string, sectionA: string, sectionB: string, reqName: string) => string;
|
|
911
|
+
noDeltaOperations: (capability: string) => string;
|
|
912
|
+
targetSpecNotExists: (specName: string) => string;
|
|
913
|
+
targetSpecStructurallyInvalid: (specName: string, details: string) => string;
|
|
914
|
+
renamedFailedSourceNotFound: (specName: string, reqName: string) => string;
|
|
915
|
+
renamedFailedTargetExists: (specName: string, reqName: string) => string;
|
|
916
|
+
removedFailedNotFound: (specName: string, reqName: string) => string;
|
|
917
|
+
modifiedFailedNotFound: (specName: string, reqName: string) => string;
|
|
918
|
+
modifiedFailedHeaderMismatch: (specName: string, reqName: string) => string;
|
|
919
|
+
addedFailedAlreadyExists: (specName: string, reqName: string) => string;
|
|
920
|
+
applyingChangesTo: (specPath: string) => string;
|
|
921
|
+
wouldApplyChangesTo: (specPath: string) => string;
|
|
922
|
+
countAdded: (n: number) => string;
|
|
923
|
+
countModified: (n: number) => string;
|
|
924
|
+
countRemoved: (n: number) => string;
|
|
925
|
+
countRenamed: (n: number) => string;
|
|
926
|
+
skeletonPurpose: (changeName: string) => string;
|
|
927
|
+
changeNotFound: (changeName: string) => string;
|
|
928
|
+
validationErrorsInRebuiltSpec: (specName: string, errors: string) => string;
|
|
929
|
+
};
|
|
930
|
+
export declare const PROJECT_CONFIG_SUGGEST_MESSAGES: {
|
|
931
|
+
configNotValidYaml: string;
|
|
932
|
+
configFailedToParse: string;
|
|
933
|
+
unknownArtifactId: (artifactId: string, schemaName: string, validIds: string) => string;
|
|
934
|
+
schemaNotFound: (schemaName: string) => string;
|
|
935
|
+
didYouMean: string;
|
|
936
|
+
schemaType: (isBuiltIn: boolean) => "nativo" | "local do projeto";
|
|
937
|
+
availableSchemas: string;
|
|
938
|
+
builtInSchemas: (schemas: string) => string;
|
|
939
|
+
projectLocalSchemas: (schemas: string) => string;
|
|
940
|
+
noProjectLocalSchemas: string;
|
|
941
|
+
fixSuggestion: (invalidName: string) => string;
|
|
942
|
+
};
|
|
943
|
+
export declare const TOOLS_MANAGER_MESSAGES: {
|
|
944
|
+
toolDoesNotSupportSkills: (toolValue: string) => string;
|
|
945
|
+
};
|
|
946
|
+
export declare const ARTIFACT_GRAPH_MESSAGES: {
|
|
947
|
+
invalidSchema: (errors: string) => string;
|
|
948
|
+
duplicateArtifactId: (id: string) => string;
|
|
949
|
+
invalidDependencyReference: (artifactId: string, ref: string) => string;
|
|
950
|
+
cyclicDependency: (cycle: string) => string;
|
|
951
|
+
templateNotFound: (path: string) => string;
|
|
952
|
+
failedToReadTemplate: (error: string) => string;
|
|
953
|
+
artifactNotFound: (artifactId: string, schemaName: string) => string;
|
|
954
|
+
};
|
|
955
|
+
export declare const CHANGE_METADATA_MESSAGES: {
|
|
956
|
+
failedToWriteMetadata: (error: string) => string;
|
|
957
|
+
failedToReadMetadata: (error: string) => string;
|
|
958
|
+
invalidMetadata: (error: string) => string;
|
|
959
|
+
invalidYaml: (error: string) => string;
|
|
960
|
+
unknownSchema: (schema: string, available: string) => string;
|
|
961
|
+
};
|
|
962
|
+
export declare const CHANGE_UTILS_MESSAGES: {
|
|
963
|
+
changeAlreadyExists: (name: string, dir: string) => string;
|
|
964
|
+
nameEmpty: string;
|
|
965
|
+
nameMustBeLowercase: string;
|
|
966
|
+
nameNoSpaces: string;
|
|
967
|
+
nameNoUnderscores: string;
|
|
968
|
+
nameNoStartHyphen: string;
|
|
969
|
+
nameNoEndHyphen: string;
|
|
970
|
+
nameNoConsecutiveHyphens: string;
|
|
971
|
+
nameOnlyAllowedChars: string;
|
|
972
|
+
nameMustStartWithLetter: string;
|
|
973
|
+
nameKebabCase: string;
|
|
974
|
+
};
|
|
975
|
+
export declare const COMPLETIONS_FACTORY_MESSAGES: {
|
|
976
|
+
unsupportedShell: (shell: string) => string;
|
|
977
|
+
};
|
|
867
978
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/messages/index.js
CHANGED
|
@@ -669,6 +669,38 @@ export const COMPLETION_MESSAGES = {
|
|
|
669
669
|
warningCouldNotRemoveLegacy: (path, err) => `Aviso: Não foi possível remover arquivo legado ${path}: ${err}`,
|
|
670
670
|
powershellCompletionHeader: '# Script de autocompletar PowerShell para a CLI do BR-OpenSpec',
|
|
671
671
|
powershellCompletionNote: '# Gerado automaticamente - não edite manualmente',
|
|
672
|
+
// Fish installer messages
|
|
673
|
+
fishAlreadyInstalled: 'Script de autocomplete já está instalado (atualizado)',
|
|
674
|
+
fishAlreadyInstalledDetail: 'O script de autocomplete já está instalado e atualizado.',
|
|
675
|
+
fishAutoLoadsHint: 'O Fish carrega automaticamente os scripts de autocomplete - devem estar disponíveis imediatamente.',
|
|
676
|
+
fishUpdatedWithBackup: 'Script de autocomplete atualizado com sucesso (versão anterior salva em backup)',
|
|
677
|
+
fishUpdated: 'Script de autocomplete atualizado com sucesso',
|
|
678
|
+
fishInstalled: 'Script de autocomplete instalado com sucesso para Fish',
|
|
679
|
+
fishAutoLoadsDir: 'O Fish carrega automaticamente os scripts de autocomplete de ~/.config/fish/completions/',
|
|
680
|
+
fishAvailableImmediately: 'Os autocompletes estão disponíveis imediatamente - sem necessidade de reiniciar o shell.',
|
|
681
|
+
fishFailedToInstall: (error) => `Falha ao instalar script de autocomplete: ${error}`,
|
|
682
|
+
fishNotInstalled: 'Script de autocomplete não está instalado',
|
|
683
|
+
fishUninstalled: 'Script de autocomplete desinstalado com sucesso',
|
|
684
|
+
fishFailedToUninstall: (error) => `Falha ao desinstalar script de autocomplete: ${error}`,
|
|
685
|
+
// PowerShell installer messages
|
|
686
|
+
powershellUtf16BEUnsupported: 'Arquivo codificado em UTF-16 BE não é suportado. Salve novamente como UTF-8 ou UTF-16 LE e tente novamente.',
|
|
687
|
+
powershellAlreadyInstalled: 'Script de autocomplete já está instalado (atualizado)',
|
|
688
|
+
powershellAlreadyInstalledDetail: 'O script de autocomplete já está instalado e atualizado.',
|
|
689
|
+
powershellAlreadyInstalledHint: 'Se o autocomplete não estiver funcionando, tente reiniciar o PowerShell ou execute: . $PROFILE',
|
|
690
|
+
powershellUpdatedWithBackup: 'Script de autocomplete atualizado com sucesso (versão anterior salva em backup)',
|
|
691
|
+
powershellUpdated: 'Script de autocomplete atualizado com sucesso',
|
|
692
|
+
powershellInstalledWithProfile: 'Script de autocomplete instalado e perfil do PowerShell configurado com sucesso',
|
|
693
|
+
powershellInstalled: 'Script de autocomplete instalado com sucesso para PowerShell',
|
|
694
|
+
powershellFailedToInstall: (error) => `Falha ao instalar script de autocomplete: ${error}`,
|
|
695
|
+
powershellScriptInstalled: 'Script de autocomplete instalado com sucesso.',
|
|
696
|
+
powershellEnableCompletions: (profilePath) => `Para ativar o autocomplete, adicione o seguinte ao seu perfil PowerShell (${profilePath}):`,
|
|
697
|
+
powershellSourceComment: '# Carrega os autocompletes do BR-OpenSpec',
|
|
698
|
+
powershellThenRestart: 'Depois reinicie o PowerShell ou execute: . $PROFILE',
|
|
699
|
+
powershellNotInstalled: 'Script de autocomplete não está instalado',
|
|
700
|
+
powershellUninstalled: 'Script de autocomplete desinstalado com sucesso',
|
|
701
|
+
powershellFailedToUninstall: (error) => `Falha ao desinstalar script de autocomplete: ${error}`,
|
|
702
|
+
// Zsh installer (missing)
|
|
703
|
+
zshNotInstalled: 'Script de autocomplete não está instalado',
|
|
672
704
|
};
|
|
673
705
|
// ═══════════════════════════════════════════════════════════
|
|
674
706
|
// Comandos — Feedback (src/commands/feedback.ts)
|
|
@@ -1957,4 +1989,110 @@ Use markdown claro com:
|
|
|
1957
1989
|
export const TELEMETRY_MESSAGES = {
|
|
1958
1990
|
firstRunNotice: 'Aviso: o BR-OpenSpec coleta estatísticas de uso anônimas. Para optar por não participar, defina OPENSPEC_TELEMETRY=0',
|
|
1959
1991
|
};
|
|
1992
|
+
// ═══════════════════════════════════════════════════════════
|
|
1993
|
+
// Parser — Change Parser (src/core/parsers/change-parser.ts)
|
|
1994
|
+
// ═══════════════════════════════════════════════════════════
|
|
1995
|
+
export const CHANGE_PARSER_MESSAGES = {
|
|
1996
|
+
mustHaveWhySection: 'A alteração deve ter uma seção Why',
|
|
1997
|
+
mustHaveWhatChangesSection: 'A alteração deve ter uma seção What Changes',
|
|
1998
|
+
addRequirement: (text) => `Adicionar requisito: ${text}`,
|
|
1999
|
+
modifyRequirement: (text) => `Modificar requisito: ${text}`,
|
|
2000
|
+
removeRequirement: (text) => `Remover requisito: ${text}`,
|
|
2001
|
+
renameRequirement: (from, to) => `Renomear requisito de "${from}" para "${to}"`,
|
|
2002
|
+
};
|
|
2003
|
+
// ═══════════════════════════════════════════════════════════
|
|
2004
|
+
// Core — Specs Apply (src/core/specs-apply.ts)
|
|
2005
|
+
// ═══════════════════════════════════════════════════════════
|
|
2006
|
+
export const SPECS_APPLY_MESSAGES = {
|
|
2007
|
+
duplicateInSection: (specName, section, reqName) => `${specName} validação falhou - requisito duplicado em ${section} para cabeçalho "### Requirement: ${reqName}"`,
|
|
2008
|
+
duplicateFromInRenamed: (specName, reqName) => `${specName} validação falhou - FROM duplicado em RENAMED para cabeçalho "### Requirement: ${reqName}"`,
|
|
2009
|
+
duplicateToInRenamed: (specName, reqName) => `${specName} validação falhou - TO duplicado em RENAMED para cabeçalho "### Requirement: ${reqName}"`,
|
|
2010
|
+
renamedModifiedMustReferenceNew: (specName, toName) => `${specName} validação falhou - quando existe um rename, MODIFIED deve referenciar o NOVO cabeçalho "### Requirement: ${toName}"`,
|
|
2011
|
+
renamedToCollidesWithAdded: (specName, toName) => `${specName} validação falhou - cabeçalho RENAMED TO colide com ADDED para "### Requirement: ${toName}"`,
|
|
2012
|
+
requirementInMultipleSections: (specName, sectionA, sectionB, reqName) => `${specName} validação falhou - requisito presente em múltiplas seções (${sectionA} e ${sectionB}) para cabeçalho "### Requirement: ${reqName}"`,
|
|
2013
|
+
noDeltaOperations: (capability) => `Análise de delta não encontrou operações para ${capability}. Forneça seções ADDED/MODIFIED/REMOVED/RENAMED no spec da change.`,
|
|
2014
|
+
targetSpecNotExists: (specName) => `${specName}: spec alvo não existe; somente requisitos ADDED são permitidos para specs novos. Operações MODIFIED e RENAMED requerem um spec existente.`,
|
|
2015
|
+
targetSpecStructurallyInvalid: (specName, details) => `${specName}: spec alvo é estruturalmente inválido e não pode ser atualizado até ser corrigido:\n${details}`,
|
|
2016
|
+
renamedFailedSourceNotFound: (specName, reqName) => `${specName} RENAMED falhou para cabeçalho "### Requirement: ${reqName}" - origem não encontrada`,
|
|
2017
|
+
renamedFailedTargetExists: (specName, reqName) => `${specName} RENAMED falhou para cabeçalho "### Requirement: ${reqName}" - destino já existe`,
|
|
2018
|
+
removedFailedNotFound: (specName, reqName) => `${specName} REMOVED falhou para cabeçalho "### Requirement: ${reqName}" - não encontrado`,
|
|
2019
|
+
modifiedFailedNotFound: (specName, reqName) => `${specName} MODIFIED falhou para cabeçalho "### Requirement: ${reqName}" - não encontrado`,
|
|
2020
|
+
modifiedFailedHeaderMismatch: (specName, reqName) => `${specName} MODIFIED falhou para cabeçalho "### Requirement: ${reqName}" - incompatibilidade de cabeçalho no conteúdo`,
|
|
2021
|
+
addedFailedAlreadyExists: (specName, reqName) => `${specName} ADDED falhou para cabeçalho "### Requirement: ${reqName}" - já existe`,
|
|
2022
|
+
applyingChangesTo: (specPath) => `Aplicando alterações em openspec/specs/${specPath}/spec.md:`,
|
|
2023
|
+
wouldApplyChangesTo: (specPath) => `Aplicaria alterações em openspec/specs/${specPath}/spec.md:`,
|
|
2024
|
+
countAdded: (n) => ` + ${n} adicionado(s)`,
|
|
2025
|
+
countModified: (n) => ` ~ ${n} modificado(s)`,
|
|
2026
|
+
countRemoved: (n) => ` - ${n} removido(s)`,
|
|
2027
|
+
countRenamed: (n) => ` → ${n} renomeado(s)`,
|
|
2028
|
+
skeletonPurpose: (changeName) => `A definir - criado ao arquivar alteração ${changeName}. Atualize o Purpose após o arquivamento.`,
|
|
2029
|
+
changeNotFound: (changeName) => `Alteração '${changeName}' não encontrada.`,
|
|
2030
|
+
validationErrorsInRebuiltSpec: (specName, errors) => `Erros de validação na especificação reconstruída para ${specName}:\n${errors}`,
|
|
2031
|
+
};
|
|
2032
|
+
// ═══════════════════════════════════════════════════════════
|
|
2033
|
+
// Core — Project Config (src/core/project-config.ts)
|
|
2034
|
+
// ═══════════════════════════════════════════════════════════
|
|
2035
|
+
export const PROJECT_CONFIG_SUGGEST_MESSAGES = {
|
|
2036
|
+
configNotValidYaml: 'openspec/config.yaml não é um objeto YAML válido',
|
|
2037
|
+
configFailedToParse: 'Falha ao analisar openspec/config.yaml:',
|
|
2038
|
+
unknownArtifactId: (artifactId, schemaName, validIds) => `ID de artefato desconhecido nas regras: "${artifactId}". IDs válidos para o schema "${schemaName}": ${validIds}`,
|
|
2039
|
+
schemaNotFound: (schemaName) => `Schema '${schemaName}' não encontrado em openspec/config.yaml\n\n`,
|
|
2040
|
+
didYouMean: 'Você quis dizer algum destes?\n',
|
|
2041
|
+
schemaType: (isBuiltIn) => isBuiltIn ? 'nativo' : 'local do projeto',
|
|
2042
|
+
availableSchemas: 'Schemas disponíveis:\n',
|
|
2043
|
+
builtInSchemas: (schemas) => ` Nativos: ${schemas}\n`,
|
|
2044
|
+
projectLocalSchemas: (schemas) => ` Locais do projeto: ${schemas}\n`,
|
|
2045
|
+
noProjectLocalSchemas: ' Locais do projeto: (nenhum encontrado)\n',
|
|
2046
|
+
fixSuggestion: (invalidName) => `\nCorreção: Edite openspec/config.yaml e altere 'schema: ${invalidName}' para um nome de schema válido`,
|
|
2047
|
+
};
|
|
2048
|
+
// ═══════════════════════════════════════════════════════════
|
|
2049
|
+
// Core — Tools Manager (src/core/tools-manager.ts)
|
|
2050
|
+
// ═══════════════════════════════════════════════════════════
|
|
2051
|
+
export const TOOLS_MANAGER_MESSAGES = {
|
|
2052
|
+
toolDoesNotSupportSkills: (toolValue) => `A ferramenta '${toolValue}' não suporta geração de skills.`,
|
|
2053
|
+
};
|
|
2054
|
+
// ═══════════════════════════════════════════════════════════
|
|
2055
|
+
// Core — Artifact Graph (src/core/artifact-graph/)
|
|
2056
|
+
// ═══════════════════════════════════════════════════════════
|
|
2057
|
+
export const ARTIFACT_GRAPH_MESSAGES = {
|
|
2058
|
+
invalidSchema: (errors) => `Schema inválido: ${errors}`,
|
|
2059
|
+
duplicateArtifactId: (id) => `ID de artefato duplicado: ${id}`,
|
|
2060
|
+
invalidDependencyReference: (artifactId, ref) => `Referência de dependência inválida no artefato '${artifactId}': '${ref}' não existe`,
|
|
2061
|
+
cyclicDependency: (cycle) => `Dependência cíclica detectada: ${cycle}`,
|
|
2062
|
+
templateNotFound: (path) => `Template não encontrado: ${path}`,
|
|
2063
|
+
failedToReadTemplate: (error) => `Falha ao ler template: ${error}`,
|
|
2064
|
+
artifactNotFound: (artifactId, schemaName) => `Artefato '${artifactId}' não encontrado no schema '${schemaName}'`,
|
|
2065
|
+
};
|
|
2066
|
+
// ═══════════════════════════════════════════════════════════
|
|
2067
|
+
// Utils — Change Metadata (src/utils/change-metadata.ts)
|
|
2068
|
+
// ═══════════════════════════════════════════════════════════
|
|
2069
|
+
export const CHANGE_METADATA_MESSAGES = {
|
|
2070
|
+
failedToWriteMetadata: (error) => `Falha ao escrever metadados: ${error}`,
|
|
2071
|
+
failedToReadMetadata: (error) => `Falha ao ler metadados: ${error}`,
|
|
2072
|
+
invalidMetadata: (error) => `Metadados inválidos: ${error}`,
|
|
2073
|
+
invalidYaml: (error) => `YAML inválido no arquivo de metadados: ${error}`,
|
|
2074
|
+
unknownSchema: (schema, available) => `Schema desconhecido '${schema}'. Disponíveis: ${available}`,
|
|
2075
|
+
};
|
|
2076
|
+
// ═══════════════════════════════════════════════════════════
|
|
2077
|
+
// Utils — Change Utils (src/utils/change-utils.ts)
|
|
2078
|
+
// ═══════════════════════════════════════════════════════════
|
|
2079
|
+
export const CHANGE_UTILS_MESSAGES = {
|
|
2080
|
+
changeAlreadyExists: (name, dir) => `A alteração '${name}' já existe em ${dir}`,
|
|
2081
|
+
nameEmpty: 'O nome da alteração não pode estar vazio',
|
|
2082
|
+
nameMustBeLowercase: 'O nome da alteração deve ser minúsculo (use kebab-case)',
|
|
2083
|
+
nameNoSpaces: 'O nome da alteração não pode conter espaços (use hífens)',
|
|
2084
|
+
nameNoUnderscores: 'O nome da alteração não pode conter underscores (use hífens)',
|
|
2085
|
+
nameNoStartHyphen: 'O nome da alteração não pode começar com hífen',
|
|
2086
|
+
nameNoEndHyphen: 'O nome da alteração não pode terminar com hífen',
|
|
2087
|
+
nameNoConsecutiveHyphens: 'O nome da alteração não pode conter hífens consecutivos',
|
|
2088
|
+
nameOnlyAllowedChars: 'O nome da alteração pode conter apenas letras minúsculas, números e hífens',
|
|
2089
|
+
nameMustStartWithLetter: 'O nome da alteração deve começar com uma letra',
|
|
2090
|
+
nameKebabCase: 'O nome da alteração deve seguir a convenção kebab-case (ex: add-auth, refactor-db)',
|
|
2091
|
+
};
|
|
2092
|
+
// ═══════════════════════════════════════════════════════════
|
|
2093
|
+
// Core — Completions Factory (src/core/completions/factory.ts)
|
|
2094
|
+
// ═══════════════════════════════════════════════════════════
|
|
2095
|
+
export const COMPLETIONS_FACTORY_MESSAGES = {
|
|
2096
|
+
unsupportedShell: (shell) => `Shell não suportado: ${shell}`,
|
|
2097
|
+
};
|
|
1960
2098
|
//# sourceMappingURL=index.js.map
|
|
@@ -4,6 +4,7 @@ import * as yaml from 'yaml';
|
|
|
4
4
|
import { ChangeMetadataSchema } from '../core/artifact-graph/types.js';
|
|
5
5
|
import { listSchemas } from '../core/artifact-graph/resolver.js';
|
|
6
6
|
import { readProjectConfig } from '../core/project-config.js';
|
|
7
|
+
import { CHANGE_METADATA_MESSAGES } from '../messages/index.js';
|
|
7
8
|
const METADATA_FILENAME = '.openspec.yaml';
|
|
8
9
|
/**
|
|
9
10
|
* Error thrown when change metadata validation fails.
|
|
@@ -29,7 +30,7 @@ export class ChangeMetadataError extends Error {
|
|
|
29
30
|
export function validateSchemaName(schemaName, projectRoot) {
|
|
30
31
|
const availableSchemas = listSchemas(projectRoot);
|
|
31
32
|
if (!availableSchemas.includes(schemaName)) {
|
|
32
|
-
throw new Error(
|
|
33
|
+
throw new Error(CHANGE_METADATA_MESSAGES.unknownSchema(schemaName, availableSchemas.join(', ')));
|
|
33
34
|
}
|
|
34
35
|
return schemaName;
|
|
35
36
|
}
|
|
@@ -48,7 +49,7 @@ export function writeChangeMetadata(changeDir, metadata, projectRoot) {
|
|
|
48
49
|
// Validate with Zod
|
|
49
50
|
const parseResult = ChangeMetadataSchema.safeParse(metadata);
|
|
50
51
|
if (!parseResult.success) {
|
|
51
|
-
throw new ChangeMetadataError(
|
|
52
|
+
throw new ChangeMetadataError(CHANGE_METADATA_MESSAGES.invalidMetadata(parseResult.error.message), metaPath);
|
|
52
53
|
}
|
|
53
54
|
// Write YAML file
|
|
54
55
|
const content = yaml.stringify(parseResult.data);
|
|
@@ -57,7 +58,7 @@ export function writeChangeMetadata(changeDir, metadata, projectRoot) {
|
|
|
57
58
|
}
|
|
58
59
|
catch (err) {
|
|
59
60
|
const ioError = err instanceof Error ? err : new Error(String(err));
|
|
60
|
-
throw new ChangeMetadataError(
|
|
61
|
+
throw new ChangeMetadataError(CHANGE_METADATA_MESSAGES.failedToWriteMetadata(ioError.message), metaPath, ioError);
|
|
61
62
|
}
|
|
62
63
|
}
|
|
63
64
|
/**
|
|
@@ -79,7 +80,7 @@ export function readChangeMetadata(changeDir, projectRoot) {
|
|
|
79
80
|
}
|
|
80
81
|
catch (err) {
|
|
81
82
|
const ioError = err instanceof Error ? err : new Error(String(err));
|
|
82
|
-
throw new ChangeMetadataError(
|
|
83
|
+
throw new ChangeMetadataError(CHANGE_METADATA_MESSAGES.failedToReadMetadata(ioError.message), metaPath, ioError);
|
|
83
84
|
}
|
|
84
85
|
let parsed;
|
|
85
86
|
try {
|
|
@@ -87,17 +88,17 @@ export function readChangeMetadata(changeDir, projectRoot) {
|
|
|
87
88
|
}
|
|
88
89
|
catch (err) {
|
|
89
90
|
const parseError = err instanceof Error ? err : new Error(String(err));
|
|
90
|
-
throw new ChangeMetadataError(
|
|
91
|
+
throw new ChangeMetadataError(CHANGE_METADATA_MESSAGES.invalidYaml(parseError.message), metaPath, parseError);
|
|
91
92
|
}
|
|
92
93
|
// Validate with Zod
|
|
93
94
|
const parseResult = ChangeMetadataSchema.safeParse(parsed);
|
|
94
95
|
if (!parseResult.success) {
|
|
95
|
-
throw new ChangeMetadataError(
|
|
96
|
+
throw new ChangeMetadataError(CHANGE_METADATA_MESSAGES.invalidMetadata(parseResult.error.message), metaPath);
|
|
96
97
|
}
|
|
97
98
|
// Validate that the schema exists
|
|
98
99
|
const availableSchemas = listSchemas(projectRoot);
|
|
99
100
|
if (!availableSchemas.includes(parseResult.data.schema)) {
|
|
100
|
-
throw new ChangeMetadataError(
|
|
101
|
+
throw new ChangeMetadataError(CHANGE_METADATA_MESSAGES.unknownSchema(parseResult.data.schema, availableSchemas.join(', ')), metaPath);
|
|
101
102
|
}
|
|
102
103
|
return parseResult.data;
|
|
103
104
|
}
|
|
@@ -2,6 +2,7 @@ import path from 'path';
|
|
|
2
2
|
import { FileSystemUtils } from './file-system.js';
|
|
3
3
|
import { writeChangeMetadata, validateSchemaName } from './change-metadata.js';
|
|
4
4
|
import { readProjectConfig } from '../core/project-config.js';
|
|
5
|
+
import { CHANGE_UTILS_MESSAGES } from '../messages/index.js';
|
|
5
6
|
const DEFAULT_SCHEMA = 'spec-driven';
|
|
6
7
|
/**
|
|
7
8
|
* Validates that a change name follows kebab-case conventions.
|
|
@@ -24,35 +25,35 @@ export function validateChangeName(name) {
|
|
|
24
25
|
// optionally followed by hyphen + lowercase letters/numbers (repeatable)
|
|
25
26
|
const kebabCasePattern = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
26
27
|
if (!name) {
|
|
27
|
-
return { valid: false, error:
|
|
28
|
+
return { valid: false, error: CHANGE_UTILS_MESSAGES.nameEmpty };
|
|
28
29
|
}
|
|
29
30
|
if (!kebabCasePattern.test(name)) {
|
|
30
31
|
// Provide specific error messages for common mistakes
|
|
31
32
|
if (/[A-Z]/.test(name)) {
|
|
32
|
-
return { valid: false, error:
|
|
33
|
+
return { valid: false, error: CHANGE_UTILS_MESSAGES.nameMustBeLowercase };
|
|
33
34
|
}
|
|
34
35
|
if (/\s/.test(name)) {
|
|
35
|
-
return { valid: false, error:
|
|
36
|
+
return { valid: false, error: CHANGE_UTILS_MESSAGES.nameNoSpaces };
|
|
36
37
|
}
|
|
37
38
|
if (/_/.test(name)) {
|
|
38
|
-
return { valid: false, error:
|
|
39
|
+
return { valid: false, error: CHANGE_UTILS_MESSAGES.nameNoUnderscores };
|
|
39
40
|
}
|
|
40
41
|
if (name.startsWith('-')) {
|
|
41
|
-
return { valid: false, error:
|
|
42
|
+
return { valid: false, error: CHANGE_UTILS_MESSAGES.nameNoStartHyphen };
|
|
42
43
|
}
|
|
43
44
|
if (name.endsWith('-')) {
|
|
44
|
-
return { valid: false, error:
|
|
45
|
+
return { valid: false, error: CHANGE_UTILS_MESSAGES.nameNoEndHyphen };
|
|
45
46
|
}
|
|
46
47
|
if (/--/.test(name)) {
|
|
47
|
-
return { valid: false, error:
|
|
48
|
+
return { valid: false, error: CHANGE_UTILS_MESSAGES.nameNoConsecutiveHyphens };
|
|
48
49
|
}
|
|
49
50
|
if (/[^a-z0-9-]/.test(name)) {
|
|
50
|
-
return { valid: false, error:
|
|
51
|
+
return { valid: false, error: CHANGE_UTILS_MESSAGES.nameOnlyAllowedChars };
|
|
51
52
|
}
|
|
52
53
|
if (/^[0-9]/.test(name)) {
|
|
53
|
-
return { valid: false, error:
|
|
54
|
+
return { valid: false, error: CHANGE_UTILS_MESSAGES.nameMustStartWithLetter };
|
|
54
55
|
}
|
|
55
|
-
return { valid: false, error:
|
|
56
|
+
return { valid: false, error: CHANGE_UTILS_MESSAGES.nameKebabCase };
|
|
56
57
|
}
|
|
57
58
|
return { valid: true };
|
|
58
59
|
}
|
|
@@ -106,7 +107,7 @@ export async function createChange(projectRoot, name, options = {}) {
|
|
|
106
107
|
const changeDir = path.join(projectRoot, 'openspec', 'changes', name);
|
|
107
108
|
// Check if change already exists
|
|
108
109
|
if (await FileSystemUtils.directoryExists(changeDir)) {
|
|
109
|
-
throw new Error(
|
|
110
|
+
throw new Error(CHANGE_UTILS_MESSAGES.changeAlreadyExists(name, changeDir));
|
|
110
111
|
}
|
|
111
112
|
// Create the directory (including parent directories if needed)
|
|
112
113
|
await FileSystemUtils.createDirectory(changeDir);
|