@oevery/rsp 3.1.0-beta.3 → 3.1.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -13
- package/README.zh-CN.md +20 -13
- package/dist/{chunk-NX55NIC3.mjs → chunk-D7XDXBFO.mjs} +1 -1
- package/dist/{chunk-7KKAUBKN.mjs → chunk-FI3SYHVH.mjs} +560 -429
- package/dist/{chunk-OTJFL6XH.mjs → chunk-FJVH7ZYU.mjs} +344 -216
- package/dist/cli.mjs +107 -63
- package/dist/{entry-WZDVKDUM.mjs → entry-5OPKUZHF.mjs} +2 -2
- package/dist/{entry-X23GPCVS.mjs → entry-FVUG6YW2.mjs} +2 -2
- package/package.json +1 -1
- package/rules/rsp-rules.md +4 -3
- package/skills/rsp/SKILL.md +4 -4
- package/skills/rsp/references/managed-routing.md +6 -2
- package/skills/rsp-commit/SKILL.md +2 -2
- package/skills/rsp-manage/SKILL.md +7 -3
- package/skills/rsp-release-docs/references/convention-discovery.md +2 -2
|
@@ -215,6 +215,162 @@ async function validateDecisionRecordsFilesystemPath(pathValue) {
|
|
|
215
215
|
return null;
|
|
216
216
|
}
|
|
217
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
|
+
|
|
218
374
|
// src/core/helpers.ts
|
|
219
375
|
import { existsSync as existsSync2 } from "fs";
|
|
220
376
|
import { readdir as readdir3, readFile, rmdir } from "fs/promises";
|
|
@@ -314,71 +470,64 @@ function normalizeLogicalPath(pathValue) {
|
|
|
314
470
|
return pathValue.split(sep2).join("/").replace(/\\/g, "/");
|
|
315
471
|
}
|
|
316
472
|
function generateChangeContent(name, summary = "", kind, options = {}) {
|
|
317
|
-
const
|
|
473
|
+
const placeholder = "<\u2026>";
|
|
474
|
+
const proposalSummary = summary || placeholder;
|
|
318
475
|
if (name === "project-setup") {
|
|
319
476
|
return `---
|
|
320
477
|
kind: ops
|
|
321
478
|
---
|
|
322
479
|
|
|
323
|
-
|
|
480
|
+
${renderDocumentTitle(CHANGE_DOCUMENT_SCHEMA, "project-setup")}
|
|
324
481
|
|
|
325
|
-
|
|
482
|
+
${changeSectionHeading("proposal")}
|
|
326
483
|
- Outcome: ${proposalSummary}
|
|
327
484
|
- Why:
|
|
328
|
-
-
|
|
485
|
+
- ${placeholder}
|
|
329
486
|
- Scope:
|
|
330
|
-
-
|
|
331
|
-
- Fill .rsp/specs/design.md with durable architecture facts
|
|
332
|
-
- Add stable navigation, context, instructions, or validation steps to project-owned CONTEXT.md or the nearest AGENTS.md when those conventions exist
|
|
487
|
+
- ${placeholder}
|
|
333
488
|
- Non-goals:
|
|
334
|
-
-
|
|
489
|
+
- ${placeholder}
|
|
335
490
|
|
|
336
|
-
|
|
491
|
+
${changeSectionHeading("spec")}
|
|
337
492
|
### ADDED
|
|
338
|
-
- Requirement:
|
|
339
|
-
-
|
|
340
|
-
|
|
341
|
-
### MODIFIED
|
|
342
|
-
- Requirement: stable project-owned context and instructions
|
|
343
|
-
- Stable navigation and context are reflected in project-owned CONTEXT.md when that convention exists
|
|
344
|
-
- Stable agent instructions are reflected in the nearest project-owned AGENTS.md when needed
|
|
493
|
+
- Requirement: ${placeholder}
|
|
494
|
+
- ${placeholder}
|
|
345
495
|
|
|
346
496
|
### Acceptance
|
|
347
|
-
#### Scenario:
|
|
348
|
-
- GIVEN
|
|
349
|
-
- WHEN
|
|
350
|
-
- THEN
|
|
351
|
-
- AND project-owned CONTEXT.md and the nearest AGENTS.md contain only the stable context and instructions assigned to them by Host Project conventions
|
|
497
|
+
#### Scenario: ${placeholder}
|
|
498
|
+
- GIVEN ${placeholder}
|
|
499
|
+
- WHEN ${placeholder}
|
|
500
|
+
- THEN ${placeholder}
|
|
352
501
|
|
|
353
|
-
|
|
502
|
+
${changeSectionHeading("design")}
|
|
354
503
|
- Approach:
|
|
355
|
-
-
|
|
504
|
+
- ${placeholder}
|
|
356
505
|
- Boundaries:
|
|
357
|
-
-
|
|
506
|
+
- ${placeholder}
|
|
358
507
|
- Affected areas:
|
|
359
508
|
- .rsp/specs/design.md
|
|
360
|
-
-
|
|
509
|
+
- CONTEXT.md
|
|
510
|
+
- AGENTS.md
|
|
361
511
|
- Durable outcome targets:
|
|
362
|
-
- Current facts:
|
|
363
|
-
- Lasting rationale:
|
|
512
|
+
- Current facts: ${placeholder}
|
|
513
|
+
- Lasting rationale: ${placeholder}
|
|
364
514
|
- Constraints:
|
|
365
|
-
-
|
|
515
|
+
- ${placeholder}
|
|
366
516
|
|
|
367
|
-
|
|
368
|
-
- [ ]
|
|
369
|
-
- [ ]
|
|
370
|
-
- [ ]
|
|
371
|
-
- [ ] Add stable project-owned context, instructions, or validation steps only where the Host Project convention requires them
|
|
517
|
+
${changeSectionHeading("tasks")}
|
|
518
|
+
- [ ] .rsp/specs/design.md: ${placeholder}
|
|
519
|
+
- [ ] CONTEXT.md: ${placeholder}
|
|
520
|
+
- [ ] AGENTS.md: ${placeholder}
|
|
372
521
|
|
|
373
|
-
|
|
522
|
+
${changeSectionHeading("verify")}
|
|
374
523
|
- Automated:
|
|
375
|
-
- [ ]
|
|
524
|
+
- [ ] rsp doctor \u2014 proves: ${placeholder}
|
|
376
525
|
- Manual or environment:
|
|
377
|
-
- [ ]
|
|
526
|
+
- [ ] ${placeholder}
|
|
378
527
|
- Coverage:
|
|
379
|
-
-
|
|
528
|
+
- ${placeholder}
|
|
380
529
|
|
|
381
|
-
|
|
530
|
+
${changeSectionHeading("blockers")}
|
|
382
531
|
- none
|
|
383
532
|
`;
|
|
384
533
|
}
|
|
@@ -390,9 +539,9 @@ kind: ops
|
|
|
390
539
|
kind: "${frontmatterKind}"
|
|
391
540
|
---
|
|
392
541
|
|
|
393
|
-
|
|
542
|
+
${renderDocumentTitle(CHANGE_DOCUMENT_SCHEMA, name)}
|
|
394
543
|
|
|
395
|
-
|
|
544
|
+
${changeSectionHeading("proposal")}
|
|
396
545
|
- Outcome: ${proposalSummary}
|
|
397
546
|
- Why:
|
|
398
547
|
- ${template.why}
|
|
@@ -401,199 +550,117 @@ kind: "${frontmatterKind}"
|
|
|
401
550
|
- Non-goals:
|
|
402
551
|
- ${template.nonGoals}
|
|
403
552
|
|
|
404
|
-
|
|
553
|
+
${changeSectionHeading("spec")}
|
|
405
554
|
${template.specSection}
|
|
406
555
|
|
|
407
556
|
### Acceptance
|
|
408
557
|
${template.acceptanceSection}
|
|
409
558
|
|
|
410
|
-
|
|
559
|
+
${changeSectionHeading("design")}
|
|
411
560
|
- Approach:
|
|
412
561
|
- ${template.approach}
|
|
413
562
|
- Boundaries:
|
|
414
|
-
-
|
|
563
|
+
- ${placeholder}
|
|
415
564
|
- Affected areas:
|
|
416
565
|
- ${template.affectedArea1}
|
|
417
566
|
- ${template.affectedArea2}
|
|
418
567
|
- Constraints:
|
|
419
568
|
- ${template.constraints}
|
|
420
569
|
|
|
421
|
-
|
|
570
|
+
${changeSectionHeading("tasks")}
|
|
422
571
|
- [ ] ${template.task}
|
|
423
572
|
|
|
424
|
-
|
|
573
|
+
${changeSectionHeading("verify")}
|
|
425
574
|
- Automated:
|
|
426
|
-
- [ ] ${template.automatedVerify} \u2014 proves:
|
|
575
|
+
- [ ] ${template.automatedVerify} \u2014 proves: ${placeholder}
|
|
427
576
|
- Manual or environment:
|
|
428
577
|
- [ ] ${template.manualVerify}
|
|
429
578
|
- Coverage:
|
|
430
|
-
-
|
|
579
|
+
- ${placeholder}
|
|
431
580
|
|
|
432
|
-
|
|
581
|
+
${changeSectionHeading("blockers")}
|
|
433
582
|
- none
|
|
434
583
|
`;
|
|
435
584
|
}
|
|
436
585
|
function generateLiteChangeContent(name, proposalSummary, frontmatterKind) {
|
|
586
|
+
const placeholder = "<\u2026>";
|
|
437
587
|
return `---
|
|
438
588
|
kind: "${frontmatterKind}"
|
|
439
589
|
---
|
|
440
590
|
|
|
441
|
-
|
|
591
|
+
${renderDocumentTitle(CHANGE_DOCUMENT_SCHEMA, name)}
|
|
442
592
|
|
|
443
|
-
|
|
593
|
+
${changeSectionHeading("proposal")}
|
|
444
594
|
- Outcome: ${proposalSummary}
|
|
445
595
|
- Why:
|
|
446
|
-
-
|
|
596
|
+
- ${placeholder}
|
|
447
597
|
- Scope:
|
|
448
|
-
-
|
|
598
|
+
- ${placeholder}
|
|
449
599
|
- Non-goals:
|
|
450
600
|
- none
|
|
451
601
|
|
|
452
|
-
|
|
602
|
+
${changeSectionHeading("spec")}
|
|
453
603
|
### MODIFIED
|
|
454
|
-
- Requirement:
|
|
455
|
-
-
|
|
604
|
+
- Requirement: ${placeholder}
|
|
605
|
+
- ${placeholder}
|
|
456
606
|
|
|
457
607
|
### Acceptance
|
|
458
|
-
#### Scenario:
|
|
459
|
-
- GIVEN
|
|
460
|
-
- WHEN
|
|
461
|
-
- THEN
|
|
608
|
+
#### Scenario: ${placeholder}
|
|
609
|
+
- GIVEN ${placeholder}
|
|
610
|
+
- WHEN ${placeholder}
|
|
611
|
+
- THEN ${placeholder}
|
|
462
612
|
|
|
463
|
-
|
|
613
|
+
${changeSectionHeading("design")}
|
|
464
614
|
- Approach:
|
|
465
|
-
-
|
|
615
|
+
- ${placeholder}
|
|
466
616
|
- Boundaries:
|
|
467
|
-
-
|
|
617
|
+
- ${placeholder}
|
|
468
618
|
- Affected areas:
|
|
469
|
-
-
|
|
619
|
+
- ${placeholder}
|
|
470
620
|
- Constraints:
|
|
471
|
-
-
|
|
621
|
+
- ${placeholder}
|
|
472
622
|
|
|
473
|
-
|
|
474
|
-
- [ ]
|
|
623
|
+
${changeSectionHeading("tasks")}
|
|
624
|
+
- [ ] ${placeholder}
|
|
475
625
|
|
|
476
|
-
|
|
626
|
+
${changeSectionHeading("verify")}
|
|
477
627
|
- Automated:
|
|
478
|
-
- [ ]
|
|
628
|
+
- [ ] ${placeholder} \u2014 proves: ${placeholder}
|
|
479
629
|
- Manual or environment:
|
|
480
|
-
- [ ]
|
|
630
|
+
- [ ] ${placeholder}
|
|
481
631
|
- Coverage:
|
|
482
|
-
-
|
|
632
|
+
- ${placeholder}
|
|
483
633
|
|
|
484
|
-
|
|
634
|
+
${changeSectionHeading("blockers")}
|
|
485
635
|
- none
|
|
486
636
|
`;
|
|
487
637
|
}
|
|
488
638
|
function getChangeTemplateByKind(kind) {
|
|
489
|
-
const
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
affectedArea1: "<concrete file path, directory, module, or subsystem containing the defect>",
|
|
515
|
-
affectedArea2: "<secondary path, dependency, or test file if needed>",
|
|
516
|
-
constraints: "<regression, backward-compatibility, safety, or scope constraint that must hold>",
|
|
517
|
-
task: "<identify the root cause and apply the minimal fix>",
|
|
518
|
-
automatedVerify,
|
|
519
|
-
manualVerify: "<exact steps to reproduce the original issue and confirm it no longer occurs, or unavailable/not applicable: owner and reason>"
|
|
520
|
-
};
|
|
521
|
-
case "refactor":
|
|
522
|
-
return {
|
|
523
|
-
why: "<what maintainability, readability, or structural issue motivates this>",
|
|
524
|
-
scope: "<what code areas are being restructured>",
|
|
525
|
-
nonGoals: "<no behavior change is expected or intended>",
|
|
526
|
-
specSection: "<!-- Describe the desired structural outcome. Implementation notes belong in ## Design. -->\n### MODIFIED\n- Requirement: internal structure improvement\n - <desired structural outcome without observable behavior change>",
|
|
527
|
-
acceptanceSection: "#### Scenario: behavior is preserved after restructuring\n- GIVEN the existing codebase before the refactor\n- WHEN the refactored code runs against existing tests\n- THEN all existing tests pass unchanged",
|
|
528
|
-
approach: "<how the code will be restructured (rename, extract, move, etc.)>",
|
|
529
|
-
affectedArea1: "<concrete file path, directory, module, or subsystem being refactored>",
|
|
530
|
-
affectedArea2: "<secondary path, dependency, or test file if needed>",
|
|
531
|
-
constraints: "<observable behavior must remain unchanged; compatibility, performance, and safety constraints must still hold>",
|
|
532
|
-
task: "<restructure the code while keeping behavior identical>",
|
|
533
|
-
automatedVerify,
|
|
534
|
-
manualVerify: "<exact scenario to spot-check that behavior is unchanged after the refactor, or unavailable/not applicable: owner and reason>"
|
|
535
|
-
};
|
|
536
|
-
case "docs":
|
|
537
|
-
return {
|
|
538
|
-
why: "<why the documentation change matters>",
|
|
539
|
-
scope: "<which docs, readers, or workflows will be updated>",
|
|
540
|
-
nonGoals: "<what documentation behavior or audience is out of scope>",
|
|
541
|
-
specSection: "<!-- Describe what the reader should see or experience. Implementation notes belong in ## Design. -->\n### MODIFIED\n- Requirement: documentation accuracy\n - <what durable reader-facing behavior or explanation changes>",
|
|
542
|
-
acceptanceSection: "#### Scenario: reader follows the updated guidance\n- GIVEN the updated documentation\n- WHEN a reader follows the documented workflow\n- THEN the steps are accurate and sufficient",
|
|
543
|
-
approach: "<how the documentation will be updated and organized>",
|
|
544
|
-
affectedArea1: "<concrete doc path, directory, module doc, or documentation surface 1>",
|
|
545
|
-
affectedArea2: "<secondary doc path or reference file if needed>",
|
|
546
|
-
constraints: "<durable wording, consistency, scope, or audience constraint that must hold>",
|
|
547
|
-
task: "<update the target documentation and supporting references>",
|
|
548
|
-
automatedVerify,
|
|
549
|
-
manualVerify: "<exact reader or operator scenario to confirm the updated guidance is accurate, or unavailable/not applicable: owner and reason>"
|
|
550
|
-
};
|
|
551
|
-
case "research":
|
|
552
|
-
return {
|
|
553
|
-
why: "<what question or uncertainty this research resolves>",
|
|
554
|
-
scope: "<what system area, option set, or hypothesis is being examined>",
|
|
555
|
-
nonGoals: "<what implementation work is explicitly out of scope>",
|
|
556
|
-
specSection: "<!-- Describe what finding or decision must be captured. Implementation notes belong in ## Design. -->\n### ADDED\n- Requirement: research outcome recording\n - <what finding, option, or decision must be captured clearly>",
|
|
557
|
-
acceptanceSection: "#### Scenario: research question is resolved\n- GIVEN the current uncertainty or open question\n- WHEN the investigation is completed\n- THEN the result is recorded clearly enough to guide follow-up work",
|
|
558
|
-
approach: "<how the investigation will be performed and what evidence will be gathered>",
|
|
559
|
-
affectedArea1: "<concrete file path, directory, module, subsystem, or source under investigation>",
|
|
560
|
-
affectedArea2: "<secondary path, dependency, environment, or evidence source if needed>",
|
|
561
|
-
constraints: "<time, evidence, safety, or scope constraint for the investigation>",
|
|
562
|
-
task: "<gather evidence, record the recommendation or finding, and note any follow-up implementation work if needed>",
|
|
563
|
-
automatedVerify,
|
|
564
|
-
manualVerify: "<exact review of the findings to confirm they answer the original question and identify follow-up work if needed, or unavailable/not applicable: owner and reason>"
|
|
565
|
-
};
|
|
566
|
-
case "ops":
|
|
567
|
-
return {
|
|
568
|
-
why: "<why this operational or environment change matters>",
|
|
569
|
-
scope: "<what environment, workflow, or operational path changes>",
|
|
570
|
-
nonGoals: "<what product or feature behavior will not change>",
|
|
571
|
-
specSection: "<!-- Describe the reliable operational outcome. Implementation notes belong in ## Design. -->\n### MODIFIED\n- Requirement: operational behavior\n - <what reliable operational outcome should change or stay true>",
|
|
572
|
-
acceptanceSection: "#### Scenario: operational path succeeds\n- GIVEN the target environment or workflow\n- WHEN the operational change is applied\n- THEN the expected operational outcome is reliable",
|
|
573
|
-
approach: "<how the operational change will be applied safely>",
|
|
574
|
-
affectedArea1: "<concrete script, config path, workflow, environment path, or operational surface 1>",
|
|
575
|
-
affectedArea2: "<secondary script, config path, dependency, or environment surface if needed>",
|
|
576
|
-
constraints: "<rollback, safety, reliability, environment, or scope constraint that must hold>",
|
|
577
|
-
task: "<apply and verify the operational change>",
|
|
578
|
-
automatedVerify,
|
|
579
|
-
manualVerify: "<exact operator workflow or environment scenario to confirm the operational result, or unavailable/not applicable: owner and reason>"
|
|
580
|
-
};
|
|
581
|
-
default:
|
|
582
|
-
return {
|
|
583
|
-
why: "<why this change matters>",
|
|
584
|
-
scope: "<what this change will do>",
|
|
585
|
-
nonGoals: "<what this change will not do>",
|
|
586
|
-
specSection: "<!-- Describe observable behavior and requirements. Implementation notes belong in ## Design. -->\n### ADDED\n- Requirement: <new or updated behavior>\n - <verifiable requirement>",
|
|
587
|
-
acceptanceSection: "#### Scenario: <name>\n- GIVEN <context>\n- WHEN <action>\n- THEN <expected outcome>",
|
|
588
|
-
approach: "<how the change will be implemented>",
|
|
589
|
-
affectedArea1: "<concrete file path, directory, module, or subsystem 1>",
|
|
590
|
-
affectedArea2: "<concrete file path, directory, module, or subsystem 2 if needed>",
|
|
591
|
-
constraints: "<behavior, compatibility, performance, safety, or scope constraint that must hold>",
|
|
592
|
-
task: "<implement the core change in the affected areas>",
|
|
593
|
-
automatedVerify,
|
|
594
|
-
manualVerify: "<exact acceptance scenario, or unavailable/not applicable: owner and reason>"
|
|
595
|
-
};
|
|
596
|
-
}
|
|
639
|
+
const placeholder = "<\u2026>";
|
|
640
|
+
const delta = kind === "feature" || kind === "research" || kind === void 0 ? "ADDED" : "MODIFIED";
|
|
641
|
+
return {
|
|
642
|
+
why: placeholder,
|
|
643
|
+
scope: placeholder,
|
|
644
|
+
nonGoals: placeholder,
|
|
645
|
+
specSection: `<!-- ${placeholder} -->
|
|
646
|
+
### ${delta}
|
|
647
|
+
- Requirement: ${placeholder}
|
|
648
|
+
- ${placeholder}`,
|
|
649
|
+
acceptanceSection: `#### Scenario: ${placeholder}
|
|
650
|
+
- GIVEN ${placeholder}
|
|
651
|
+
- WHEN ${placeholder}
|
|
652
|
+
- THEN ${placeholder}`,
|
|
653
|
+
approach: placeholder,
|
|
654
|
+
affectedArea1: placeholder,
|
|
655
|
+
affectedArea2: placeholder,
|
|
656
|
+
constraints: placeholder,
|
|
657
|
+
task: placeholder,
|
|
658
|
+
automatedVerify: placeholder,
|
|
659
|
+
manualVerify: placeholder
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
function changeSectionHeading(sectionId) {
|
|
663
|
+
return renderDocumentSectionHeading(CHANGE_DOCUMENT_SCHEMA, sectionId);
|
|
597
664
|
}
|
|
598
665
|
function renderRspAgentsBlock() {
|
|
599
666
|
return `${RSP_AGENTS_BEGIN}
|
|
@@ -675,78 +742,73 @@ async function detectProjectName() {
|
|
|
675
742
|
return basename(process.cwd());
|
|
676
743
|
}
|
|
677
744
|
function generateDesignContent(projectName) {
|
|
745
|
+
const placeholder = "<\u2026>";
|
|
678
746
|
return `# Project Design: ${projectName}
|
|
679
747
|
|
|
680
748
|
## Purpose
|
|
681
|
-
-
|
|
682
|
-
- <who or what it serves>
|
|
749
|
+
- ${placeholder}
|
|
683
750
|
|
|
684
751
|
## Stable Facts
|
|
685
|
-
-
|
|
686
|
-
- <key architectural invariant>
|
|
752
|
+
- ${placeholder}
|
|
687
753
|
|
|
688
754
|
## Boundaries
|
|
689
755
|
- In scope:
|
|
690
|
-
-
|
|
756
|
+
- ${placeholder}
|
|
691
757
|
- Out of scope:
|
|
692
|
-
-
|
|
758
|
+
- ${placeholder}
|
|
693
759
|
|
|
694
760
|
## Structure
|
|
695
|
-
-
|
|
761
|
+
- ${placeholder}
|
|
696
762
|
|
|
697
763
|
## Constraints
|
|
698
|
-
-
|
|
764
|
+
- ${placeholder}
|
|
699
765
|
`;
|
|
700
766
|
}
|
|
701
767
|
function generateSpecContent(name) {
|
|
702
768
|
const title = toTitleCase(name);
|
|
769
|
+
const placeholder = "<\u2026>";
|
|
703
770
|
return `# ${title}
|
|
704
771
|
|
|
705
772
|
## Purpose
|
|
706
|
-
-
|
|
773
|
+
- ${placeholder}
|
|
707
774
|
|
|
708
775
|
## Stable Facts
|
|
709
|
-
-
|
|
776
|
+
- ${placeholder}
|
|
710
777
|
|
|
711
778
|
## Boundaries
|
|
712
779
|
- In scope:
|
|
713
|
-
-
|
|
780
|
+
- ${placeholder}
|
|
714
781
|
- Out of scope:
|
|
715
|
-
-
|
|
782
|
+
- ${placeholder}
|
|
716
783
|
|
|
717
784
|
## Constraints
|
|
718
|
-
-
|
|
785
|
+
- ${placeholder}
|
|
719
786
|
`;
|
|
720
787
|
}
|
|
721
788
|
function toTitleCase(value) {
|
|
722
789
|
return value.split(/[-/]/g).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
|
|
723
790
|
}
|
|
724
|
-
function extractSection(content, heading) {
|
|
725
|
-
const lines = content.split("\n");
|
|
726
|
-
const start = lines.findIndex((line) => line.trim() === `## ${heading}`);
|
|
727
|
-
if (start === -1)
|
|
728
|
-
return "";
|
|
729
|
-
const body = [];
|
|
730
|
-
for (let index = start + 1; index < lines.length; index++) {
|
|
731
|
-
const line = lines[index];
|
|
732
|
-
if (line.startsWith("## "))
|
|
733
|
-
break;
|
|
734
|
-
body.push(line);
|
|
735
|
-
}
|
|
736
|
-
return body.join("\n").trim();
|
|
737
|
-
}
|
|
738
791
|
function extractBlockerLines(content) {
|
|
739
|
-
const
|
|
740
|
-
return
|
|
792
|
+
const document = parseRspDocument(content, CHANGE_DOCUMENT_SCHEMA);
|
|
793
|
+
return normalizeBlockerBody(getDocumentSectionBody(document, "blockers"));
|
|
794
|
+
}
|
|
795
|
+
function hasMeaningfulBlockerBody(body) {
|
|
796
|
+
return hasMeaningfulBlockerLines(normalizeBlockerBody(body));
|
|
797
|
+
}
|
|
798
|
+
function normalizeBlockerBody(body) {
|
|
799
|
+
return body.replace(/<!--[\s\S]*?-->/g, "").split("\n").map((line) => line.trim()).filter(Boolean);
|
|
741
800
|
}
|
|
742
801
|
function hasMeaningfulBlockers(content) {
|
|
743
|
-
|
|
802
|
+
return hasMeaningfulBlockerLines(extractBlockerLines(content));
|
|
803
|
+
}
|
|
804
|
+
function hasMeaningfulBlockerLines(lines) {
|
|
744
805
|
if (lines.length === 0)
|
|
745
806
|
return false;
|
|
746
807
|
return lines.some((line) => !/^[-*]\s*(?:none)?$/i.test(line) && !/^none$/i.test(line));
|
|
747
808
|
}
|
|
748
809
|
function detectDeltaSections(content) {
|
|
749
|
-
const
|
|
810
|
+
const document = parseRspDocument(content, CHANGE_DOCUMENT_SCHEMA);
|
|
811
|
+
const body = getDocumentSectionBody(document, "spec");
|
|
750
812
|
return {
|
|
751
813
|
added: /^###\s*ADDED/im.test(body),
|
|
752
814
|
modified: /^###\s*MODIFIED/im.test(body),
|
|
@@ -755,8 +817,9 @@ function detectDeltaSections(content) {
|
|
|
755
817
|
}
|
|
756
818
|
function collectArchiveReadiness(content, options = {}) {
|
|
757
819
|
const warnings = [];
|
|
758
|
-
const
|
|
759
|
-
const
|
|
820
|
+
const document = parseRspDocument(content, CHANGE_DOCUMENT_SCHEMA);
|
|
821
|
+
const tasksSection = getDocumentSectionBody(document, "tasks");
|
|
822
|
+
const verifySection = getDocumentSectionBody(document, "verify");
|
|
760
823
|
const taskTodos = getOpenCheckboxes(tasksSection);
|
|
761
824
|
if (taskTodos.length > 0)
|
|
762
825
|
warnings.push(`${taskTodos.length} task item(s) still incomplete`);
|
|
@@ -835,7 +898,7 @@ var ARCHIVES_DIR = join4(RSP_DIR, "archives");
|
|
|
835
898
|
var LOCK_PATH = join4(RSP_DIR, ".lock");
|
|
836
899
|
var CONFIG_PATH = join4(RSP_DIR, "config.yaml");
|
|
837
900
|
var VALID_KINDS = ["feature", "fix", "refactor", "docs", "ops", "research"];
|
|
838
|
-
var DEFAULT_REQUIRED_SECTIONS =
|
|
901
|
+
var DEFAULT_REQUIRED_SECTIONS = getCanonicalSectionHeadings(CHANGE_DOCUMENT_SCHEMA);
|
|
839
902
|
var DEFAULT_MANAGE_POLICY = { activation: "explicit", closeout: "local" };
|
|
840
903
|
var FAILED_CLOSED_MANAGE_POLICY = { activation: "explicit", closeout: "manual" };
|
|
841
904
|
var MANAGE_ACTIVATIONS = ["explicit", "auto"];
|
|
@@ -865,6 +928,7 @@ async function inspectRspConfig() {
|
|
|
865
928
|
const decisions = parsed.decisions && typeof parsed.decisions === "object" && !Array.isArray(parsed.decisions) ? parsed.decisions : void 0;
|
|
866
929
|
const decisionPath = decisions?.path;
|
|
867
930
|
const manageValue = parsed.manage && typeof parsed.manage === "object" && !Array.isArray(parsed.manage) ? parsed.manage : void 0;
|
|
931
|
+
const languageValue = parsed.language && typeof parsed.language === "object" && !Array.isArray(parsed.language) ? parsed.language : void 0;
|
|
868
932
|
const activation = MANAGE_ACTIVATIONS.includes(manageValue?.activation) ? manageValue?.activation : void 0;
|
|
869
933
|
const closeout = MANAGE_CLOSEOUTS.includes(manageValue?.closeout) ? manageValue?.closeout : void 0;
|
|
870
934
|
const kinds = Array.isArray(parsed.kinds) && parsed.kinds.every((value) => typeof value === "string" && value.trim() !== "") ? parsed.kinds.map((value) => value.trim()) : void 0;
|
|
@@ -873,7 +937,8 @@ async function inspectRspConfig() {
|
|
|
873
937
|
config: {
|
|
874
938
|
kinds,
|
|
875
939
|
decisions: decisionRecordsIssue === null && validateDecisionRecordsPath(decisionPath) === null ? { path: normalizeDecisionRecordsPath(String(decisionPath)) } : void 0,
|
|
876
|
-
manage: manageValue ? { activation, closeout } : void 0
|
|
940
|
+
manage: manageValue ? { activation, closeout } : void 0,
|
|
941
|
+
language: parseLanguageConfig(languageValue)
|
|
877
942
|
},
|
|
878
943
|
issues
|
|
879
944
|
};
|
|
@@ -882,7 +947,7 @@ async function inspectRspConfig() {
|
|
|
882
947
|
}
|
|
883
948
|
function validateRspConfig(parsed) {
|
|
884
949
|
const issues = [];
|
|
885
|
-
const supported = /* @__PURE__ */ new Set(["kinds", "decisions", "manage"]);
|
|
950
|
+
const supported = /* @__PURE__ */ new Set(["kinds", "decisions", "manage", "language"]);
|
|
886
951
|
for (const key of Object.keys(parsed)) {
|
|
887
952
|
if (key === "required_sections") {
|
|
888
953
|
issues.push('config.yaml field "required_sections" is no longer supported');
|
|
@@ -921,6 +986,24 @@ function validateRspConfig(parsed) {
|
|
|
921
986
|
issues.push('config.yaml field "manage.closeout" must be one of: manual, lifecycle, local');
|
|
922
987
|
}
|
|
923
988
|
}
|
|
989
|
+
if ("language" in parsed) {
|
|
990
|
+
if (!parsed.language || typeof parsed.language !== "object" || Array.isArray(parsed.language)) {
|
|
991
|
+
issues.push('config.yaml field "language" must be a YAML mapping');
|
|
992
|
+
} else {
|
|
993
|
+
const language = parsed.language;
|
|
994
|
+
const supportedLanguageFields = /* @__PURE__ */ new Set(["default", "artifacts", "commit"]);
|
|
995
|
+
for (const key of Object.keys(language)) {
|
|
996
|
+
if (!supportedLanguageFields.has(key))
|
|
997
|
+
issues.push(`config.yaml field "language.${key}" is not supported`);
|
|
998
|
+
}
|
|
999
|
+
if (!("default" in language))
|
|
1000
|
+
issues.push('config.yaml field "language.default" is required when language is configured');
|
|
1001
|
+
for (const key of supportedLanguageFields) {
|
|
1002
|
+
if (key in language && canonicalizeLanguageTag(language[key]) === null)
|
|
1003
|
+
issues.push(`config.yaml field "language.${key}" must be a non-empty valid BCP 47 language tag`);
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
924
1007
|
return issues;
|
|
925
1008
|
}
|
|
926
1009
|
function clearConfigCache() {
|
|
@@ -940,6 +1023,37 @@ function resolveManagePolicy(config, options = {}) {
|
|
|
940
1023
|
closeout: config.manage?.closeout ?? DEFAULT_MANAGE_POLICY.closeout
|
|
941
1024
|
};
|
|
942
1025
|
}
|
|
1026
|
+
function resolveLanguagePolicy(config, options = {}) {
|
|
1027
|
+
if (options.configValid === false || !config.language)
|
|
1028
|
+
return { artifacts: null, commit: null };
|
|
1029
|
+
return {
|
|
1030
|
+
artifacts: config.language.artifacts ?? config.language.default,
|
|
1031
|
+
commit: config.language.commit ?? config.language.default
|
|
1032
|
+
};
|
|
1033
|
+
}
|
|
1034
|
+
function parseLanguageConfig(value) {
|
|
1035
|
+
if (!value)
|
|
1036
|
+
return void 0;
|
|
1037
|
+
const defaultLanguage = canonicalizeLanguageTag(value.default);
|
|
1038
|
+
if (defaultLanguage === null)
|
|
1039
|
+
return void 0;
|
|
1040
|
+
const artifacts = canonicalizeLanguageTag(value.artifacts);
|
|
1041
|
+
const commit = canonicalizeLanguageTag(value.commit);
|
|
1042
|
+
return {
|
|
1043
|
+
default: defaultLanguage,
|
|
1044
|
+
artifacts: artifacts ?? void 0,
|
|
1045
|
+
commit: commit ?? void 0
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
function canonicalizeLanguageTag(value) {
|
|
1049
|
+
if (typeof value !== "string" || value.trim() === "")
|
|
1050
|
+
return null;
|
|
1051
|
+
try {
|
|
1052
|
+
return Intl.getCanonicalLocales(value.trim())[0] ?? null;
|
|
1053
|
+
} catch {
|
|
1054
|
+
return null;
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
943
1057
|
async function getVersion() {
|
|
944
1058
|
const { version } = JSON.parse(await readFile2(join4(PKG_ROOT, "package.json"), "utf-8"));
|
|
945
1059
|
return version;
|
|
@@ -975,6 +1089,19 @@ export {
|
|
|
975
1089
|
resolveDecisionRecordsPath,
|
|
976
1090
|
ensureDecisionRecordsDirectory,
|
|
977
1091
|
validateDecisionRecordsFilesystemPath,
|
|
1092
|
+
CHANGE_DOCUMENT_SCHEMA,
|
|
1093
|
+
GROUP_BRIEF_DOCUMENT_SCHEMA,
|
|
1094
|
+
DocumentSectionCardinalityError,
|
|
1095
|
+
parseRspDocument,
|
|
1096
|
+
getDocumentTitles,
|
|
1097
|
+
getDocumentTitle,
|
|
1098
|
+
getDocumentSections,
|
|
1099
|
+
getDocumentSectionBody,
|
|
1100
|
+
getDocumentSectionDefinitionByHeading,
|
|
1101
|
+
getCanonicalSectionHeadings,
|
|
1102
|
+
renderDocumentSectionHeading,
|
|
1103
|
+
renderDocumentTitle,
|
|
1104
|
+
appendDocumentSectionItem,
|
|
978
1105
|
toErrorMessage,
|
|
979
1106
|
emitJson,
|
|
980
1107
|
recordRuntimeDiagnostic,
|
|
@@ -992,8 +1119,8 @@ export {
|
|
|
992
1119
|
detectProjectName,
|
|
993
1120
|
generateDesignContent,
|
|
994
1121
|
generateSpecContent,
|
|
995
|
-
extractSection,
|
|
996
1122
|
extractBlockerLines,
|
|
1123
|
+
hasMeaningfulBlockerBody,
|
|
997
1124
|
hasMeaningfulBlockers,
|
|
998
1125
|
detectDeltaSections,
|
|
999
1126
|
collectArchiveReadiness,
|
|
@@ -1018,5 +1145,6 @@ export {
|
|
|
1018
1145
|
resolveKinds,
|
|
1019
1146
|
resolveRequiredSections,
|
|
1020
1147
|
resolveManagePolicy,
|
|
1148
|
+
resolveLanguagePolicy,
|
|
1021
1149
|
getVersion
|
|
1022
1150
|
};
|