@oevery/rsp 4.0.0 → 4.2.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 (141) hide show
  1. package/dist/chunk-AUZYUJ46.mjs +418 -0
  2. package/dist/chunk-DNRGBQS7.mjs +108 -0
  3. package/dist/chunk-LGDCIQYL.mjs +725 -0
  4. package/dist/chunk-P3KO3GZW.mjs +1023 -0
  5. package/dist/{chunk-HFSTLCCK.mjs → chunk-VDRXFKBP.mjs} +133 -1800
  6. package/dist/cli.mjs +286 -294
  7. package/dist/client-U2PN3VS4.mjs +108 -0
  8. package/dist/{entry-RIJQ2X46.mjs → entry-NDASTAI5.mjs} +13 -9
  9. package/dist/viewer/NOTICE.txt +299 -0
  10. package/dist/viewer/app.css +2 -0
  11. package/dist/viewer/app.js +670 -0
  12. package/dist/viewer/chunks/abnfDiagram-S7VZAUFO-K5EOZOEF.js +1 -0
  13. package/dist/viewer/chunks/architecture-WOLXFQ4H-UAT5D5BF.js +1 -0
  14. package/dist/viewer/chunks/architectureDiagram-YGSQEFNW-UNPSYM2L.js +36 -0
  15. package/dist/viewer/chunks/blockDiagram-APKKGZFW-2F7TTPAV.js +140 -0
  16. package/dist/viewer/chunks/bpmnIcons-APHXDYXF-ZWPFFN3O.js +1 -0
  17. package/dist/viewer/chunks/c4Diagram-KJ2TOQU5-KNLN6CME.js +38 -0
  18. package/dist/viewer/chunks/chunk-274PZN6B.js +1 -0
  19. package/dist/viewer/chunks/chunk-2VGDQLQD.js +1 -0
  20. package/dist/viewer/chunks/chunk-33XESOCS.js +1 -0
  21. package/dist/viewer/chunks/chunk-3ZSL2TGQ.js +1 -0
  22. package/dist/viewer/chunks/chunk-432I4U3B.js +1 -0
  23. package/dist/viewer/chunks/chunk-4PJNGS3V.js +1 -0
  24. package/dist/viewer/chunks/chunk-54NLSLTF.js +9 -0
  25. package/dist/viewer/chunks/chunk-6QRHYJ5Q.js +1 -0
  26. package/dist/viewer/chunks/chunk-6RCFKBVR.js +1 -0
  27. package/dist/viewer/chunks/chunk-6Y2HP7DC.js +213 -0
  28. package/dist/viewer/chunks/chunk-7MAT7NHP.js +1 -0
  29. package/dist/viewer/chunks/chunk-AKHBD6LT.js +2 -0
  30. package/dist/viewer/chunks/chunk-CZIM56BV.js +1 -0
  31. package/dist/viewer/chunks/chunk-DN4Y7LXJ.js +1 -0
  32. package/dist/viewer/chunks/chunk-E2UQN267.js +1 -0
  33. package/dist/viewer/chunks/chunk-GBARLDDT.js +1 -0
  34. package/dist/viewer/chunks/chunk-H4YEV7VR.js +1 -0
  35. package/dist/viewer/chunks/chunk-HUVVDQHZ.js +1 -0
  36. package/dist/viewer/chunks/chunk-IAKC32WV.js +72 -0
  37. package/dist/viewer/chunks/chunk-JTW4SY4L.js +1 -0
  38. package/dist/viewer/chunks/chunk-JZV675LF.js +1 -0
  39. package/dist/viewer/chunks/chunk-KDUDYTWO.js +1 -0
  40. package/dist/viewer/chunks/chunk-KHX3A5RZ.js +331 -0
  41. package/dist/viewer/chunks/chunk-KPXJJSUR.js +15 -0
  42. package/dist/viewer/chunks/chunk-LEGNM6PH.js +1 -0
  43. package/dist/viewer/chunks/chunk-LYY4ZY72.js +1 -0
  44. package/dist/viewer/chunks/chunk-MKDEK2LU.js +1 -0
  45. package/dist/viewer/chunks/chunk-MYJRFART.js +129 -0
  46. package/dist/viewer/chunks/chunk-NUXZBURC.js +1 -0
  47. package/dist/viewer/chunks/chunk-NXHT5I2L.js +1 -0
  48. package/dist/viewer/chunks/chunk-OF62XRVM.js +62 -0
  49. package/dist/viewer/chunks/chunk-ONCOEVBS.js +1 -0
  50. package/dist/viewer/chunks/chunk-P44SH3OO.js +1 -0
  51. package/dist/viewer/chunks/chunk-PDXKDBIX.js +1 -0
  52. package/dist/viewer/chunks/chunk-PHKIAKHS.js +1 -0
  53. package/dist/viewer/chunks/chunk-PJDMAGPE.js +2 -0
  54. package/dist/viewer/chunks/chunk-RC2NPHSX.js +88 -0
  55. package/dist/viewer/chunks/chunk-T6DM5RSK.js +10 -0
  56. package/dist/viewer/chunks/chunk-U5STX66M.js +2 -0
  57. package/dist/viewer/chunks/chunk-UIAJQPOF.js +1 -0
  58. package/dist/viewer/chunks/chunk-UX2S3LP3.js +1 -0
  59. package/dist/viewer/chunks/chunk-VEMTQQ7Y.js +2 -0
  60. package/dist/viewer/chunks/chunk-WKCPBR3C.js +1 -0
  61. package/dist/viewer/chunks/chunk-WMQ4Y2MS.js +32 -0
  62. package/dist/viewer/chunks/chunk-YPWIQAHX.js +3 -0
  63. package/dist/viewer/chunks/chunk-Z4ZDIMO2.js +136 -0
  64. package/dist/viewer/chunks/chunk-ZATQCNXH.js +1 -0
  65. package/dist/viewer/chunks/chunk-ZE5RDJX6.js +1 -0
  66. package/dist/viewer/chunks/chunk-ZO62R4L2.js +1 -0
  67. package/dist/viewer/chunks/classDiagram-v2-L25IHNXF-5F2M2CZ4.js +217 -0
  68. package/dist/viewer/chunks/cose-bilkent-JH36ORCC-S56HJ5WN.js +1 -0
  69. package/dist/viewer/chunks/cynefin-EF2NZ3EQ-6WY2NCLE.js +1 -0
  70. package/dist/viewer/chunks/cynefinDiagram-L36ESFBY-DE6PZNQA.js +62 -0
  71. package/dist/viewer/chunks/dagre-SPEB5RXH-QEOTKFV2.js +4 -0
  72. package/dist/viewer/chunks/diagram-3B7GL73J-RZPIU7FY.js +200 -0
  73. package/dist/viewer/chunks/diagram-DNEMPZML-YD26TJKC.js +24 -0
  74. package/dist/viewer/chunks/diagram-L3MUGCDN-TJUEX4QI.js +41 -0
  75. package/dist/viewer/chunks/diagram-LA4NIPWU-3PUQS6UW.js +24 -0
  76. package/dist/viewer/chunks/diagram-MHCOC4E2-YBTBD4PG.js +3 -0
  77. package/dist/viewer/chunks/diagram-XPFHEONP-VF2TQBNR.js +30 -0
  78. package/dist/viewer/chunks/ebnfDiagram-UZSYDL2S-QE7DIEME.js +1 -0
  79. package/dist/viewer/chunks/elk-XV6XMXQE-X5OZRIL3.js +27 -0
  80. package/dist/viewer/chunks/erDiagram-EYBAPDEH-5B2S7VB4.js +99 -0
  81. package/dist/viewer/chunks/eventmodeling-K75KTNOO-CHCQYZBI.js +1 -0
  82. package/dist/viewer/chunks/flowDiagram-MALGAFMO-XJFC5NTN.js +1 -0
  83. package/dist/viewer/chunks/ganttDiagram-23REXITY-TUUZ3QYO.js +292 -0
  84. package/dist/viewer/chunks/gitGraph-VSP46ZUC-VFZPJQ6A.js +1 -0
  85. package/dist/viewer/chunks/gitGraphDiagram-STJD7YCW-4CVV5LSH.js +106 -0
  86. package/dist/viewer/chunks/info-OHQRW6UA-P4744SJ6.js +1 -0
  87. package/dist/viewer/chunks/infoDiagram-KBXO4MBG-Q2WYYNNN.js +2 -0
  88. package/dist/viewer/chunks/ishikawaDiagram-M35NCXHK-PLQPTX2U.js +70 -0
  89. package/dist/viewer/chunks/journeyDiagram-OPLOXDXO-FB73RE6F.js +139 -0
  90. package/dist/viewer/chunks/kanban-definition-BZKZHSQY-6FU4TXYJ.js +89 -0
  91. package/dist/viewer/chunks/katex-IN4S3RHS.js +257 -0
  92. package/dist/viewer/chunks/mindmap-definition-I2QAS2VK-ZQ36ABVW.js +98 -0
  93. package/dist/viewer/chunks/packet-JDAUHWVQ-NYXIQQVA.js +1 -0
  94. package/dist/viewer/chunks/pegDiagram-FD4XPPHP-5XXVT6XN.js +1 -0
  95. package/dist/viewer/chunks/pie-XZMESJXO-32CUE6KZ.js +1 -0
  96. package/dist/viewer/chunks/pieDiagram-J4AKIIQI-WRTBDFM5.js +39 -0
  97. package/dist/viewer/chunks/quadrantDiagram-XTYBLID2-YPBDQIAK.js +7 -0
  98. package/dist/viewer/chunks/radar-ABXABTNO-272FILRO.js +1 -0
  99. package/dist/viewer/chunks/railroad-I3PHUGI6-6EGAH4XA.js +1 -0
  100. package/dist/viewer/chunks/railroad-abnf-LNEFI6M7-G6E7ATER.js +1 -0
  101. package/dist/viewer/chunks/railroad-ebnf-DWYD2UWJ-MR4P6BDR.js +1 -0
  102. package/dist/viewer/chunks/railroad-peg-JR7BVNR7-WXLQUGUO.js +1 -0
  103. package/dist/viewer/chunks/railroadDiagram-MRQ6NCDL-XHZQ2BLY.js +1 -0
  104. package/dist/viewer/chunks/requirementDiagram-Y7LWUY3S-W5FHRRML.js +84 -0
  105. package/dist/viewer/chunks/sankeyDiagram-WGJFYUXA-OJJIRVTI.js +40 -0
  106. package/dist/viewer/chunks/sequenceDiagram-5NDRBG5M-D6T4DRNG.js +169 -0
  107. package/dist/viewer/chunks/sizeCapture-INFHLROL-WL3FTUJX.js +1 -0
  108. package/dist/viewer/chunks/stateDiagram-v2-FJ4FJNEA-LAXL7KGG.js +291 -0
  109. package/dist/viewer/chunks/swimlanes-UMQ2AFYF-XIJBD726.js +1 -0
  110. package/dist/viewer/chunks/swimlanesDiagram-OMPLPG4O-7O7MHFW7.js +8 -0
  111. package/dist/viewer/chunks/timeline-definition-SEUHKTQE-BAZQA47I.js +120 -0
  112. package/dist/viewer/chunks/treeView-D4JQ5SDB-6YMDAAPA.js +1 -0
  113. package/dist/viewer/chunks/treemap-SAJKECNS-2X573PPS.js +1 -0
  114. package/dist/viewer/chunks/usecaseDiagram-AFA6YXN4-4YCEMWJB.js +370 -0
  115. package/dist/viewer/chunks/vennDiagram-NNMJR3RB-GEMROLAE.js +34 -0
  116. package/dist/viewer/chunks/wardley-7MLQ67FV-DGTGHYYC.js +1 -0
  117. package/dist/viewer/chunks/wardleyDiagram-S7YFUSEH-K4HVD3SS.js +78 -0
  118. package/dist/viewer/chunks/xychartDiagram-GGNKY7IX-HVLWCUEO.js +7 -0
  119. package/dist/viewer/mermaid.js +12 -0
  120. package/dist/viewer-build.json +1 -0
  121. package/dist/viewer-service.mjs +607 -0
  122. package/package.json +27 -8
  123. package/skills/rsp/SKILL.md +13 -10
  124. package/skills/rsp/references/architecture.md +39 -0
  125. package/skills/rsp/references/closeout.md +4 -4
  126. package/skills/rsp/references/durable-review.md +6 -9
  127. package/skills/rsp/references/focus-continuation.md +6 -2
  128. package/skills/rsp/references/setup-repair.md +11 -6
  129. package/skills/rsp/references/work-model.md +14 -4
  130. package/skills/rsp-commit/SKILL.md +2 -2
  131. package/skills/rsp-doc/SKILL.md +13 -10
  132. package/skills/rsp-doc/references/skill-authoring.md +2 -2
  133. package/skills/rsp-implement/SKILL.md +16 -12
  134. package/skills/rsp-review/SKILL.md +12 -12
  135. package/skills/rsp-review/references/code-review.md +11 -5
  136. package/skills/rsp-review/references/document-review.md +8 -10
  137. package/skills/rsp-shape/SKILL.md +3 -1
  138. package/skills/rsp-shape/references/planning.md +4 -4
  139. package/skills/rsp-verify/SKILL.md +9 -14
  140. package/skills/rsp-doc/references/artifact-lenses.md +0 -43
  141. package/skills/rsp-review/references/structural-review.md +0 -41
@@ -0,0 +1,1023 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/core/config.ts
4
+ import { readFile } from "fs/promises";
5
+ import { dirname as dirname2, join as join3, resolve as resolve2 } from "path";
6
+ import { fileURLToPath } from "url";
7
+ import pc from "picocolors";
8
+ import { stringify } from "yaml";
9
+
10
+ // src/core/markdown.mjs
11
+ import { fromHtml } from "hast-util-from-html";
12
+ import { fromMarkdown } from "mdast-util-from-markdown";
13
+ import { frontmatterFromMarkdown } from "mdast-util-frontmatter";
14
+ import { gfmFromMarkdown } from "mdast-util-gfm";
15
+ import { frontmatter } from "micromark-extension-frontmatter";
16
+ import { gfm } from "micromark-extension-gfm";
17
+ var htmlTokenAnalyses = /* @__PURE__ */ new WeakMap();
18
+ var matters = ["yaml", { type: "yaml", fence: { open: "---", close: "..." } }];
19
+ var extensions = [gfm(), frontmatter(matters)];
20
+ var mdastExtensions = [gfmFromMarkdown(), frontmatterFromMarkdown(matters)];
21
+ function parseMarkdown(input, options = {}) {
22
+ if (typeof input !== "string")
23
+ return input;
24
+ const syntax = options.frontmatter === false ? { extensions: [gfm()], mdastExtensions: [gfmFromMarkdown()] } : { extensions, mdastExtensions };
25
+ return { source: input, root: fromMarkdown(input, syntax), start: 0, end: input.length };
26
+ }
27
+ function walkMarkdown(node, visit) {
28
+ if (visit(node) === false)
29
+ return;
30
+ if ("children" in node) {
31
+ for (const child of node.children)
32
+ walkMarkdown(child, visit);
33
+ }
34
+ }
35
+ function markdownNodeSource(input, node) {
36
+ const { source } = parseMarkdown(input);
37
+ return source.slice(node.position?.start.offset ?? 0, node.position?.end.offset ?? 0);
38
+ }
39
+ function isMarkdownComment(node) {
40
+ return node.type === "html" && !readHtmlToken(node, node.value).maskedSource.trim();
41
+ }
42
+ function htmlComments(value) {
43
+ if (!value.includes("<!--"))
44
+ return [];
45
+ const comments = [];
46
+ function visit(node, topLevel) {
47
+ if (node.type === "comment" && node.position)
48
+ comments.push({ start: node.position.start.offset ?? 0, end: node.position.end.offset ?? value.length, topLevel });
49
+ if ("children" in node)
50
+ node.children.forEach((child) => visit(child, node.type === "root"));
51
+ }
52
+ visit(fromHtml(value, { fragment: true }), false);
53
+ return comments;
54
+ }
55
+ function readHtmlToken(node, value) {
56
+ let analyses = htmlTokenAnalyses.get(node);
57
+ const cached = analyses?.get(value);
58
+ if (cached)
59
+ return cached;
60
+ const comments = htmlComments(value);
61
+ const analysis = {
62
+ comments,
63
+ maskedSource: maskSourceRanges(value, comments.map((comment) => [comment.start, comment.end]), 0, value.length)
64
+ };
65
+ if (!analyses) {
66
+ analyses = /* @__PURE__ */ new Map();
67
+ htmlTokenAnalyses.set(node, analyses);
68
+ }
69
+ analyses.set(value, analysis);
70
+ return analysis;
71
+ }
72
+ function markdownTopLevelComments(input) {
73
+ const document = parseMarkdown(input);
74
+ return document.root.children.flatMap((node) => {
75
+ if (node.type !== "html")
76
+ return [];
77
+ const source = markdownNodeSource(document, node);
78
+ const offset = node.position?.start.offset ?? document.start;
79
+ return readHtmlToken(node, source).comments.filter((comment) => comment.topLevel).map((comment) => ({
80
+ start: offset + comment.start,
81
+ end: offset + comment.end,
82
+ source: source.slice(comment.start, comment.end)
83
+ }));
84
+ });
85
+ }
86
+ function maskSourceRanges(source, ranges, start, end) {
87
+ let result = "";
88
+ let offset = start;
89
+ for (const [from, until] of ranges.sort((a, b) => a[0] - b[0])) {
90
+ result += source.slice(offset, from);
91
+ result += source.slice(from, until).replace(/[^\r\n]/g, " ");
92
+ offset = until;
93
+ }
94
+ return result + source.slice(offset, end);
95
+ }
96
+ function markdownText(node) {
97
+ if (node.type === "yaml")
98
+ return "";
99
+ if (node.type === "html")
100
+ return readHtmlToken(node, node.value).maskedSource;
101
+ if (node.type === "image" || node.type === "imageReference")
102
+ return node.alt ?? "";
103
+ if ("value" in node)
104
+ return node.value;
105
+ if ("children" in node) {
106
+ const separator = ["root", "list", "listItem", "blockquote"].includes(node.type) ? "\n" : "";
107
+ return node.children.map((child) => markdownText(child)).filter(Boolean).join(separator);
108
+ }
109
+ return node.type === "break" ? "\n" : "";
110
+ }
111
+ function markdownSection(document, nodes, start, end) {
112
+ return { source: document.source, root: { type: "root", children: nodes }, start, end };
113
+ }
114
+ function visibleMarkdownSource(input, options = {}) {
115
+ const document = parseMarkdown(input);
116
+ const visibleNodes = /* @__PURE__ */ new Set();
117
+ function markVisibleContent(node) {
118
+ if (node.type === "yaml" || node.type === "code" && !options.includeCode)
119
+ return false;
120
+ let visible = false;
121
+ if (node.type === "image" || node.type === "imageReference" || node.type === "footnoteReference")
122
+ visible = true;
123
+ else if (node.type === "html")
124
+ visible = !isMarkdownComment(node);
125
+ else if ("value" in node)
126
+ visible = Boolean(node.value.trim());
127
+ else if ("children" in node)
128
+ visible = node.children.map(markVisibleContent).some(Boolean);
129
+ if (visible)
130
+ visibleNodes.add(node);
131
+ return visible;
132
+ }
133
+ markVisibleContent(document.root);
134
+ const ranges = [];
135
+ walkMarkdown(document.root, (node) => {
136
+ if (!visibleNodes.has(node)) {
137
+ const start = Math.max(document.start, node.position?.start.offset ?? document.start);
138
+ const end = Math.min(document.end, node.position?.end.offset ?? document.end);
139
+ if (end > start)
140
+ ranges.push([start, end]);
141
+ return false;
142
+ }
143
+ if (node.type === "html") {
144
+ const nodeStart = node.position?.start.offset ?? document.start;
145
+ for (const comment of readHtmlToken(node, markdownNodeSource(document, node)).comments)
146
+ ranges.push([nodeStart + comment.start, nodeStart + comment.end]);
147
+ return false;
148
+ }
149
+ });
150
+ return maskSourceRanges(document.source, ranges, document.start, document.end);
151
+ }
152
+ function visibleMarkdownLines(input) {
153
+ return visibleMarkdownSource(input).split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
154
+ }
155
+ function markdownListItems(input) {
156
+ const document = parseMarkdown(input);
157
+ const items = [];
158
+ walkMarkdown(document.root, (node) => {
159
+ if (node.type === "blockquote" || node.type === "footnoteDefinition")
160
+ return false;
161
+ if (node.type === "listItem") {
162
+ const source = markdownNodeSource(document, node);
163
+ items.push({ node, line: source.split(/\r?\n/, 1)[0].trim() });
164
+ }
165
+ });
166
+ return items;
167
+ }
168
+ function markdownFrontmatter(input) {
169
+ const document = parseMarkdown(input);
170
+ const node = document.root.children[0];
171
+ if (node?.type !== "yaml" || node.position?.start.offset !== 0)
172
+ return null;
173
+ const raw = markdownNodeSource(document, node);
174
+ const lines = raw.split(/\r?\n/);
175
+ if (lines[0] !== "---" || !["---", "..."].includes(lines.at(-1) ?? ""))
176
+ return null;
177
+ let end = node.position.end.offset ?? raw.length;
178
+ if (document.source.slice(end, end + 2) === "\r\n")
179
+ end += 2;
180
+ else if (document.source[end] === "\n")
181
+ end++;
182
+ return { node, value: node.value, end };
183
+ }
184
+ function markdownProse(input) {
185
+ const document = parseMarkdown(input);
186
+ const fragments = [];
187
+ walkMarkdown(document.root, (node) => {
188
+ if (node.type === "text" || node.type === "html")
189
+ fragments.push({ value: markdownText(node), line: node.position?.start.line ?? 1 });
190
+ });
191
+ return fragments;
192
+ }
193
+
194
+ // src/core/document-model.ts
195
+ var CHANGE_DOCUMENT_SCHEMA = {
196
+ titlePrefix: "# Change:",
197
+ sections: [
198
+ { id: "proposal", heading: "Proposal" },
199
+ { id: "spec", heading: "Spec" },
200
+ { id: "design", heading: "Design" },
201
+ { id: "tasks", heading: "Tasks" },
202
+ { id: "verify", heading: "Verify" },
203
+ { id: "blockers", heading: "Blockers" }
204
+ ]
205
+ };
206
+ var GROUP_BRIEF_DOCUMENT_SCHEMA = {
207
+ titlePrefix: "# Change Group:",
208
+ sections: [
209
+ { id: "goal", heading: "Goal" },
210
+ { id: "scope", heading: "Scope" },
211
+ { id: "sharedConstraints", heading: "Shared Constraints" },
212
+ { id: "slices", heading: "Slices" },
213
+ { id: "completionConditions", heading: "Completion Conditions" },
214
+ { id: "durableOutcomes", heading: "Durable Outcomes" },
215
+ { id: "blockers", heading: "Blockers" }
216
+ ]
217
+ };
218
+ var DocumentSectionCardinalityError = class extends Error {
219
+ constructor(sectionId, count) {
220
+ super(`document must contain exactly one ${sectionId} section; found ${count}`);
221
+ this.sectionId = sectionId;
222
+ this.count = count;
223
+ this.name = "DocumentSectionCardinalityError";
224
+ }
225
+ sectionId;
226
+ count;
227
+ };
228
+ function parseRspDocument(input, schema) {
229
+ const markdown = parseMarkdown(input);
230
+ const source = markdown.source;
231
+ const definitionsByHeading = new Map(schema.sections.map((definition) => [definition.heading, definition]));
232
+ const observedHeadings = collectLevelTwoHeadings(markdown);
233
+ const sections = /* @__PURE__ */ new Map();
234
+ const unknownHeadings = [];
235
+ for (let index = 0; index < observedHeadings.length; index++) {
236
+ const observed = observedHeadings[index];
237
+ const definition = definitionsByHeading.get(observed.heading);
238
+ if (!definition) {
239
+ unknownHeadings.push(observed.heading);
240
+ continue;
241
+ }
242
+ const nextHeading = observedHeadings[index + 1];
243
+ const bodyEnd = nextHeading ? lineBreakStart(source, nextHeading.start) : source.length;
244
+ const section = {
245
+ id: definition.id,
246
+ heading: definition.heading,
247
+ canonical: observed.canonical,
248
+ bodyEnd,
249
+ markdown: markdownSection(markdown, markdown.root.children.filter((node) => (node.position?.start.offset ?? 0) >= observed.end && (node.position?.end.offset ?? source.length) <= bodyEnd), observed.end, bodyEnd)
250
+ };
251
+ const matches = sections.get(definition.id) ?? [];
252
+ matches.push(section);
253
+ sections.set(definition.id, matches);
254
+ }
255
+ const missingSections = schema.sections.filter((definition) => !sections.has(definition.id)).map((definition) => definition.id);
256
+ const duplicateSections = schema.sections.filter((definition) => (sections.get(definition.id)?.length ?? 0) > 1).map((definition) => definition.id);
257
+ const titleOccurrences = collectDocumentTitles(markdown, schema.titlePrefix);
258
+ const titles = titleOccurrences.map((title) => title.value);
259
+ return {
260
+ source,
261
+ markdown,
262
+ schema,
263
+ title: titles[0] ?? null,
264
+ titles,
265
+ titleOccurrences,
266
+ sections,
267
+ unknownHeadings,
268
+ missingSections,
269
+ duplicateSections
270
+ };
271
+ }
272
+ function getDocumentTitles(document, compatibility = "broad") {
273
+ if (compatibility === "broad")
274
+ return document.titles;
275
+ return document.titleOccurrences.filter((title) => compatibility === "identity" ? title.identityCompatible : title.spaced).map((title) => title.value);
276
+ }
277
+ function getDocumentTitle(document, compatibility = "broad") {
278
+ return getDocumentTitles(document, compatibility)[0] ?? null;
279
+ }
280
+ function getDocumentSections(document, sectionId) {
281
+ return document.sections.get(sectionId) ?? [];
282
+ }
283
+ function getDocumentSection(document, sectionId) {
284
+ return getDocumentSections(document, sectionId)[0];
285
+ }
286
+ function getDocumentSectionMarkdown(document, sectionId) {
287
+ return getDocumentSection(document, sectionId)?.markdown ?? markdownSection(document.markdown, [], 0, 0);
288
+ }
289
+ function getDocumentSectionDefinitionByHeading(schema, heading) {
290
+ return schema.sections.find((candidate) => candidate.heading === heading);
291
+ }
292
+ function getCanonicalSectionHeadings(schema) {
293
+ return schema.sections.map((section) => section.heading);
294
+ }
295
+ function renderDocumentSectionHeading(schema, sectionId) {
296
+ const definition = schema.sections.find((candidate) => candidate.id === sectionId);
297
+ if (!definition)
298
+ throw new Error(`unknown document section: ${sectionId}`);
299
+ return `## ${definition.heading}`;
300
+ }
301
+ function renderDocumentTitle(schema, title) {
302
+ return `${schema.titlePrefix} ${title}`;
303
+ }
304
+ function appendDocumentSectionItem(document, sectionId, item) {
305
+ const matches = getDocumentSections(document, sectionId);
306
+ if (matches.length !== 1)
307
+ throw new DocumentSectionCardinalityError(sectionId, matches.length);
308
+ const insertAt = matches[0].bodyEnd;
309
+ const before = document.source.slice(0, insertAt);
310
+ const separator = before.endsWith("\n\n") ? "" : before.endsWith("\n") ? "\n" : "\n\n";
311
+ return `${before}${separator}${item}
312
+ ${document.source.slice(insertAt)}`;
313
+ }
314
+ function collectLevelTwoHeadings(document) {
315
+ const headings = [];
316
+ for (const node of document.root.children) {
317
+ if (node.type !== "heading" || node.depth !== 2 || node.position?.start.column !== 1)
318
+ continue;
319
+ const raw = markdownNodeSource(document, node);
320
+ if (!raw.startsWith("## ") || raw.includes("\n"))
321
+ continue;
322
+ const value = raw.slice(3);
323
+ const heading = value.trimEnd();
324
+ headings.push({
325
+ heading,
326
+ canonical: heading === value,
327
+ start: node.position.start.offset,
328
+ end: node.position.end.offset
329
+ });
330
+ }
331
+ return headings;
332
+ }
333
+ function collectDocumentTitles(document, prefix) {
334
+ const titles = [];
335
+ for (const node of document.root.children) {
336
+ if (node.type !== "heading" || node.depth !== 1)
337
+ continue;
338
+ const start = node.position.start.offset;
339
+ const lineStart = document.source.lastIndexOf("\n", start - 1) + 1;
340
+ const line = document.source.slice(lineStart, node.position.end.offset);
341
+ const trimmed = line.trim();
342
+ if (!trimmed.startsWith(prefix))
343
+ continue;
344
+ const identity = line.startsWith(prefix) ? line.slice(prefix.length).match(/^\s+(\S+)\s*$/) : null;
345
+ titles.push({
346
+ value: trimmed.slice(prefix.length).trim(),
347
+ identityCompatible: identity !== null,
348
+ spaced: line.startsWith(`${prefix} `)
349
+ });
350
+ }
351
+ return titles;
352
+ }
353
+ function lineBreakStart(source, headingStart) {
354
+ if (headingStart > 0 && source[headingStart - 1] === "\n")
355
+ return headingStart - 1;
356
+ return headingStart;
357
+ }
358
+
359
+ // src/core/yaml.mjs
360
+ import { parseDocument } from "yaml";
361
+ function parseYamlDocument(source) {
362
+ const document = parseDocument(source, { keepSourceTokens: true });
363
+ if (document.errors.length)
364
+ throw new Error(document.errors.map((error) => error.message).join("; "));
365
+ return document;
366
+ }
367
+ function yamlMapping(document) {
368
+ const value = document.toJS();
369
+ if (value === null || value === void 0)
370
+ return {};
371
+ if (typeof value !== "object" || Array.isArray(value))
372
+ throw new Error("YAML document must be a mapping/object");
373
+ return value;
374
+ }
375
+
376
+ // src/core/content.ts
377
+ function parseYamlText(text) {
378
+ return yamlMapping(parseYamlDocument(text));
379
+ }
380
+ function parseFrontmatter(content) {
381
+ const matter = markdownFrontmatter(content);
382
+ return matter ? parseYamlText(matter.value) : null;
383
+ }
384
+ function checkboxItem(line) {
385
+ const marker = line.match(/^[-*+]\s+\[([ /x-])\]/i);
386
+ return marker ? { state: marker[1].toLowerCase(), line } : null;
387
+ }
388
+ function collectCheckboxItems(content) {
389
+ return markdownListItems(content).flatMap(({ line }) => {
390
+ const item = checkboxItem(line);
391
+ return item ? [item] : [];
392
+ });
393
+ }
394
+ function countCheckboxItems(items) {
395
+ const count = { todo: 0, progress: 0, done: 0, dropped: 0, total: items.length };
396
+ for (const { state } of items) {
397
+ if (state === " ")
398
+ count.todo++;
399
+ else if (state === "/")
400
+ count.progress++;
401
+ else if (state === "x")
402
+ count.done++;
403
+ else
404
+ count.dropped++;
405
+ }
406
+ return count;
407
+ }
408
+ function countCheckboxes(content) {
409
+ return countCheckboxItems(collectCheckboxItems(content));
410
+ }
411
+ function collectVerifyCheckboxes(content) {
412
+ const document = parseMarkdown(content);
413
+ const buckets = { required: [], optional: [], unclassified: [] };
414
+ let current = "unclassified";
415
+ let sawClassificationHeading = false;
416
+ walkMarkdown(document.root, (node) => {
417
+ if (node.type === "blockquote" || node.type === "footnoteDefinition")
418
+ return false;
419
+ if (node.type === "heading" && node.depth === 3 && node.position?.start.column === 1) {
420
+ const heading = markdownNodeSource(document, node).match(/^###\s+(Required|Optional)\s*$/i);
421
+ if (heading) {
422
+ current = heading[1].toLowerCase();
423
+ sawClassificationHeading = true;
424
+ }
425
+ }
426
+ if (node.type === "listItem") {
427
+ const line = markdownNodeSource(document, node).split(/\r?\n/, 1)[0].trim();
428
+ const item = checkboxItem(line);
429
+ if (item)
430
+ buckets[current].push(item);
431
+ }
432
+ });
433
+ return { ...buckets, legacy: buckets.unclassified.length > 0 || !sawClassificationHeading };
434
+ }
435
+ function summarizeVerifyCheckboxes(buckets) {
436
+ return {
437
+ required: countCheckboxItems([...buckets.required, ...buckets.unclassified]),
438
+ optional: countCheckboxItems(buckets.optional),
439
+ unclassified: countCheckboxItems(buckets.unclassified),
440
+ legacy: buckets.legacy
441
+ };
442
+ }
443
+ function openCheckboxLines(items) {
444
+ return items.filter((item) => item.state === " " || item.state === "/").map((item) => item.line);
445
+ }
446
+ function getOpenCheckboxes(content) {
447
+ return openCheckboxLines(collectCheckboxItems(content));
448
+ }
449
+ function changeDocument(content) {
450
+ return typeof content === "string" ? parseRspDocument(content, CHANGE_DOCUMENT_SCHEMA) : content;
451
+ }
452
+ function hasMeaningfulBlockerBody(body) {
453
+ return readBlockerEntries(body).length > 0;
454
+ }
455
+ function getVisibleBodyLines(body) {
456
+ return visibleMarkdownLines(body);
457
+ }
458
+ function hasMeaningfulBlockers(content) {
459
+ return hasMeaningfulBlockerBody(getDocumentSectionMarkdown(changeDocument(content), "blockers"));
460
+ }
461
+ function isEmptyBlockerLine(line) {
462
+ const normalized = line.trim();
463
+ return /^none[.。]?$/i.test(normalized) || /^[-*]\s*(?:none[.。]?)?$/i.test(normalized);
464
+ }
465
+ var REQUIRES_RE = /^[-*]\s+requires\s+`([^`]+)`:[ \t]*(\S.*)$/i;
466
+ function readBlockerEntries(input) {
467
+ const document = parseMarkdown(input);
468
+ const entries = [];
469
+ const nodeLines = (node, start = node.position.start.offset) => getVisibleBodyLines(
470
+ markdownSection(document, [node], start, node.position.end.offset)
471
+ );
472
+ const text = (line) => line.replace(/^[-*]\s+/, "").trim();
473
+ const appendExternal = (lines) => {
474
+ for (const line of lines) {
475
+ if (!isEmptyBlockerLine(line))
476
+ entries.push({ kind: "external", line, text: text(line) });
477
+ }
478
+ };
479
+ const visit = (node) => {
480
+ if (node.type === "code")
481
+ return;
482
+ if (node.type !== "list") {
483
+ appendExternal(nodeLines(node));
484
+ return;
485
+ }
486
+ for (const item of node.children) {
487
+ for (const [index, child] of item.children.entries()) {
488
+ if (child.type === "list" || child.type === "code") {
489
+ visit(child);
490
+ continue;
491
+ }
492
+ const lines = nodeLines(child, index === 0 ? item.position.start.offset : child.position.start.offset);
493
+ if (index === 0 && child.type === "paragraph") {
494
+ const line = lines[0] ?? "";
495
+ const dependency = line.match(REQUIRES_RE);
496
+ if (dependency) {
497
+ entries.push({ kind: "dependency", line, text: text(line), name: dependency[1], reason: dependency[2] });
498
+ lines.shift();
499
+ } else if (/^[-*]\s+requires\b/i.test(line)) {
500
+ entries.push({ kind: "malformed-dependency", line, text: text(line) });
501
+ lines.shift();
502
+ }
503
+ }
504
+ appendExternal(lines);
505
+ }
506
+ }
507
+ };
508
+ document.root.children.forEach(visit);
509
+ return entries;
510
+ }
511
+ function detectDeltaSections(content) {
512
+ const body = getDocumentSectionMarkdown(changeDocument(content), "spec");
513
+ const labels = new Set(body.root.children.filter((node) => node.type === "heading" && node.depth === 3).map((node) => markdownText(node).trim().toUpperCase()));
514
+ return { added: labels.has("ADDED"), modified: labels.has("MODIFIED"), removed: labels.has("REMOVED") };
515
+ }
516
+ function parseScenarios(content) {
517
+ const document = parseMarkdown(content);
518
+ const scenarios = [];
519
+ let scenario = null;
520
+ for (const node of document.root.children) {
521
+ if (node.type === "heading" && node.depth <= 4) {
522
+ const title = markdownText(node);
523
+ scenario = (node.depth === 3 || node.depth === 4) && /^Scenario:\s*\S/i.test(title) ? { heading: title.slice(title.indexOf(":") + 1).trim(), steps: [] } : null;
524
+ if (scenario)
525
+ scenarios.push(scenario);
526
+ continue;
527
+ }
528
+ if (scenario && node.type === "list") {
529
+ for (const item of node.children) {
530
+ const line = markdownNodeSource(document, item).split(/\r?\n/, 1)[0].trim();
531
+ const step = line.replace(/^[-*+]\s+/, "");
532
+ if (/^(?:GIVEN|WHEN|THEN|AND|BUT)\b/i.test(step))
533
+ scenario.steps.push(step);
534
+ }
535
+ }
536
+ }
537
+ return scenarios.filter((item) => item.steps.length > 0);
538
+ }
539
+
540
+ // src/core/decisions.ts
541
+ import { existsSync } from "fs";
542
+ import { mkdir, readdir as readdir2, realpath, stat } from "fs/promises";
543
+ import { dirname, isAbsolute, join as join2, posix, relative, resolve, sep } from "path";
544
+
545
+ // src/core/managed-path.ts
546
+ import { lstatSync } from "fs";
547
+ import { readdir, writeFile } from "fs/promises";
548
+ import { join } from "path";
549
+ var ManagedPathError = class extends Error {
550
+ constructor(path, message) {
551
+ super(message);
552
+ this.path = path;
553
+ this.name = "ManagedPathError";
554
+ }
555
+ path;
556
+ };
557
+ function inspectManagedDirectory(path, label, options = {}) {
558
+ try {
559
+ const stats = lstatSync(path);
560
+ if (stats.isDirectory())
561
+ return { exists: true, issue: null };
562
+ return {
563
+ exists: true,
564
+ issue: new ManagedPathError(path, `${label} must be a real directory: ${path}`)
565
+ };
566
+ } catch (error) {
567
+ if (error.code === "ENOENT") {
568
+ if (options.allowMissing)
569
+ return { exists: false, issue: null };
570
+ return {
571
+ exists: false,
572
+ issue: new ManagedPathError(path, `${label} must be a real directory: ${path}`)
573
+ };
574
+ }
575
+ const message = error instanceof Error ? error.message : String(error);
576
+ return {
577
+ exists: false,
578
+ issue: new ManagedPathError(path, `unable to inspect ${label} ${path}: ${message}`)
579
+ };
580
+ }
581
+ }
582
+ function requireManagedDirectory(path, label, options = {}) {
583
+ const inspection = inspectManagedDirectory(path, label, options);
584
+ if (inspection.issue)
585
+ throw inspection.issue;
586
+ return inspection.exists;
587
+ }
588
+ function inspectManagedFile(path, label, options = {}) {
589
+ try {
590
+ const stats = lstatSync(path);
591
+ if (stats.isFile())
592
+ return { exists: true, issue: null };
593
+ return {
594
+ exists: true,
595
+ issue: new ManagedPathError(path, `${label} must be a regular file: ${path}`)
596
+ };
597
+ } catch (error) {
598
+ if (error.code === "ENOENT") {
599
+ if (options.allowMissing)
600
+ return { exists: false, issue: null };
601
+ return {
602
+ exists: false,
603
+ issue: new ManagedPathError(path, `${label} must be a regular file: ${path}`)
604
+ };
605
+ }
606
+ const message = error instanceof Error ? error.message : String(error);
607
+ return {
608
+ exists: false,
609
+ issue: new ManagedPathError(path, `unable to inspect ${label} ${path}: ${message}`)
610
+ };
611
+ }
612
+ }
613
+ function requireManagedFile(path, label, options = {}) {
614
+ const inspection = inspectManagedFile(path, label, options);
615
+ if (inspection.issue)
616
+ throw inspection.issue;
617
+ return inspection.exists;
618
+ }
619
+ async function ensureManagedFile(path, content, label) {
620
+ if (requireManagedFile(path, label, { allowMissing: true }))
621
+ return false;
622
+ await writeFile(path, content, { flag: "wx" });
623
+ return true;
624
+ }
625
+ async function writeManagedFile(path, content, label) {
626
+ const exists = requireManagedFile(path, label, { allowMissing: true });
627
+ if (exists)
628
+ await writeFile(path, content);
629
+ else
630
+ await writeFile(path, content, { flag: "wx" });
631
+ }
632
+ function resolveManagedDirectoryChain(root, segments, label) {
633
+ requireManagedDirectory(root, `${label} root`);
634
+ let current = root;
635
+ for (const segment of segments) {
636
+ if (!segment || segment === "." || segment === ".." || segment.includes("/") || segment.includes("\\"))
637
+ throw new ManagedPathError(current, `${label} contains an invalid directory segment: ${segment}`);
638
+ current = join(current, segment);
639
+ requireManagedDirectory(current, label, { allowMissing: true });
640
+ }
641
+ return current;
642
+ }
643
+ async function inspectManagedFileTree(root, label, options = {}) {
644
+ const result = { rootExists: false, files: [], issues: [] };
645
+ const rootInspection = inspectManagedDirectory(root, `${label} root`, options);
646
+ if (rootInspection.issue) {
647
+ result.issues.push(rootInspection.issue);
648
+ return result;
649
+ }
650
+ if (!rootInspection.exists)
651
+ return result;
652
+ result.rootExists = true;
653
+ await collectManagedFiles(root, label, result, options.includeHidden);
654
+ result.files.sort();
655
+ return result;
656
+ }
657
+ async function collectManagedFiles(directory, label, result, includeHidden = false) {
658
+ let entries;
659
+ try {
660
+ entries = await readdir(directory, { withFileTypes: true });
661
+ } catch (error) {
662
+ const message = error instanceof Error ? error.message : String(error);
663
+ result.issues.push(new ManagedPathError(directory, `unable to inspect the ${label} tree at ${directory}: ${message}`));
664
+ return;
665
+ }
666
+ for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
667
+ if (!includeHidden && entry.name.startsWith("."))
668
+ continue;
669
+ const path = join(directory, entry.name);
670
+ if (entry.isDirectory()) {
671
+ await collectManagedFiles(path, label, result, includeHidden);
672
+ continue;
673
+ }
674
+ if (entry.isFile()) {
675
+ result.files.push(path);
676
+ continue;
677
+ }
678
+ result.issues.push(new ManagedPathError(path, `unsupported entry in the ${label} tree: ${path}`));
679
+ }
680
+ }
681
+
682
+ // src/core/decisions.ts
683
+ var DEFAULT_DECISION_RECORDS_PATH = ".rsp/specs/decisions";
684
+ function normalizeDecisionRecordsPath(pathValue) {
685
+ return posix.normalize(pathValue.trim().replace(/\\/g, "/")).replace(/^\.\//, "").replace(/\/$/, "");
686
+ }
687
+ function validateDecisionRecordsPath(pathValue) {
688
+ if (typeof pathValue !== "string" || pathValue.trim() === "")
689
+ return 'config.yaml field "decisions.path" must be a non-empty string';
690
+ const raw = pathValue.trim();
691
+ const portable = raw.replace(/\\/g, "/");
692
+ if (isAbsolute(raw) || posix.isAbsolute(portable) || /^[a-z]:[\\/]/i.test(raw))
693
+ return 'config.yaml field "decisions.path" must be project-relative';
694
+ const normalized = normalizeDecisionRecordsPath(raw);
695
+ if (normalized === "." || normalized === ".." || normalized.startsWith("../"))
696
+ return 'config.yaml field "decisions.path" must stay inside the Host Project';
697
+ if ((normalized === ".rsp" || normalized.startsWith(".rsp/")) && normalized !== DEFAULT_DECISION_RECORDS_PATH)
698
+ return `config.yaml field "decisions.path" must use ${DEFAULT_DECISION_RECORDS_PATH} or a path outside .rsp/`;
699
+ return null;
700
+ }
701
+ function getDecisionRecordsConfigIssue(config) {
702
+ if (!("decisions" in config))
703
+ return null;
704
+ const decisions = config.decisions;
705
+ if (!decisions || typeof decisions !== "object" || Array.isArray(decisions))
706
+ return 'config.yaml field "decisions" must be a mapping with one path';
707
+ const keys = Object.keys(decisions);
708
+ if (keys.length !== 1 || keys[0] !== "path")
709
+ return 'config.yaml field "decisions" supports only "path"';
710
+ return validateDecisionRecordsPath(decisions.path);
711
+ }
712
+ function resolveDecisionRecordsPath(config) {
713
+ const configured = config.decisions?.path;
714
+ return configured && validateDecisionRecordsPath(configured) === null ? normalizeDecisionRecordsPath(configured) : DEFAULT_DECISION_RECORDS_PATH;
715
+ }
716
+ async function validateDecisionRecordsFilesystemPath(pathValue, cwd = process.cwd()) {
717
+ const projectRoot = await realpath(cwd);
718
+ const targetPath = resolve(projectRoot, pathValue);
719
+ let existingAncestor = targetPath;
720
+ while (!existsSync(existingAncestor)) {
721
+ const parent = dirname(existingAncestor);
722
+ if (parent === existingAncestor)
723
+ return `Decision Record path "${pathValue}" has no accessible Host Project ancestor`;
724
+ existingAncestor = parent;
725
+ }
726
+ const resolvedAncestor = await realpath(existingAncestor);
727
+ const rel = relative(projectRoot, resolvedAncestor);
728
+ if (rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel))
729
+ return `Decision Record path "${pathValue}" resolves outside the Host Project through ${existingAncestor}`;
730
+ if (existsSync(targetPath) && !(await stat(targetPath)).isDirectory())
731
+ return `Decision Record path "${pathValue}" must be a directory`;
732
+ return null;
733
+ }
734
+
735
+ // src/core/config.ts
736
+ var __dirname = dirname2(fileURLToPath(import.meta.url));
737
+ var PKG_ROOT = join3(__dirname, "..");
738
+ var RSP_DIR = ".rsp";
739
+ var RSP_RULES_PATH = join3(RSP_DIR, "rsp-rules.md");
740
+ var OBSOLETE_RSP_RULES_PATH = join3(RSP_DIR, "rules", "rsp-rules.md");
741
+ var CHANGES_DIR = join3(RSP_DIR, "changes");
742
+ var FOCUS_DIR = join3(RSP_DIR, "focus.d");
743
+ var MAX_FOCUS_CAPSULE_BYTES = 4096;
744
+ var ARCHIVES_DIR = join3(RSP_DIR, "archives");
745
+ var LOCK_PATH = join3(RSP_DIR, ".lock");
746
+ var CONFIG_PATH = join3(RSP_DIR, "config.yaml");
747
+ var VALID_KINDS = ["feature", "fix", "refactor", "docs", "ops", "research"];
748
+ var DEFAULT_REQUIRED_SECTIONS = getCanonicalSectionHeadings(CHANGE_DOCUMENT_SCHEMA);
749
+ var DEFAULT_WORKFLOW_POLICY = { delegation: "off", closeout: "ask" };
750
+ var INVALID_CONFIG_WORKFLOW_POLICY = { delegation: "off", closeout: "manual" };
751
+ var CONFIG_DEFAULTS = {
752
+ kinds: [],
753
+ decisions: { path: ".rsp/specs/decisions" },
754
+ language: { default: "en" },
755
+ workflow: { delegation: "off", closeout: "ask" }
756
+ };
757
+ function generateConfigTemplate(config = {}) {
758
+ const kinds = config.kinds ?? CONFIG_DEFAULTS.kinds;
759
+ const decisions = { ...CONFIG_DEFAULTS.decisions, ...config.decisions };
760
+ const workflow = { ...CONFIG_DEFAULTS.workflow, ...config.workflow };
761
+ const language = { ...CONFIG_DEFAULTS.language, ...config.language };
762
+ const kindsYaml = stringify(kinds).trimEnd();
763
+ const renderedKinds = kinds.length === 0 ? `kinds: ${kindsYaml}` : `kinds:
764
+ ${kindsYaml.split("\n").map((line) => ` ${line}`).join("\n")}`;
765
+ const artifactsLine = language.artifacts ? ` artifacts: ${stringify(language.artifacts).trim()}` : " # artifacts: zh-CN";
766
+ const commitLine = language.commit ? ` commit: ${stringify(language.commit).trim()}` : " # commit: zh-CN";
767
+ return `# RSP project configuration
768
+ # An empty kinds list retains the built-in defaults.
769
+ # A non-empty kinds list replaces the built-in defaults; it does not extend them.
770
+ # Every entry must be a unique non-empty string.
771
+ #
772
+ # Built-in defaults:
773
+ # kinds: ${VALID_KINDS.join(", ")}
774
+ #
775
+ ${renderedKinds}
776
+
777
+ # Decision Records default to .rsp/specs/decisions.
778
+ decisions:
779
+ path: ${stringify(decisions.path).trim()}
780
+
781
+ # Automatic delegation is off; explicit worker requests remain allowed.
782
+ # Closeout applies only to established final delivery: ask, auto, or manual.
783
+ # Ordinary completion and checkpoint commits keep work open. No setting grants Git authority.
784
+ workflow:
785
+ delegation: ${workflow.delegation}
786
+ closeout: ${workflow.closeout}
787
+
788
+ # Set one shared project default for durable artifact and commit prose.
789
+ # Response language remains user/session-owned and is never read from this project mapping.
790
+ language:
791
+ default: ${stringify(language.default).trim()}
792
+ ${artifactsLine}
793
+ ${commitLine}
794
+ `;
795
+ }
796
+ var DELEGATION_POLICIES = ["off", "auto"];
797
+ var CLOSEOUT_POLICIES = ["ask", "auto", "manual"];
798
+ var _configCache = null;
799
+ async function loadRspConfig() {
800
+ const inspection = await inspectRspConfig();
801
+ if (inspection.issues.length > 0)
802
+ throw new Error(inspection.issues.join("; "));
803
+ return inspection.config;
804
+ }
805
+ async function inspectRspConfig(cwd = process.cwd()) {
806
+ cwd = resolve2(cwd);
807
+ if (_configCache?.cwd === cwd)
808
+ return _configCache.inspection;
809
+ const configPath = join3(cwd, CONFIG_PATH);
810
+ const configFile = inspectManagedFile(configPath, "config file", { allowMissing: true });
811
+ if (configFile.issue)
812
+ throw configFile.issue;
813
+ if (!configFile.exists) {
814
+ const inspection2 = { config: {}, issues: [] };
815
+ _configCache = { cwd, inspection: inspection2 };
816
+ return inspection2;
817
+ }
818
+ const raw = await readFile(configPath, "utf-8");
819
+ const parsed = parseYamlText(raw);
820
+ const issues = validateRspConfig(parsed);
821
+ const decisions = parsed.decisions && typeof parsed.decisions === "object" && !Array.isArray(parsed.decisions) ? parsed.decisions : void 0;
822
+ const decisionPath = decisions?.path;
823
+ const workflowValue = parsed.workflow && typeof parsed.workflow === "object" && !Array.isArray(parsed.workflow) ? parsed.workflow : void 0;
824
+ const languageValue = parsed.language && typeof parsed.language === "object" && !Array.isArray(parsed.language) ? parsed.language : void 0;
825
+ const delegation = DELEGATION_POLICIES.includes(workflowValue?.delegation) ? workflowValue?.delegation : void 0;
826
+ const closeout = CLOSEOUT_POLICIES.includes(workflowValue?.closeout) ? workflowValue?.closeout : void 0;
827
+ const kinds = Array.isArray(parsed.kinds) && parsed.kinds.every((value) => typeof value === "string" && value.trim() !== "") ? parsed.kinds.map((value) => value.trim()) : void 0;
828
+ const decisionRecordsIssue = getDecisionRecordsConfigIssue(parsed);
829
+ const inspection = {
830
+ config: {
831
+ kinds,
832
+ decisions: decisionRecordsIssue === null && validateDecisionRecordsPath(decisionPath) === null ? { path: normalizeDecisionRecordsPath(String(decisionPath)) } : void 0,
833
+ workflow: workflowValue ? { delegation, closeout } : void 0,
834
+ language: parseLanguageConfig(languageValue)
835
+ },
836
+ issues
837
+ };
838
+ _configCache = { cwd, inspection };
839
+ return inspection;
840
+ }
841
+ function validateRspConfig(parsed) {
842
+ const issues = [];
843
+ const supported = /* @__PURE__ */ new Set(["kinds", "decisions", "workflow", "language"]);
844
+ for (const key of Object.keys(parsed)) {
845
+ if (key === "manage") {
846
+ issues.push('config.yaml field "manage" requires explicit migration: remove manage and choose workflow.delegation (off | auto) and workflow.closeout (ask | auto | manual); recommended defaults are off/ask. Old local closeout no longer grants Git authority. rsp update will not convert it automatically');
847
+ } else if (key === "required_sections") {
848
+ issues.push('config.yaml field "required_sections" is no longer supported');
849
+ } else if (!supported.has(key)) {
850
+ issues.push(`config.yaml field "${key}" is not supported`);
851
+ }
852
+ }
853
+ if ("kinds" in parsed) {
854
+ if (!Array.isArray(parsed.kinds)) {
855
+ issues.push('config.yaml field "kinds" must be a YAML list');
856
+ } else if (!parsed.kinds.every((value) => typeof value === "string" && value.trim() !== "")) {
857
+ issues.push('config.yaml field "kinds" must contain only non-empty strings');
858
+ } else {
859
+ const kinds = parsed.kinds.map((value) => value.trim());
860
+ const duplicates = [...new Set(kinds.filter((value, index) => kinds.indexOf(value) !== index))];
861
+ if (duplicates.length > 0)
862
+ issues.push(`config.yaml field "kinds" contains duplicate entries: ${duplicates.join(", ")}`);
863
+ }
864
+ }
865
+ const decisionRecordsIssue = getDecisionRecordsConfigIssue(parsed);
866
+ if (decisionRecordsIssue)
867
+ issues.push(decisionRecordsIssue);
868
+ if ("workflow" in parsed) {
869
+ if (!parsed.workflow || typeof parsed.workflow !== "object" || Array.isArray(parsed.workflow)) {
870
+ issues.push('config.yaml field "workflow" must be a YAML mapping');
871
+ } else {
872
+ const workflow = parsed.workflow;
873
+ const supportedWorkflowFields = /* @__PURE__ */ new Set(["delegation", "closeout"]);
874
+ for (const key of Object.keys(workflow)) {
875
+ if (!supportedWorkflowFields.has(key))
876
+ issues.push(`config.yaml field "workflow.${key}" is not supported`);
877
+ }
878
+ if ("delegation" in workflow && !DELEGATION_POLICIES.includes(workflow.delegation))
879
+ issues.push('config.yaml field "workflow.delegation" must be one of: off, auto');
880
+ if ("closeout" in workflow && !CLOSEOUT_POLICIES.includes(workflow.closeout))
881
+ issues.push('config.yaml field "workflow.closeout" must be one of: ask, auto, manual');
882
+ }
883
+ }
884
+ if ("language" in parsed) {
885
+ if (!parsed.language || typeof parsed.language !== "object" || Array.isArray(parsed.language)) {
886
+ issues.push('config.yaml field "language" must be a YAML mapping');
887
+ } else {
888
+ const language = parsed.language;
889
+ const supportedLanguageFields = /* @__PURE__ */ new Set(["default", "artifacts", "commit"]);
890
+ for (const key of Object.keys(language)) {
891
+ if (!supportedLanguageFields.has(key))
892
+ issues.push(`config.yaml field "language.${key}" is not supported`);
893
+ }
894
+ if (!("default" in language))
895
+ issues.push('config.yaml field "language.default" is required when language is configured');
896
+ for (const key of supportedLanguageFields) {
897
+ if (key in language && canonicalizeLanguageTag(language[key]) === null)
898
+ issues.push(`config.yaml field "language.${key}" must be a non-empty valid BCP 47 language tag`);
899
+ }
900
+ }
901
+ }
902
+ return issues;
903
+ }
904
+ function clearConfigCache() {
905
+ _configCache = null;
906
+ }
907
+ function resolveKinds(config) {
908
+ return config.kinds && config.kinds.length > 0 ? config.kinds : [...VALID_KINDS];
909
+ }
910
+ function resolveRequiredSections(_config) {
911
+ return [...DEFAULT_REQUIRED_SECTIONS];
912
+ }
913
+ function resolveWorkflowPolicy(config, options = {}) {
914
+ if (options.configValid === false)
915
+ return { ...INVALID_CONFIG_WORKFLOW_POLICY };
916
+ return {
917
+ delegation: config.workflow?.delegation ?? DEFAULT_WORKFLOW_POLICY.delegation,
918
+ closeout: config.workflow?.closeout ?? DEFAULT_WORKFLOW_POLICY.closeout
919
+ };
920
+ }
921
+ function resolveLanguagePolicy(config, options = {}) {
922
+ if (options.configValid === false || !config.language)
923
+ return { artifacts: null, commit: null };
924
+ return {
925
+ artifacts: config.language.artifacts ?? config.language.default,
926
+ commit: config.language.commit ?? config.language.default
927
+ };
928
+ }
929
+ function parseLanguageConfig(value) {
930
+ if (!value)
931
+ return void 0;
932
+ const defaultLanguage = canonicalizeLanguageTag(value.default);
933
+ if (defaultLanguage === null)
934
+ return void 0;
935
+ const artifacts = canonicalizeLanguageTag(value.artifacts);
936
+ const commit = canonicalizeLanguageTag(value.commit);
937
+ return {
938
+ default: defaultLanguage,
939
+ artifacts: artifacts ?? void 0,
940
+ commit: commit ?? void 0
941
+ };
942
+ }
943
+ function canonicalizeLanguageTag(value) {
944
+ if (typeof value !== "string" || value.trim() === "")
945
+ return null;
946
+ try {
947
+ return Intl.getCanonicalLocales(value.trim())[0] ?? null;
948
+ } catch {
949
+ return null;
950
+ }
951
+ }
952
+ async function getVersion() {
953
+ const { version } = JSON.parse(await readFile(join3(PKG_ROOT, "package.json"), "utf-8"));
954
+ return version;
955
+ }
956
+
957
+ export {
958
+ parseMarkdown,
959
+ walkMarkdown,
960
+ isMarkdownComment,
961
+ markdownTopLevelComments,
962
+ markdownText,
963
+ visibleMarkdownSource,
964
+ markdownListItems,
965
+ markdownProse,
966
+ CHANGE_DOCUMENT_SCHEMA,
967
+ GROUP_BRIEF_DOCUMENT_SCHEMA,
968
+ DocumentSectionCardinalityError,
969
+ parseRspDocument,
970
+ getDocumentTitles,
971
+ getDocumentTitle,
972
+ getDocumentSections,
973
+ getDocumentSectionMarkdown,
974
+ getDocumentSectionDefinitionByHeading,
975
+ getCanonicalSectionHeadings,
976
+ renderDocumentSectionHeading,
977
+ renderDocumentTitle,
978
+ appendDocumentSectionItem,
979
+ parseFrontmatter,
980
+ countCheckboxes,
981
+ collectVerifyCheckboxes,
982
+ summarizeVerifyCheckboxes,
983
+ openCheckboxLines,
984
+ getOpenCheckboxes,
985
+ hasMeaningfulBlockerBody,
986
+ getVisibleBodyLines,
987
+ hasMeaningfulBlockers,
988
+ readBlockerEntries,
989
+ detectDeltaSections,
990
+ parseScenarios,
991
+ ManagedPathError,
992
+ inspectManagedDirectory,
993
+ requireManagedDirectory,
994
+ inspectManagedFile,
995
+ requireManagedFile,
996
+ ensureManagedFile,
997
+ writeManagedFile,
998
+ resolveManagedDirectoryChain,
999
+ inspectManagedFileTree,
1000
+ DEFAULT_DECISION_RECORDS_PATH,
1001
+ resolveDecisionRecordsPath,
1002
+ validateDecisionRecordsFilesystemPath,
1003
+ pc,
1004
+ PKG_ROOT,
1005
+ RSP_DIR,
1006
+ RSP_RULES_PATH,
1007
+ OBSOLETE_RSP_RULES_PATH,
1008
+ CHANGES_DIR,
1009
+ FOCUS_DIR,
1010
+ MAX_FOCUS_CAPSULE_BYTES,
1011
+ ARCHIVES_DIR,
1012
+ LOCK_PATH,
1013
+ CONFIG_PATH,
1014
+ generateConfigTemplate,
1015
+ loadRspConfig,
1016
+ inspectRspConfig,
1017
+ clearConfigCache,
1018
+ resolveKinds,
1019
+ resolveRequiredSections,
1020
+ resolveWorkflowPolicy,
1021
+ resolveLanguagePolicy,
1022
+ getVersion
1023
+ };