@oevery/rsp 3.2.0 → 4.0.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 (68) hide show
  1. package/README.md +25 -17
  2. package/README.zh-CN.md +25 -17
  3. package/dist/chunk-HFSTLCCK.mjs +3968 -0
  4. package/dist/cli.mjs +3510 -2446
  5. package/dist/entry-RIJQ2X46.mjs +1932 -0
  6. package/package.json +32 -19
  7. package/skills/rsp/SKILL.md +42 -68
  8. package/skills/rsp/references/closeout.md +38 -0
  9. package/skills/rsp/references/context-migration.md +22 -0
  10. package/skills/rsp/references/control-outcome.md +5 -0
  11. package/skills/rsp/references/coordination-recovery.md +25 -0
  12. package/skills/rsp/references/coordination-review.md +15 -0
  13. package/skills/rsp/references/coordination.md +59 -0
  14. package/skills/rsp/references/durable-review.md +74 -9
  15. package/skills/rsp/references/focus-continuation.md +13 -0
  16. package/skills/rsp/references/groups-dependencies.md +6 -4
  17. package/skills/rsp/references/response-language.md +13 -0
  18. package/skills/rsp/references/setup-repair.md +37 -7
  19. package/skills/rsp/references/work-model.md +27 -0
  20. package/skills/rsp-commit/SKILL.md +82 -22
  21. package/skills/rsp-commit/references/message.md +34 -0
  22. package/skills/rsp-doc/SKILL.md +65 -0
  23. package/skills/rsp-doc/agents/openai.yaml +3 -0
  24. package/skills/rsp-doc/references/artifact-lenses.md +43 -0
  25. package/skills/rsp-doc/references/release-communication.md +39 -0
  26. package/skills/rsp-doc/references/skill-authoring.md +36 -0
  27. package/skills/rsp-implement/SKILL.md +62 -21
  28. package/skills/rsp-implement/references/diagnosis.md +10 -0
  29. package/skills/rsp-implement/references/finding-resolution.md +21 -0
  30. package/skills/rsp-implement/references/test-first.md +7 -0
  31. package/skills/rsp-review/SKILL.md +44 -46
  32. package/skills/rsp-review/references/code-review.md +23 -7
  33. package/skills/rsp-review/references/document-review.md +25 -8
  34. package/skills/rsp-review/references/release-communication.md +21 -0
  35. package/skills/{rsp-structural-audit/references/structural-lenses.md → rsp-review/references/structural-review.md} +5 -5
  36. package/skills/rsp-shape/SKILL.md +24 -45
  37. package/skills/rsp-shape/references/complex-shaping.md +3 -1
  38. package/skills/rsp-shape/references/deep-clarification.md +4 -4
  39. package/skills/rsp-shape/references/domain-modeling.md +13 -0
  40. package/skills/rsp-shape/references/module-seams.md +13 -0
  41. package/skills/rsp-shape/references/planning.md +42 -0
  42. package/skills/{rsp-design → rsp-shape}/references/reversible-exploration.md +2 -2
  43. package/skills/rsp-verify/SKILL.md +72 -0
  44. package/dist/chunk-JOMFFQRG.mjs +0 -99
  45. package/dist/chunk-JYDEJEM4.mjs +0 -358
  46. package/dist/chunk-SEGJLWRE.mjs +0 -1158
  47. package/dist/chunk-ZORUJTVS.mjs +0 -1967
  48. package/dist/entry-I22OUIW5.mjs +0 -888
  49. package/dist/entry-RG6HREUN.mjs +0 -174
  50. package/rules/rsp-rules.md +0 -47
  51. package/skills/rsp/references/managed-routing.md +0 -44
  52. package/skills/rsp/references/release-operations.md +0 -9
  53. package/skills/rsp-design/SKILL.md +0 -53
  54. package/skills/rsp-design/references/domain-modeling.md +0 -11
  55. package/skills/rsp-design/references/module-seams.md +0 -11
  56. package/skills/rsp-diagnose/SKILL.md +0 -46
  57. package/skills/rsp-manage/SKILL.md +0 -96
  58. package/skills/rsp-manage/references/closeout.md +0 -11
  59. package/skills/rsp-manage/references/interruption-recovery.md +0 -7
  60. package/skills/rsp-manage/references/review-convergence.md +0 -7
  61. package/skills/rsp-release-docs/SKILL.md +0 -77
  62. package/skills/rsp-release-docs/references/convention-discovery.md +0 -65
  63. package/skills/rsp-release-docs/references/evidence-and-surfaces.md +0 -21
  64. package/skills/rsp-release-docs/references/output-contracts.md +0 -97
  65. package/skills/rsp-release-docs/references/publication-lifecycle.md +0 -24
  66. package/skills/rsp-resolve-findings/SKILL.md +0 -61
  67. package/skills/rsp-structural-audit/SKILL.md +0 -71
  68. package/skills/rsp-tdd/SKILL.md +0 -48
@@ -1,1158 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // src/core/config.ts
4
- import { readFile as readFile2 } from "fs/promises";
5
- import { dirname as dirname3, join as join4 } from "path";
6
- import { fileURLToPath } from "url";
7
- import pc from "picocolors";
8
-
9
- // src/core/decisions.ts
10
- import { existsSync } from "fs";
11
- import { mkdir, readdir as readdir2, realpath, stat } from "fs/promises";
12
- import { dirname, isAbsolute, join as join2, posix, relative, resolve, sep } from "path";
13
-
14
- // src/core/managed-path.ts
15
- import { lstatSync } from "fs";
16
- import { readdir, writeFile } from "fs/promises";
17
- import { join } from "path";
18
- var ManagedPathError = class extends Error {
19
- constructor(path, message) {
20
- super(message);
21
- this.path = path;
22
- this.name = "ManagedPathError";
23
- }
24
- path;
25
- };
26
- function inspectManagedDirectory(path, label, options = {}) {
27
- try {
28
- const stats = lstatSync(path);
29
- if (stats.isDirectory())
30
- return { exists: true, issue: null };
31
- return {
32
- exists: true,
33
- issue: new ManagedPathError(path, `${label} must be a real directory: ${path}`)
34
- };
35
- } catch (error) {
36
- if (error.code === "ENOENT") {
37
- if (options.allowMissing)
38
- return { exists: false, issue: null };
39
- return {
40
- exists: false,
41
- issue: new ManagedPathError(path, `${label} must be a real directory: ${path}`)
42
- };
43
- }
44
- const message = error instanceof Error ? error.message : String(error);
45
- return {
46
- exists: false,
47
- issue: new ManagedPathError(path, `unable to inspect ${label} ${path}: ${message}`)
48
- };
49
- }
50
- }
51
- function requireManagedDirectory(path, label, options = {}) {
52
- const inspection = inspectManagedDirectory(path, label, options);
53
- if (inspection.issue)
54
- throw inspection.issue;
55
- return inspection.exists;
56
- }
57
- function inspectManagedFile(path, label, options = {}) {
58
- try {
59
- const stats = lstatSync(path);
60
- if (stats.isFile())
61
- return { exists: true, issue: null };
62
- return {
63
- exists: true,
64
- issue: new ManagedPathError(path, `${label} must be a regular file: ${path}`)
65
- };
66
- } catch (error) {
67
- if (error.code === "ENOENT") {
68
- if (options.allowMissing)
69
- return { exists: false, issue: null };
70
- return {
71
- exists: false,
72
- issue: new ManagedPathError(path, `${label} must be a regular file: ${path}`)
73
- };
74
- }
75
- const message = error instanceof Error ? error.message : String(error);
76
- return {
77
- exists: false,
78
- issue: new ManagedPathError(path, `unable to inspect ${label} ${path}: ${message}`)
79
- };
80
- }
81
- }
82
- function requireManagedFile(path, label, options = {}) {
83
- const inspection = inspectManagedFile(path, label, options);
84
- if (inspection.issue)
85
- throw inspection.issue;
86
- return inspection.exists;
87
- }
88
- async function ensureManagedFile(path, content, label) {
89
- if (requireManagedFile(path, label, { allowMissing: true }))
90
- return false;
91
- await writeFile(path, content, { flag: "wx" });
92
- return true;
93
- }
94
- async function writeManagedFile(path, content, label) {
95
- const exists = requireManagedFile(path, label, { allowMissing: true });
96
- if (exists)
97
- await writeFile(path, content);
98
- else
99
- await writeFile(path, content, { flag: "wx" });
100
- }
101
- function resolveManagedDirectoryChain(root, segments, label) {
102
- requireManagedDirectory(root, `${label} root`);
103
- let current = root;
104
- for (const segment of segments) {
105
- if (!segment || segment === "." || segment === ".." || segment.includes("/") || segment.includes("\\"))
106
- throw new ManagedPathError(current, `${label} contains an invalid directory segment: ${segment}`);
107
- current = join(current, segment);
108
- requireManagedDirectory(current, label, { allowMissing: true });
109
- }
110
- return current;
111
- }
112
- async function inspectManagedFileTree(root, label, options = {}) {
113
- const result = { rootExists: false, files: [], issues: [] };
114
- const rootInspection = inspectManagedDirectory(root, `${label} root`, options);
115
- if (rootInspection.issue) {
116
- result.issues.push(rootInspection.issue);
117
- return result;
118
- }
119
- if (!rootInspection.exists)
120
- return result;
121
- result.rootExists = true;
122
- await collectManagedFiles(root, label, result);
123
- result.files.sort();
124
- return result;
125
- }
126
- async function collectManagedFiles(directory, label, result) {
127
- let entries;
128
- try {
129
- entries = await readdir(directory, { withFileTypes: true });
130
- } catch (error) {
131
- const message = error instanceof Error ? error.message : String(error);
132
- result.issues.push(new ManagedPathError(directory, `unable to inspect the ${label} tree at ${directory}: ${message}`));
133
- return;
134
- }
135
- for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
136
- if (entry.name.startsWith("."))
137
- continue;
138
- const path = join(directory, entry.name);
139
- if (entry.isDirectory()) {
140
- await collectManagedFiles(path, label, result);
141
- continue;
142
- }
143
- if (entry.isFile()) {
144
- result.files.push(path);
145
- continue;
146
- }
147
- result.issues.push(new ManagedPathError(path, `unsupported entry in the ${label} tree: ${path}`));
148
- }
149
- }
150
-
151
- // src/core/decisions.ts
152
- var DEFAULT_DECISION_RECORDS_PATH = ".rsp/specs/decisions";
153
- function normalizeDecisionRecordsPath(pathValue) {
154
- return posix.normalize(pathValue.trim().replace(/\\/g, "/")).replace(/^\.\//, "").replace(/\/$/, "");
155
- }
156
- function validateDecisionRecordsPath(pathValue) {
157
- if (typeof pathValue !== "string" || pathValue.trim() === "")
158
- return 'config.yaml field "decisions.path" must be a non-empty string';
159
- const raw = pathValue.trim();
160
- const portable = raw.replace(/\\/g, "/");
161
- if (isAbsolute(raw) || posix.isAbsolute(portable) || /^[a-z]:[\\/]/i.test(raw))
162
- return 'config.yaml field "decisions.path" must be project-relative';
163
- const normalized = normalizeDecisionRecordsPath(raw);
164
- if (normalized === "." || normalized === ".." || normalized.startsWith("../"))
165
- return 'config.yaml field "decisions.path" must stay inside the Host Project';
166
- if ((normalized === ".rsp" || normalized.startsWith(".rsp/")) && normalized !== DEFAULT_DECISION_RECORDS_PATH)
167
- return `config.yaml field "decisions.path" must use ${DEFAULT_DECISION_RECORDS_PATH} or a path outside .rsp/`;
168
- return null;
169
- }
170
- function getDecisionRecordsConfigIssue(config) {
171
- if (!("decisions" in config))
172
- return null;
173
- const decisions = config.decisions;
174
- if (!decisions || typeof decisions !== "object" || Array.isArray(decisions))
175
- return 'config.yaml field "decisions" must be a mapping with one path';
176
- const keys = Object.keys(decisions);
177
- if (keys.length !== 1 || keys[0] !== "path")
178
- return 'config.yaml field "decisions" supports only "path"';
179
- return validateDecisionRecordsPath(decisions.path);
180
- }
181
- function resolveDecisionRecordsPath(config) {
182
- const configured = config.decisions?.path;
183
- return configured && validateDecisionRecordsPath(configured) === null ? normalizeDecisionRecordsPath(configured) : DEFAULT_DECISION_RECORDS_PATH;
184
- }
185
- async function ensureDecisionRecordsDirectory(pathValue) {
186
- const filesystemError = await validateDecisionRecordsFilesystemPath(pathValue);
187
- if (filesystemError)
188
- throw new Error(filesystemError);
189
- const existed = existsSync(pathValue);
190
- await mkdir(pathValue, { recursive: true });
191
- const entries = await readdir2(pathValue);
192
- if (entries.length === 0) {
193
- await ensureManagedFile(join2(pathValue, ".gitkeep"), "", "Decision Record placeholder");
194
- return true;
195
- }
196
- if (entries.includes(".gitkeep"))
197
- requireManagedFile(join2(pathValue, ".gitkeep"), "Decision Record placeholder");
198
- return !existed;
199
- }
200
- async function validateDecisionRecordsFilesystemPath(pathValue) {
201
- const projectRoot = await realpath(".");
202
- let existingAncestor = resolve(pathValue);
203
- while (!existsSync(existingAncestor)) {
204
- const parent = dirname(existingAncestor);
205
- if (parent === existingAncestor)
206
- return `Decision Record path "${pathValue}" has no accessible Host Project ancestor`;
207
- existingAncestor = parent;
208
- }
209
- const resolvedAncestor = await realpath(existingAncestor);
210
- const rel = relative(projectRoot, resolvedAncestor);
211
- if (rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel))
212
- return `Decision Record path "${pathValue}" resolves outside the Host Project through ${existingAncestor}`;
213
- if (existsSync(resolve(pathValue)) && !(await stat(resolve(pathValue))).isDirectory())
214
- return `Decision Record path "${pathValue}" must be a directory`;
215
- return null;
216
- }
217
-
218
- // src/core/document-model.ts
219
- var CHANGE_DOCUMENT_SCHEMA = {
220
- titlePrefix: "# Change:",
221
- sections: [
222
- { id: "proposal", heading: "Proposal" },
223
- { id: "spec", heading: "Spec" },
224
- { id: "design", heading: "Design" },
225
- { id: "tasks", heading: "Tasks" },
226
- { id: "verify", heading: "Verify" },
227
- { id: "blockers", heading: "Blockers" }
228
- ]
229
- };
230
- var GROUP_BRIEF_DOCUMENT_SCHEMA = {
231
- titlePrefix: "# Change Group:",
232
- sections: [
233
- { id: "goal", heading: "Goal" },
234
- { id: "scope", heading: "Scope" },
235
- { id: "sharedConstraints", heading: "Shared Constraints" },
236
- { id: "slices", heading: "Slices" },
237
- { id: "completionConditions", heading: "Completion Conditions" },
238
- { id: "durableOutcomes", heading: "Durable Outcomes" },
239
- { id: "blockers", heading: "Blockers" }
240
- ]
241
- };
242
- var DocumentSectionCardinalityError = class extends Error {
243
- constructor(sectionId, count) {
244
- super(`document must contain exactly one ${sectionId} section; found ${count}`);
245
- this.sectionId = sectionId;
246
- this.count = count;
247
- this.name = "DocumentSectionCardinalityError";
248
- }
249
- sectionId;
250
- count;
251
- };
252
- function parseRspDocument(source, schema) {
253
- const definitionsByHeading = new Map(schema.sections.map((definition) => [definition.heading, definition]));
254
- const observedHeadings = collectLevelTwoHeadings(source);
255
- const sections = /* @__PURE__ */ new Map();
256
- const unknownHeadings = [];
257
- for (let index = 0; index < observedHeadings.length; index++) {
258
- const observed = observedHeadings[index];
259
- const definition = definitionsByHeading.get(observed.heading);
260
- if (!definition) {
261
- unknownHeadings.push(observed.heading);
262
- continue;
263
- }
264
- const nextHeading = observedHeadings[index + 1];
265
- const bodyEnd = nextHeading ? lineBreakStart(source, nextHeading.start) : source.length;
266
- const section = {
267
- id: definition.id,
268
- heading: definition.heading,
269
- canonical: observed.canonical,
270
- body: source.slice(observed.end, bodyEnd).trim(),
271
- headingStart: observed.start,
272
- headingEnd: observed.end,
273
- bodyStart: observed.end,
274
- bodyEnd
275
- };
276
- const matches = sections.get(definition.id) ?? [];
277
- matches.push(section);
278
- sections.set(definition.id, matches);
279
- }
280
- const missingSections = schema.sections.filter((definition) => !sections.has(definition.id)).map((definition) => definition.id);
281
- const duplicateSections = schema.sections.filter((definition) => (sections.get(definition.id)?.length ?? 0) > 1).map((definition) => definition.id);
282
- const titleOccurrences = collectDocumentTitles(source, schema.titlePrefix);
283
- const titles = titleOccurrences.map((title) => title.value);
284
- return {
285
- source,
286
- schema,
287
- title: titles[0] ?? null,
288
- titles,
289
- titleOccurrences,
290
- sections,
291
- unknownHeadings,
292
- missingSections,
293
- duplicateSections
294
- };
295
- }
296
- function getDocumentTitles(document, compatibility = "broad") {
297
- if (compatibility === "broad")
298
- return document.titles;
299
- return document.titleOccurrences.filter((title) => compatibility === "identity" ? title.identityCompatible : title.spaced).map((title) => title.value);
300
- }
301
- function getDocumentTitle(document, compatibility = "broad") {
302
- return getDocumentTitles(document, compatibility)[0] ?? null;
303
- }
304
- function getDocumentSections(document, sectionId) {
305
- return document.sections.get(sectionId) ?? [];
306
- }
307
- function getDocumentSection(document, sectionId) {
308
- return getDocumentSections(document, sectionId)[0];
309
- }
310
- function getDocumentSectionBody(document, sectionId) {
311
- return getDocumentSection(document, sectionId)?.body ?? "";
312
- }
313
- function getDocumentSectionDefinitionByHeading(schema, heading) {
314
- return schema.sections.find((candidate) => candidate.heading === heading);
315
- }
316
- function getCanonicalSectionHeadings(schema) {
317
- return schema.sections.map((section) => section.heading);
318
- }
319
- function renderDocumentSectionHeading(schema, sectionId) {
320
- const definition = schema.sections.find((candidate) => candidate.id === sectionId);
321
- if (!definition)
322
- throw new Error(`unknown document section: ${sectionId}`);
323
- return `## ${definition.heading}`;
324
- }
325
- function renderDocumentTitle(schema, title) {
326
- return `${schema.titlePrefix} ${title}`;
327
- }
328
- function appendDocumentSectionItem(document, sectionId, item) {
329
- const matches = getDocumentSections(document, sectionId);
330
- if (matches.length !== 1)
331
- throw new DocumentSectionCardinalityError(sectionId, matches.length);
332
- const insertAt = matches[0].bodyEnd;
333
- const before = document.source.slice(0, insertAt);
334
- const separator = before.endsWith("\n\n") ? "" : before.endsWith("\n") ? "\n" : "\n\n";
335
- return `${before}${separator}${item}
336
- ${document.source.slice(insertAt)}`;
337
- }
338
- function collectLevelTwoHeadings(source) {
339
- const headings = [];
340
- const pattern = /^## ([^\r\n]+)\r?$/gm;
341
- let match;
342
- while ((match = pattern.exec(source)) !== null) {
343
- const heading = match[1].trimEnd();
344
- headings.push({
345
- heading,
346
- canonical: heading === match[1],
347
- start: match.index,
348
- end: match.index + match[0].length
349
- });
350
- }
351
- return headings;
352
- }
353
- function collectDocumentTitles(source, prefix) {
354
- const titles = [];
355
- for (const line of source.split(/\r?\n/)) {
356
- const trimmed = line.trim();
357
- if (!trimmed.startsWith(prefix))
358
- continue;
359
- const identity = line.startsWith(prefix) ? line.slice(prefix.length).match(/^\s+(\S+)\s*$/) : null;
360
- titles.push({
361
- value: trimmed.slice(prefix.length).trim(),
362
- identityCompatible: identity !== null,
363
- spaced: line.startsWith(`${prefix} `)
364
- });
365
- }
366
- return titles;
367
- }
368
- function lineBreakStart(source, headingStart) {
369
- if (headingStart > 0 && source[headingStart - 1] === "\n")
370
- return headingStart - 1;
371
- return headingStart;
372
- }
373
-
374
- // src/core/helpers.ts
375
- import { existsSync as existsSync2 } from "fs";
376
- import { readdir as readdir3, readFile, rmdir } from "fs/promises";
377
- import { basename, dirname as dirname2, join as join3, relative as relative2, sep as sep2 } from "path";
378
- import { parse } from "yaml";
379
- var RSP_AGENTS_BEGIN = "<!-- rsp:begin -->";
380
- var RSP_AGENTS_END = "<!-- rsp:end -->";
381
- function parseYamlText(text) {
382
- const parsed = parse(text);
383
- if (parsed === null || parsed === void 0)
384
- return {};
385
- if (typeof parsed !== "object" || Array.isArray(parsed))
386
- throw new Error("YAML document must be a mapping/object");
387
- return parsed;
388
- }
389
- function parseFrontmatter(content) {
390
- const m = content.match(/^---\r?\n([\s\S]*?)\r?\n(?:---|\.\.\.)\r?\n/);
391
- if (!m)
392
- return null;
393
- return parseYamlText(m[1]);
394
- }
395
- function countCheckboxes(content) {
396
- const todo = (content.match(/\[ \]/g) || []).length;
397
- const progress = (content.match(/\[\/\]/g) || []).length;
398
- const done = (content.match(/\[x\]/g) || []).length;
399
- const dropped = (content.match(/\[-\]/g) || []).length;
400
- return { todo, progress, done, dropped, total: todo + progress + done + dropped };
401
- }
402
- async function walkMarkdownFiles(dir, options = {}) {
403
- const all = await walkFiles(dir, options);
404
- return all.filter((f) => f.endsWith(".md"));
405
- }
406
- async function inspectUnsupportedRules() {
407
- const rulesDir = join3(RSP_DIR, "rules");
408
- const diagnostics = [];
409
- const rawEntries = await walkAllEntries(rulesDir, diagnostics);
410
- const entries = rawEntries.filter((entry) => entry.path !== OBSOLETE_RSP_RULES_PATH).map((entry) => ({
411
- path: normalizeLogicalPath(relative2(rulesDir, entry.path)),
412
- kind: entry.kind
413
- }));
414
- const obsoleteFallbackPresent = existsSync2(OBSOLETE_RSP_RULES_PATH);
415
- return {
416
- entries,
417
- diagnostics,
418
- directoryRemaining: existsSync2(rulesDir) && (!obsoleteFallbackPresent || entries.length > 0)
419
- };
420
- }
421
- async function walkAllEntries(dir, diagnostics) {
422
- const entries = [];
423
- try {
424
- const items = await readdir3(dir, { withFileTypes: true });
425
- for (const item of items) {
426
- const path = join3(dir, item.name);
427
- const kind = item.isDirectory() ? "directory" : "file";
428
- entries.push({ path, kind });
429
- if (kind === "directory")
430
- entries.push(...await walkAllEntries(path, diagnostics));
431
- }
432
- } catch (error) {
433
- if (error.code !== "ENOENT") {
434
- diagnostics.push({
435
- code: "walk_failed",
436
- operation: "readdir",
437
- path: dir,
438
- message: toErrorMessage(error)
439
- });
440
- }
441
- }
442
- return entries;
443
- }
444
- async function walkFiles(dir, options = {}) {
445
- const files = [];
446
- try {
447
- const items = await readdir3(dir, { withFileTypes: true });
448
- for (const item of items) {
449
- if (item.name.startsWith("."))
450
- continue;
451
- const filePath = join3(dir, item.name);
452
- if (item.isDirectory())
453
- files.push(...await walkFiles(filePath, options));
454
- else
455
- files.push(filePath);
456
- }
457
- } catch (error) {
458
- if (error.code !== "ENOENT") {
459
- options.onError?.({
460
- code: "walk_failed",
461
- operation: "readdir",
462
- path: dir,
463
- message: toErrorMessage(error)
464
- });
465
- }
466
- }
467
- return files;
468
- }
469
- function normalizeLogicalPath(pathValue) {
470
- return pathValue.split(sep2).join("/").replace(/\\/g, "/");
471
- }
472
- function generateChangeContent(name, summary = "", kind, options = {}) {
473
- const placeholder = "<\u2026>";
474
- const proposalSummary = summary || placeholder;
475
- const issueFrontmatter = renderIssueFrontmatter(options.issues ?? []);
476
- if (name === "project-setup") {
477
- return `---
478
- kind: ops
479
- ${issueFrontmatter}---
480
-
481
- ${renderDocumentTitle(CHANGE_DOCUMENT_SCHEMA, "project-setup")}
482
-
483
- ${changeSectionHeading("proposal")}
484
- - Outcome: ${proposalSummary}
485
- - Why:
486
- - ${placeholder}
487
- - Scope:
488
- - ${placeholder}
489
- - Non-goals:
490
- - ${placeholder}
491
-
492
- ${changeSectionHeading("spec")}
493
- ### ADDED
494
- - Requirement: ${placeholder}
495
- - ${placeholder}
496
-
497
- ### Acceptance
498
- #### Scenario: ${placeholder}
499
- - GIVEN ${placeholder}
500
- - WHEN ${placeholder}
501
- - THEN ${placeholder}
502
-
503
- ${changeSectionHeading("design")}
504
- - Approach:
505
- - ${placeholder}
506
- - Boundaries:
507
- - ${placeholder}
508
- - Affected areas:
509
- - .rsp/specs/design.md
510
- - CONTEXT.md
511
- - AGENTS.md
512
- - Durable outcome targets:
513
- - Current facts: ${placeholder}
514
- - Lasting rationale: ${placeholder}
515
- - Constraints:
516
- - ${placeholder}
517
-
518
- ${changeSectionHeading("tasks")}
519
- - [ ] .rsp/specs/design.md: ${placeholder}
520
- - [ ] CONTEXT.md: ${placeholder}
521
- - [ ] AGENTS.md: ${placeholder}
522
-
523
- ${changeSectionHeading("verify")}
524
- - Automated:
525
- - [ ] rsp doctor \u2014 proves: ${placeholder}
526
- - Manual or environment:
527
- - [ ] ${placeholder}
528
- - Coverage:
529
- - ${placeholder}
530
-
531
- ${changeSectionHeading("blockers")}
532
- - none
533
- `;
534
- }
535
- const frontmatterKind = kind ?? "<choose: feature | fix | refactor | docs | ops | research>";
536
- if (options.lite)
537
- return generateLiteChangeContent(name, proposalSummary, frontmatterKind, issueFrontmatter);
538
- const template = getChangeTemplateByKind(kind);
539
- return `---
540
- kind: "${frontmatterKind}"
541
- ${issueFrontmatter}---
542
-
543
- ${renderDocumentTitle(CHANGE_DOCUMENT_SCHEMA, name)}
544
-
545
- ${changeSectionHeading("proposal")}
546
- - Outcome: ${proposalSummary}
547
- - Why:
548
- - ${template.why}
549
- - Scope:
550
- - ${template.scope}
551
- - Non-goals:
552
- - ${template.nonGoals}
553
-
554
- ${changeSectionHeading("spec")}
555
- ${template.specSection}
556
-
557
- ### Acceptance
558
- ${template.acceptanceSection}
559
-
560
- ${changeSectionHeading("design")}
561
- - Approach:
562
- - ${template.approach}
563
- - Boundaries:
564
- - ${placeholder}
565
- - Affected areas:
566
- - ${template.affectedArea1}
567
- - ${template.affectedArea2}
568
- - Constraints:
569
- - ${template.constraints}
570
-
571
- ${changeSectionHeading("tasks")}
572
- - [ ] ${template.task}
573
-
574
- ${changeSectionHeading("verify")}
575
- - Automated:
576
- - [ ] ${template.automatedVerify} \u2014 proves: ${placeholder}
577
- - Manual or environment:
578
- - [ ] ${template.manualVerify}
579
- - Coverage:
580
- - ${placeholder}
581
-
582
- ${changeSectionHeading("blockers")}
583
- - none
584
- `;
585
- }
586
- function generateLiteChangeContent(name, proposalSummary, frontmatterKind, issueFrontmatter) {
587
- const placeholder = "<\u2026>";
588
- return `---
589
- kind: "${frontmatterKind}"
590
- ${issueFrontmatter}---
591
-
592
- ${renderDocumentTitle(CHANGE_DOCUMENT_SCHEMA, name)}
593
-
594
- ${changeSectionHeading("proposal")}
595
- - Outcome: ${proposalSummary}
596
- - Why:
597
- - ${placeholder}
598
- - Scope:
599
- - ${placeholder}
600
- - Non-goals:
601
- - none
602
-
603
- ${changeSectionHeading("spec")}
604
- ### MODIFIED
605
- - Requirement: ${placeholder}
606
- - ${placeholder}
607
-
608
- ### Acceptance
609
- #### Scenario: ${placeholder}
610
- - GIVEN ${placeholder}
611
- - WHEN ${placeholder}
612
- - THEN ${placeholder}
613
-
614
- ${changeSectionHeading("design")}
615
- - Approach:
616
- - ${placeholder}
617
- - Boundaries:
618
- - ${placeholder}
619
- - Affected areas:
620
- - ${placeholder}
621
- - Constraints:
622
- - ${placeholder}
623
-
624
- ${changeSectionHeading("tasks")}
625
- - [ ] ${placeholder}
626
-
627
- ${changeSectionHeading("verify")}
628
- - Automated:
629
- - [ ] ${placeholder} \u2014 proves: ${placeholder}
630
- - Manual or environment:
631
- - [ ] ${placeholder}
632
- - Coverage:
633
- - ${placeholder}
634
-
635
- ${changeSectionHeading("blockers")}
636
- - none
637
- `;
638
- }
639
- function renderIssueFrontmatter(issues) {
640
- if (issues.length === 0)
641
- return "";
642
- return `issues:
643
- ${issues.map((issue) => ` - url: ${JSON.stringify(issue.url)}
644
- relation: ${issue.relation}`).join("\n")}
645
- `;
646
- }
647
- function getChangeTemplateByKind(kind) {
648
- const placeholder = "<\u2026>";
649
- const delta = kind === "feature" || kind === "research" || kind === void 0 ? "ADDED" : "MODIFIED";
650
- return {
651
- why: placeholder,
652
- scope: placeholder,
653
- nonGoals: placeholder,
654
- specSection: `### ${delta}
655
- - Requirement: ${placeholder}
656
- - ${placeholder}`,
657
- acceptanceSection: `#### Scenario: ${placeholder}
658
- - GIVEN ${placeholder}
659
- - WHEN ${placeholder}
660
- - THEN ${placeholder}`,
661
- approach: placeholder,
662
- affectedArea1: placeholder,
663
- affectedArea2: placeholder,
664
- constraints: placeholder,
665
- task: placeholder,
666
- automatedVerify: placeholder,
667
- manualVerify: placeholder
668
- };
669
- }
670
- function changeSectionHeading(sectionId) {
671
- return renderDocumentSectionHeading(CHANGE_DOCUMENT_SCHEMA, sectionId);
672
- }
673
- function renderRspAgentsBlock() {
674
- return `${RSP_AGENTS_BEGIN}
675
- ## RSP Entry
676
-
677
- RSP tracks current work, stable specs, and archives under \`.rsp/\`.
678
-
679
- Read in order:
680
- 1. Nearest \`AGENTS.md\` for project or module instructions.
681
- 2. Root \`CONTEXT-MAP.md\` if present, then the relevant nearest \`CONTEXT.md\`.
682
- 3. Use the project \`rsp\` Skill at \`.agents/skills/rsp/SKILL.md\`; hosts may load it through Skill discovery or read it directly. Only when it is absent or cannot be used, read \`.rsp/rsp-rules.md\` as the fallback protocol.
683
- 4. \`.rsp/focus.d/\`; for grouped work read the sibling Group Brief, then the explicitly selected focused Change.
684
- 5. Only the relevant Specs and Decision Records under the configured authoritative path.
685
-
686
- If \`.rsp/focus.d/\` is empty and the user has not provided a concrete task, ask what to work on or suggest \`npx -y @oevery/rsp create <name>\` for tracked work.
687
- Do not treat \`.rsp/specs/\` or \`.rsp/changes/\` as replacements for nearest \`AGENTS.md\` or \`CONTEXT.md\`.
688
- ${RSP_AGENTS_END}`;
689
- }
690
- function upsertRspAgentsBlock(content) {
691
- const block = renderRspAgentsBlock();
692
- const managedRe = /<!-- rsp:begin -->[\s\S]*?<!-- rsp:end -->/;
693
- if (managedRe.test(content)) {
694
- const next2 = content.replace(managedRe, block);
695
- return { content: next2, changed: next2 !== content };
696
- }
697
- const trimmed = content.trimStart();
698
- const next = trimmed ? `${block}
699
-
700
- ${trimmed}` : `${block}
701
- `;
702
- return { content: next, changed: true };
703
- }
704
- function hasRspAgentsBlock(content) {
705
- return content.includes(RSP_AGENTS_BEGIN) && content.includes(RSP_AGENTS_END);
706
- }
707
- var SPEC_NAME_RE = /^[a-z0-9-]+(?:\/[a-z0-9-]+)*$/;
708
- function isValidSpecName(name) {
709
- return SPEC_NAME_RE.test(name);
710
- }
711
- function guardRspInitialized() {
712
- const designPath = join3(RSP_DIR, "specs", "design.md");
713
- const rules = inspectManagedFile(RSP_RULES_PATH, "fallback protocol", { allowMissing: true });
714
- const design = inspectManagedFile(designPath, "design Spec", { allowMissing: true });
715
- if (rules.issue || design.issue || !rules.exists || !design.exists) {
716
- const initialized = existsSync2(RSP_DIR);
717
- console.error(` ${pc.red("Error:")} ${initialized ? "RSP project requires an update" : "RSP is not initialized in this project"}`);
718
- console.error(` ${pc.dim(initialized ? "Run: rsp update" : "Run: rsp init")}`);
719
- process.exit(1);
720
- }
721
- }
722
- function getOpenCheckboxes(sectionText) {
723
- return sectionText.split("\n").map((line) => line.trim()).filter((line) => /^- \[[ /]\]/.test(line));
724
- }
725
- async function cleanupEmptyParentDirs(path, stopDir) {
726
- let parent = dirname2(path);
727
- while (parent !== stopDir) {
728
- try {
729
- const remaining = await readdir3(parent);
730
- if (remaining.length === 0)
731
- await rmdir(parent);
732
- else
733
- break;
734
- } catch {
735
- break;
736
- }
737
- parent = dirname2(parent);
738
- }
739
- }
740
- async function detectProjectName() {
741
- try {
742
- if (existsSync2("package.json")) {
743
- const raw = await readFile("package.json", "utf-8");
744
- const pkg = JSON.parse(raw);
745
- if (pkg.name && pkg.name !== "")
746
- return pkg.name;
747
- }
748
- } catch {
749
- }
750
- return basename(process.cwd());
751
- }
752
- function generateDesignContent(projectName) {
753
- const placeholder = "<\u2026>";
754
- return `# Project Design: ${projectName}
755
-
756
- ## Purpose
757
- - ${placeholder}
758
-
759
- ## Stable Facts
760
- - ${placeholder}
761
-
762
- ## Boundaries
763
- - In scope:
764
- - ${placeholder}
765
- - Out of scope:
766
- - ${placeholder}
767
-
768
- ## Structure
769
- - ${placeholder}
770
-
771
- ## Constraints
772
- - ${placeholder}
773
- `;
774
- }
775
- function generateSpecContent(name) {
776
- const title = toTitleCase(name);
777
- const placeholder = "<\u2026>";
778
- return `# ${title}
779
-
780
- ## Purpose
781
- - ${placeholder}
782
-
783
- ## Stable Facts
784
- - ${placeholder}
785
-
786
- ## Boundaries
787
- - In scope:
788
- - ${placeholder}
789
- - Out of scope:
790
- - ${placeholder}
791
-
792
- ## Constraints
793
- - ${placeholder}
794
- `;
795
- }
796
- function toTitleCase(value) {
797
- return value.split(/[-/]/g).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
798
- }
799
- function extractBlockerLines(content) {
800
- const document = parseRspDocument(content, CHANGE_DOCUMENT_SCHEMA);
801
- return normalizeBlockerBody(getDocumentSectionBody(document, "blockers"));
802
- }
803
- function hasMeaningfulBlockerBody(body) {
804
- return hasMeaningfulBlockerLines(normalizeBlockerBody(body));
805
- }
806
- function normalizeBlockerBody(body) {
807
- return body.replace(/<!--[\s\S]*?-->/g, "").split("\n").map((line) => line.trim()).filter(Boolean);
808
- }
809
- function hasMeaningfulBlockers(content) {
810
- return hasMeaningfulBlockerLines(extractBlockerLines(content));
811
- }
812
- function hasMeaningfulBlockerLines(lines) {
813
- if (lines.length === 0)
814
- return false;
815
- return lines.some((line) => !/^[-*]\s*(?:none)?$/i.test(line) && !/^none$/i.test(line));
816
- }
817
- function detectDeltaSections(content) {
818
- const document = parseRspDocument(content, CHANGE_DOCUMENT_SCHEMA);
819
- const body = getDocumentSectionBody(document, "spec");
820
- return {
821
- added: /^###\s*ADDED/im.test(body),
822
- modified: /^###\s*MODIFIED/im.test(body),
823
- removed: /^###\s*REMOVED/im.test(body)
824
- };
825
- }
826
- function collectArchiveReadiness(content, options = {}) {
827
- const warnings = [];
828
- const document = parseRspDocument(content, CHANGE_DOCUMENT_SCHEMA);
829
- const tasksSection = getDocumentSectionBody(document, "tasks");
830
- const verifySection = getDocumentSectionBody(document, "verify");
831
- const taskTodos = getOpenCheckboxes(tasksSection);
832
- if (taskTodos.length > 0)
833
- warnings.push(`${taskTodos.length} task item(s) still incomplete`);
834
- const verifyTodos = getOpenCheckboxes(verifySection);
835
- if (verifyTodos.length > 0)
836
- warnings.push(`${verifyTodos.length} Verify checklist item(s) are still incomplete`);
837
- const activeBlockers = options.activeBlockers ?? hasMeaningfulBlockers(content);
838
- if (activeBlockers)
839
- warnings.push("active blockers are present in the change file");
840
- const scenarios = parseScenarios(content);
841
- const missingScenarios = scenarios.length === 0;
842
- if (missingScenarios)
843
- warnings.push("no Scenario blocks found (some changes do not need them)");
844
- const archiveReady = activeBlockers ? "no" : warnings.length === 0 ? "yes" : "judgment";
845
- return {
846
- taskTodos,
847
- verifyTodos,
848
- activeBlockers,
849
- scenarioCount: scenarios.length,
850
- missingScenarios,
851
- deterministic: warnings.length === 0 ? "pass" : "warnings",
852
- semantic: "needs-review",
853
- archiveReady,
854
- warnings
855
- };
856
- }
857
- function buildDurableReviewGuidance(factCandidateTargets, decisionRecordsPath) {
858
- return {
859
- required: true,
860
- factDecisions: [
861
- "No current-fact update needed",
862
- "Update existing spec or scoped instruction",
863
- "Create a new durable spec"
864
- ],
865
- rationaleDecisions: [
866
- "No Decision Record needed",
867
- "Create or update a Decision Record"
868
- ],
869
- factCandidateTargets,
870
- decisionRecordsPath,
871
- note: "Semantic review decides current-fact and lasting-rationale updates independently. The CLI never infers scoped instruction or Decision Record filenames and never promotes Change content automatically."
872
- };
873
- }
874
- function getDurableReviewCandidateTargets() {
875
- return [".rsp/specs/design.md"];
876
- }
877
- function collectArchiveChecklist(content, options = {}) {
878
- return collectArchiveReadiness(content, options).warnings;
879
- }
880
- function parseScenarios(content) {
881
- const scenarios = [];
882
- const regex = /^#{3,4}\s*Scenario:\s*(.+)$/gim;
883
- let match;
884
- while ((match = regex.exec(content)) !== null) {
885
- const heading = match[1].trim();
886
- const startPos = match.index + match[0].length;
887
- const restContent = content.slice(startPos);
888
- const nextSection = restContent.match(/^#{3,4}\s/m);
889
- const block = nextSection ? restContent.slice(0, nextSection.index) : restContent;
890
- const steps = block.split("\n").map((s) => s.trim()).filter((s) => s.startsWith("-") && /GIVEN|WHEN|THEN|AND|BUT/i.test(s)).map((s) => s.replace(/^[-*]\s*/, ""));
891
- if (steps.length > 0)
892
- scenarios.push({ heading, steps });
893
- }
894
- return scenarios;
895
- }
896
-
897
- // src/core/config.ts
898
- var __dirname = dirname3(fileURLToPath(import.meta.url));
899
- var PKG_ROOT = join4(__dirname, "..");
900
- var RSP_DIR = ".rsp";
901
- var RSP_RULES_PATH = join4(RSP_DIR, "rsp-rules.md");
902
- var OBSOLETE_RSP_RULES_PATH = join4(RSP_DIR, "rules", "rsp-rules.md");
903
- var CHANGES_DIR = join4(RSP_DIR, "changes");
904
- var FOCUS_DIR = join4(RSP_DIR, "focus.d");
905
- var ARCHIVES_DIR = join4(RSP_DIR, "archives");
906
- var LOCK_PATH = join4(RSP_DIR, ".lock");
907
- var CONFIG_PATH = join4(RSP_DIR, "config.yaml");
908
- var VALID_KINDS = ["feature", "fix", "refactor", "docs", "ops", "research"];
909
- var DEFAULT_REQUIRED_SECTIONS = getCanonicalSectionHeadings(CHANGE_DOCUMENT_SCHEMA);
910
- var DEFAULT_MANAGE_POLICY = { activation: "explicit", closeout: "local" };
911
- var FAILED_CLOSED_MANAGE_POLICY = { activation: "explicit", closeout: "manual" };
912
- var MANAGE_ACTIVATIONS = ["explicit", "auto"];
913
- var MANAGE_CLOSEOUTS = ["manual", "lifecycle", "local"];
914
- var _configCache = null;
915
- async function loadRspConfig() {
916
- const inspection = await inspectRspConfig();
917
- if (inspection.issues.length > 0)
918
- throw new Error(inspection.issues.join("; "));
919
- return inspection.config;
920
- }
921
- async function inspectRspConfig() {
922
- const cwd = process.cwd();
923
- if (_configCache?.cwd === cwd)
924
- return _configCache.inspection;
925
- const configFile = inspectManagedFile(CONFIG_PATH, "config file", { allowMissing: true });
926
- if (configFile.issue)
927
- throw configFile.issue;
928
- if (!configFile.exists) {
929
- const inspection2 = { config: {}, issues: [] };
930
- _configCache = { cwd, inspection: inspection2 };
931
- return inspection2;
932
- }
933
- const raw = await readFile2(CONFIG_PATH, "utf-8");
934
- const parsed = parseYamlText(raw);
935
- const issues = validateRspConfig(parsed);
936
- const decisions = parsed.decisions && typeof parsed.decisions === "object" && !Array.isArray(parsed.decisions) ? parsed.decisions : void 0;
937
- const decisionPath = decisions?.path;
938
- const manageValue = parsed.manage && typeof parsed.manage === "object" && !Array.isArray(parsed.manage) ? parsed.manage : void 0;
939
- const languageValue = parsed.language && typeof parsed.language === "object" && !Array.isArray(parsed.language) ? parsed.language : void 0;
940
- const activation = MANAGE_ACTIVATIONS.includes(manageValue?.activation) ? manageValue?.activation : void 0;
941
- const closeout = MANAGE_CLOSEOUTS.includes(manageValue?.closeout) ? manageValue?.closeout : void 0;
942
- const kinds = Array.isArray(parsed.kinds) && parsed.kinds.every((value) => typeof value === "string" && value.trim() !== "") ? parsed.kinds.map((value) => value.trim()) : void 0;
943
- const decisionRecordsIssue = getDecisionRecordsConfigIssue(parsed);
944
- const inspection = {
945
- config: {
946
- kinds,
947
- decisions: decisionRecordsIssue === null && validateDecisionRecordsPath(decisionPath) === null ? { path: normalizeDecisionRecordsPath(String(decisionPath)) } : void 0,
948
- manage: manageValue ? { activation, closeout } : void 0,
949
- language: parseLanguageConfig(languageValue)
950
- },
951
- issues
952
- };
953
- _configCache = { cwd, inspection };
954
- return inspection;
955
- }
956
- function validateRspConfig(parsed) {
957
- const issues = [];
958
- const supported = /* @__PURE__ */ new Set(["kinds", "decisions", "manage", "language"]);
959
- for (const key of Object.keys(parsed)) {
960
- if (key === "required_sections") {
961
- issues.push('config.yaml field "required_sections" is no longer supported');
962
- } else if (!supported.has(key)) {
963
- issues.push(`config.yaml field "${key}" is not supported`);
964
- }
965
- }
966
- if ("kinds" in parsed) {
967
- if (!Array.isArray(parsed.kinds)) {
968
- issues.push('config.yaml field "kinds" must be a YAML list');
969
- } else if (!parsed.kinds.every((value) => typeof value === "string" && value.trim() !== "")) {
970
- issues.push('config.yaml field "kinds" must contain only non-empty strings');
971
- } else {
972
- const kinds = parsed.kinds.map((value) => value.trim());
973
- const duplicates = [...new Set(kinds.filter((value, index) => kinds.indexOf(value) !== index))];
974
- if (duplicates.length > 0)
975
- issues.push(`config.yaml field "kinds" contains duplicate entries: ${duplicates.join(", ")}`);
976
- }
977
- }
978
- const decisionRecordsIssue = getDecisionRecordsConfigIssue(parsed);
979
- if (decisionRecordsIssue)
980
- issues.push(decisionRecordsIssue);
981
- if ("manage" in parsed) {
982
- if (!parsed.manage || typeof parsed.manage !== "object" || Array.isArray(parsed.manage)) {
983
- issues.push('config.yaml field "manage" must be a YAML mapping');
984
- } else {
985
- const manage = parsed.manage;
986
- const supportedManageFields = /* @__PURE__ */ new Set(["activation", "closeout"]);
987
- for (const key of Object.keys(manage)) {
988
- if (!supportedManageFields.has(key))
989
- issues.push(`config.yaml field "manage.${key}" is not supported`);
990
- }
991
- if ("activation" in manage && !MANAGE_ACTIVATIONS.includes(manage.activation))
992
- issues.push('config.yaml field "manage.activation" must be one of: explicit, auto');
993
- if ("closeout" in manage && !MANAGE_CLOSEOUTS.includes(manage.closeout))
994
- issues.push('config.yaml field "manage.closeout" must be one of: manual, lifecycle, local');
995
- }
996
- }
997
- if ("language" in parsed) {
998
- if (!parsed.language || typeof parsed.language !== "object" || Array.isArray(parsed.language)) {
999
- issues.push('config.yaml field "language" must be a YAML mapping');
1000
- } else {
1001
- const language = parsed.language;
1002
- const supportedLanguageFields = /* @__PURE__ */ new Set(["default", "artifacts", "commit"]);
1003
- for (const key of Object.keys(language)) {
1004
- if (!supportedLanguageFields.has(key))
1005
- issues.push(`config.yaml field "language.${key}" is not supported`);
1006
- }
1007
- if (!("default" in language))
1008
- issues.push('config.yaml field "language.default" is required when language is configured');
1009
- for (const key of supportedLanguageFields) {
1010
- if (key in language && canonicalizeLanguageTag(language[key]) === null)
1011
- issues.push(`config.yaml field "language.${key}" must be a non-empty valid BCP 47 language tag`);
1012
- }
1013
- }
1014
- }
1015
- return issues;
1016
- }
1017
- function clearConfigCache() {
1018
- _configCache = null;
1019
- }
1020
- function resolveKinds(config) {
1021
- return config.kinds && config.kinds.length > 0 ? config.kinds : [...VALID_KINDS];
1022
- }
1023
- function resolveRequiredSections(_config) {
1024
- return [...DEFAULT_REQUIRED_SECTIONS];
1025
- }
1026
- function resolveManagePolicy(config, options = {}) {
1027
- if (options.configValid === false)
1028
- return { ...FAILED_CLOSED_MANAGE_POLICY };
1029
- return {
1030
- activation: config.manage?.activation ?? DEFAULT_MANAGE_POLICY.activation,
1031
- closeout: config.manage?.closeout ?? DEFAULT_MANAGE_POLICY.closeout
1032
- };
1033
- }
1034
- function resolveLanguagePolicy(config, options = {}) {
1035
- if (options.configValid === false || !config.language)
1036
- return { artifacts: null, commit: null };
1037
- return {
1038
- artifacts: config.language.artifacts ?? config.language.default,
1039
- commit: config.language.commit ?? config.language.default
1040
- };
1041
- }
1042
- function parseLanguageConfig(value) {
1043
- if (!value)
1044
- return void 0;
1045
- const defaultLanguage = canonicalizeLanguageTag(value.default);
1046
- if (defaultLanguage === null)
1047
- return void 0;
1048
- const artifacts = canonicalizeLanguageTag(value.artifacts);
1049
- const commit = canonicalizeLanguageTag(value.commit);
1050
- return {
1051
- default: defaultLanguage,
1052
- artifacts: artifacts ?? void 0,
1053
- commit: commit ?? void 0
1054
- };
1055
- }
1056
- function canonicalizeLanguageTag(value) {
1057
- if (typeof value !== "string" || value.trim() === "")
1058
- return null;
1059
- try {
1060
- return Intl.getCanonicalLocales(value.trim())[0] ?? null;
1061
- } catch {
1062
- return null;
1063
- }
1064
- }
1065
- async function getVersion() {
1066
- const { version } = JSON.parse(await readFile2(join4(PKG_ROOT, "package.json"), "utf-8"));
1067
- return version;
1068
- }
1069
-
1070
- // src/core/output.ts
1071
- function toErrorMessage(error) {
1072
- return error instanceof Error ? error.message : String(error);
1073
- }
1074
- function emitJson(data, options = {}) {
1075
- const indentation = options.compact ? void 0 : 2;
1076
- process.stdout.write(`${JSON.stringify(data, null, indentation)}
1077
- `);
1078
- }
1079
- function recordRuntimeDiagnostic(runtime, diagnostic, verbose = false) {
1080
- runtime.push(diagnostic);
1081
- if (verbose) {
1082
- console.error(` ${pc.dim(`[verbose] ${diagnostic.operation} ${diagnostic.path}: ${diagnostic.message}`)}`);
1083
- }
1084
- }
1085
-
1086
- export {
1087
- ManagedPathError,
1088
- inspectManagedDirectory,
1089
- requireManagedDirectory,
1090
- inspectManagedFile,
1091
- requireManagedFile,
1092
- ensureManagedFile,
1093
- writeManagedFile,
1094
- resolveManagedDirectoryChain,
1095
- inspectManagedFileTree,
1096
- DEFAULT_DECISION_RECORDS_PATH,
1097
- resolveDecisionRecordsPath,
1098
- ensureDecisionRecordsDirectory,
1099
- validateDecisionRecordsFilesystemPath,
1100
- CHANGE_DOCUMENT_SCHEMA,
1101
- GROUP_BRIEF_DOCUMENT_SCHEMA,
1102
- DocumentSectionCardinalityError,
1103
- parseRspDocument,
1104
- getDocumentTitles,
1105
- getDocumentTitle,
1106
- getDocumentSections,
1107
- getDocumentSectionBody,
1108
- getDocumentSectionDefinitionByHeading,
1109
- getCanonicalSectionHeadings,
1110
- renderDocumentSectionHeading,
1111
- renderDocumentTitle,
1112
- appendDocumentSectionItem,
1113
- toErrorMessage,
1114
- emitJson,
1115
- recordRuntimeDiagnostic,
1116
- parseFrontmatter,
1117
- countCheckboxes,
1118
- walkMarkdownFiles,
1119
- inspectUnsupportedRules,
1120
- normalizeLogicalPath,
1121
- generateChangeContent,
1122
- upsertRspAgentsBlock,
1123
- hasRspAgentsBlock,
1124
- isValidSpecName,
1125
- guardRspInitialized,
1126
- cleanupEmptyParentDirs,
1127
- detectProjectName,
1128
- generateDesignContent,
1129
- generateSpecContent,
1130
- extractBlockerLines,
1131
- hasMeaningfulBlockerBody,
1132
- hasMeaningfulBlockers,
1133
- detectDeltaSections,
1134
- collectArchiveReadiness,
1135
- buildDurableReviewGuidance,
1136
- getDurableReviewCandidateTargets,
1137
- collectArchiveChecklist,
1138
- parseScenarios,
1139
- pc,
1140
- PKG_ROOT,
1141
- RSP_DIR,
1142
- RSP_RULES_PATH,
1143
- OBSOLETE_RSP_RULES_PATH,
1144
- CHANGES_DIR,
1145
- FOCUS_DIR,
1146
- ARCHIVES_DIR,
1147
- LOCK_PATH,
1148
- CONFIG_PATH,
1149
- VALID_KINDS,
1150
- loadRspConfig,
1151
- inspectRspConfig,
1152
- clearConfigCache,
1153
- resolveKinds,
1154
- resolveRequiredSections,
1155
- resolveManagePolicy,
1156
- resolveLanguagePolicy,
1157
- getVersion
1158
- };