@oevery/rsp 3.1.0-beta.3 → 3.1.0-beta.5
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 +28 -16
- package/README.zh-CN.md +20 -13
- package/dist/{chunk-NX55NIC3.mjs → chunk-3X2V6IEW.mjs} +1 -1
- package/dist/{chunk-OTJFL6XH.mjs → chunk-UVYEXKYS.mjs} +358 -221
- package/dist/{chunk-7KKAUBKN.mjs → chunk-ZKZVWJQ7.mjs} +687 -421
- package/dist/cli.mjs +343 -88
- package/dist/{entry-WZDVKDUM.mjs → entry-BKEJDNOQ.mjs} +2 -2
- package/dist/{entry-X23GPCVS.mjs → entry-ETZGIFKP.mjs} +2 -2
- package/package.json +1 -1
- package/rules/rsp-rules.md +8 -5
- package/skills/rsp/SKILL.md +8 -18
- package/skills/rsp/references/groups-dependencies.md +1 -0
- package/skills/rsp/references/managed-routing.md +12 -23
- package/skills/rsp/references/release-operations.md +9 -0
- package/skills/rsp/references/reopen-recovery.md +9 -0
- package/skills/rsp-commit/SKILL.md +4 -2
- package/skills/rsp-implement/SKILL.md +5 -5
- package/skills/rsp-manage/SKILL.md +17 -7
- package/skills/rsp-release-docs/references/convention-discovery.md +2 -2
- package/skills/rsp-shape/SKILL.md +2 -0
- package/skills/rsp-shape/references/external-issue-input.md +9 -0
|
@@ -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,85 +470,79 @@ 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;
|
|
475
|
+
const issueFrontmatter = renderIssueFrontmatter(options.issues ?? []);
|
|
318
476
|
if (name === "project-setup") {
|
|
319
477
|
return `---
|
|
320
478
|
kind: ops
|
|
321
|
-
---
|
|
479
|
+
${issueFrontmatter}---
|
|
322
480
|
|
|
323
|
-
|
|
481
|
+
${renderDocumentTitle(CHANGE_DOCUMENT_SCHEMA, "project-setup")}
|
|
324
482
|
|
|
325
|
-
|
|
483
|
+
${changeSectionHeading("proposal")}
|
|
326
484
|
- Outcome: ${proposalSummary}
|
|
327
485
|
- Why:
|
|
328
|
-
-
|
|
486
|
+
- ${placeholder}
|
|
329
487
|
- 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
|
|
488
|
+
- ${placeholder}
|
|
333
489
|
- Non-goals:
|
|
334
|
-
-
|
|
490
|
+
- ${placeholder}
|
|
335
491
|
|
|
336
|
-
|
|
492
|
+
${changeSectionHeading("spec")}
|
|
337
493
|
### 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
|
|
494
|
+
- Requirement: ${placeholder}
|
|
495
|
+
- ${placeholder}
|
|
345
496
|
|
|
346
497
|
### 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
|
|
498
|
+
#### Scenario: ${placeholder}
|
|
499
|
+
- GIVEN ${placeholder}
|
|
500
|
+
- WHEN ${placeholder}
|
|
501
|
+
- THEN ${placeholder}
|
|
352
502
|
|
|
353
|
-
|
|
503
|
+
${changeSectionHeading("design")}
|
|
354
504
|
- Approach:
|
|
355
|
-
-
|
|
505
|
+
- ${placeholder}
|
|
356
506
|
- Boundaries:
|
|
357
|
-
-
|
|
507
|
+
- ${placeholder}
|
|
358
508
|
- Affected areas:
|
|
359
509
|
- .rsp/specs/design.md
|
|
360
|
-
-
|
|
510
|
+
- CONTEXT.md
|
|
511
|
+
- AGENTS.md
|
|
361
512
|
- Durable outcome targets:
|
|
362
|
-
- Current facts:
|
|
363
|
-
- Lasting rationale:
|
|
513
|
+
- Current facts: ${placeholder}
|
|
514
|
+
- Lasting rationale: ${placeholder}
|
|
364
515
|
- Constraints:
|
|
365
|
-
-
|
|
516
|
+
- ${placeholder}
|
|
366
517
|
|
|
367
|
-
|
|
368
|
-
- [ ]
|
|
369
|
-
- [ ]
|
|
370
|
-
- [ ]
|
|
371
|
-
- [ ] Add stable project-owned context, instructions, or validation steps only where the Host Project convention requires them
|
|
518
|
+
${changeSectionHeading("tasks")}
|
|
519
|
+
- [ ] .rsp/specs/design.md: ${placeholder}
|
|
520
|
+
- [ ] CONTEXT.md: ${placeholder}
|
|
521
|
+
- [ ] AGENTS.md: ${placeholder}
|
|
372
522
|
|
|
373
|
-
|
|
523
|
+
${changeSectionHeading("verify")}
|
|
374
524
|
- Automated:
|
|
375
|
-
- [ ]
|
|
525
|
+
- [ ] rsp doctor \u2014 proves: ${placeholder}
|
|
376
526
|
- Manual or environment:
|
|
377
|
-
- [ ]
|
|
527
|
+
- [ ] ${placeholder}
|
|
378
528
|
- Coverage:
|
|
379
|
-
-
|
|
529
|
+
- ${placeholder}
|
|
380
530
|
|
|
381
|
-
|
|
531
|
+
${changeSectionHeading("blockers")}
|
|
382
532
|
- none
|
|
383
533
|
`;
|
|
384
534
|
}
|
|
385
535
|
const frontmatterKind = kind ?? "<choose: feature | fix | refactor | docs | ops | research>";
|
|
386
536
|
if (options.lite)
|
|
387
|
-
return generateLiteChangeContent(name, proposalSummary, frontmatterKind);
|
|
537
|
+
return generateLiteChangeContent(name, proposalSummary, frontmatterKind, issueFrontmatter);
|
|
388
538
|
const template = getChangeTemplateByKind(kind);
|
|
389
539
|
return `---
|
|
390
540
|
kind: "${frontmatterKind}"
|
|
391
|
-
---
|
|
541
|
+
${issueFrontmatter}---
|
|
392
542
|
|
|
393
|
-
|
|
543
|
+
${renderDocumentTitle(CHANGE_DOCUMENT_SCHEMA, name)}
|
|
394
544
|
|
|
395
|
-
|
|
545
|
+
${changeSectionHeading("proposal")}
|
|
396
546
|
- Outcome: ${proposalSummary}
|
|
397
547
|
- Why:
|
|
398
548
|
- ${template.why}
|
|
@@ -401,199 +551,125 @@ kind: "${frontmatterKind}"
|
|
|
401
551
|
- Non-goals:
|
|
402
552
|
- ${template.nonGoals}
|
|
403
553
|
|
|
404
|
-
|
|
554
|
+
${changeSectionHeading("spec")}
|
|
405
555
|
${template.specSection}
|
|
406
556
|
|
|
407
557
|
### Acceptance
|
|
408
558
|
${template.acceptanceSection}
|
|
409
559
|
|
|
410
|
-
|
|
560
|
+
${changeSectionHeading("design")}
|
|
411
561
|
- Approach:
|
|
412
562
|
- ${template.approach}
|
|
413
563
|
- Boundaries:
|
|
414
|
-
-
|
|
564
|
+
- ${placeholder}
|
|
415
565
|
- Affected areas:
|
|
416
566
|
- ${template.affectedArea1}
|
|
417
567
|
- ${template.affectedArea2}
|
|
418
568
|
- Constraints:
|
|
419
569
|
- ${template.constraints}
|
|
420
570
|
|
|
421
|
-
|
|
571
|
+
${changeSectionHeading("tasks")}
|
|
422
572
|
- [ ] ${template.task}
|
|
423
573
|
|
|
424
|
-
|
|
574
|
+
${changeSectionHeading("verify")}
|
|
425
575
|
- Automated:
|
|
426
|
-
- [ ] ${template.automatedVerify} \u2014 proves:
|
|
576
|
+
- [ ] ${template.automatedVerify} \u2014 proves: ${placeholder}
|
|
427
577
|
- Manual or environment:
|
|
428
578
|
- [ ] ${template.manualVerify}
|
|
429
579
|
- Coverage:
|
|
430
|
-
-
|
|
580
|
+
- ${placeholder}
|
|
431
581
|
|
|
432
|
-
|
|
582
|
+
${changeSectionHeading("blockers")}
|
|
433
583
|
- none
|
|
434
584
|
`;
|
|
435
585
|
}
|
|
436
|
-
function generateLiteChangeContent(name, proposalSummary, frontmatterKind) {
|
|
586
|
+
function generateLiteChangeContent(name, proposalSummary, frontmatterKind, issueFrontmatter) {
|
|
587
|
+
const placeholder = "<\u2026>";
|
|
437
588
|
return `---
|
|
438
589
|
kind: "${frontmatterKind}"
|
|
439
|
-
---
|
|
590
|
+
${issueFrontmatter}---
|
|
440
591
|
|
|
441
|
-
|
|
592
|
+
${renderDocumentTitle(CHANGE_DOCUMENT_SCHEMA, name)}
|
|
442
593
|
|
|
443
|
-
|
|
594
|
+
${changeSectionHeading("proposal")}
|
|
444
595
|
- Outcome: ${proposalSummary}
|
|
445
596
|
- Why:
|
|
446
|
-
-
|
|
597
|
+
- ${placeholder}
|
|
447
598
|
- Scope:
|
|
448
|
-
-
|
|
599
|
+
- ${placeholder}
|
|
449
600
|
- Non-goals:
|
|
450
601
|
- none
|
|
451
602
|
|
|
452
|
-
|
|
603
|
+
${changeSectionHeading("spec")}
|
|
453
604
|
### MODIFIED
|
|
454
|
-
- Requirement:
|
|
455
|
-
-
|
|
605
|
+
- Requirement: ${placeholder}
|
|
606
|
+
- ${placeholder}
|
|
456
607
|
|
|
457
608
|
### Acceptance
|
|
458
|
-
#### Scenario:
|
|
459
|
-
- GIVEN
|
|
460
|
-
- WHEN
|
|
461
|
-
- THEN
|
|
609
|
+
#### Scenario: ${placeholder}
|
|
610
|
+
- GIVEN ${placeholder}
|
|
611
|
+
- WHEN ${placeholder}
|
|
612
|
+
- THEN ${placeholder}
|
|
462
613
|
|
|
463
|
-
|
|
614
|
+
${changeSectionHeading("design")}
|
|
464
615
|
- Approach:
|
|
465
|
-
-
|
|
616
|
+
- ${placeholder}
|
|
466
617
|
- Boundaries:
|
|
467
|
-
-
|
|
618
|
+
- ${placeholder}
|
|
468
619
|
- Affected areas:
|
|
469
|
-
-
|
|
620
|
+
- ${placeholder}
|
|
470
621
|
- Constraints:
|
|
471
|
-
-
|
|
622
|
+
- ${placeholder}
|
|
472
623
|
|
|
473
|
-
|
|
474
|
-
- [ ]
|
|
624
|
+
${changeSectionHeading("tasks")}
|
|
625
|
+
- [ ] ${placeholder}
|
|
475
626
|
|
|
476
|
-
|
|
627
|
+
${changeSectionHeading("verify")}
|
|
477
628
|
- Automated:
|
|
478
|
-
- [ ]
|
|
629
|
+
- [ ] ${placeholder} \u2014 proves: ${placeholder}
|
|
479
630
|
- Manual or environment:
|
|
480
|
-
- [ ]
|
|
631
|
+
- [ ] ${placeholder}
|
|
481
632
|
- Coverage:
|
|
482
|
-
-
|
|
633
|
+
- ${placeholder}
|
|
483
634
|
|
|
484
|
-
|
|
635
|
+
${changeSectionHeading("blockers")}
|
|
485
636
|
- none
|
|
486
637
|
`;
|
|
487
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
|
+
}
|
|
488
647
|
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
|
-
}
|
|
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: `<!-- ${placeholder} -->
|
|
655
|
+
### ${delta}
|
|
656
|
+
- Requirement: ${placeholder}
|
|
657
|
+
- ${placeholder}`,
|
|
658
|
+
acceptanceSection: `#### Scenario: ${placeholder}
|
|
659
|
+
- GIVEN ${placeholder}
|
|
660
|
+
- WHEN ${placeholder}
|
|
661
|
+
- THEN ${placeholder}`,
|
|
662
|
+
approach: placeholder,
|
|
663
|
+
affectedArea1: placeholder,
|
|
664
|
+
affectedArea2: placeholder,
|
|
665
|
+
constraints: placeholder,
|
|
666
|
+
task: placeholder,
|
|
667
|
+
automatedVerify: placeholder,
|
|
668
|
+
manualVerify: placeholder
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
function changeSectionHeading(sectionId) {
|
|
672
|
+
return renderDocumentSectionHeading(CHANGE_DOCUMENT_SCHEMA, sectionId);
|
|
597
673
|
}
|
|
598
674
|
function renderRspAgentsBlock() {
|
|
599
675
|
return `${RSP_AGENTS_BEGIN}
|
|
@@ -675,78 +751,73 @@ async function detectProjectName() {
|
|
|
675
751
|
return basename(process.cwd());
|
|
676
752
|
}
|
|
677
753
|
function generateDesignContent(projectName) {
|
|
754
|
+
const placeholder = "<\u2026>";
|
|
678
755
|
return `# Project Design: ${projectName}
|
|
679
756
|
|
|
680
757
|
## Purpose
|
|
681
|
-
-
|
|
682
|
-
- <who or what it serves>
|
|
758
|
+
- ${placeholder}
|
|
683
759
|
|
|
684
760
|
## Stable Facts
|
|
685
|
-
-
|
|
686
|
-
- <key architectural invariant>
|
|
761
|
+
- ${placeholder}
|
|
687
762
|
|
|
688
763
|
## Boundaries
|
|
689
764
|
- In scope:
|
|
690
|
-
-
|
|
765
|
+
- ${placeholder}
|
|
691
766
|
- Out of scope:
|
|
692
|
-
-
|
|
767
|
+
- ${placeholder}
|
|
693
768
|
|
|
694
769
|
## Structure
|
|
695
|
-
-
|
|
770
|
+
- ${placeholder}
|
|
696
771
|
|
|
697
772
|
## Constraints
|
|
698
|
-
-
|
|
773
|
+
- ${placeholder}
|
|
699
774
|
`;
|
|
700
775
|
}
|
|
701
776
|
function generateSpecContent(name) {
|
|
702
777
|
const title = toTitleCase(name);
|
|
778
|
+
const placeholder = "<\u2026>";
|
|
703
779
|
return `# ${title}
|
|
704
780
|
|
|
705
781
|
## Purpose
|
|
706
|
-
-
|
|
782
|
+
- ${placeholder}
|
|
707
783
|
|
|
708
784
|
## Stable Facts
|
|
709
|
-
-
|
|
785
|
+
- ${placeholder}
|
|
710
786
|
|
|
711
787
|
## Boundaries
|
|
712
788
|
- In scope:
|
|
713
|
-
-
|
|
789
|
+
- ${placeholder}
|
|
714
790
|
- Out of scope:
|
|
715
|
-
-
|
|
791
|
+
- ${placeholder}
|
|
716
792
|
|
|
717
793
|
## Constraints
|
|
718
|
-
-
|
|
794
|
+
- ${placeholder}
|
|
719
795
|
`;
|
|
720
796
|
}
|
|
721
797
|
function toTitleCase(value) {
|
|
722
798
|
return value.split(/[-/]/g).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
|
|
723
799
|
}
|
|
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
800
|
function extractBlockerLines(content) {
|
|
739
|
-
const
|
|
740
|
-
return
|
|
801
|
+
const document = parseRspDocument(content, CHANGE_DOCUMENT_SCHEMA);
|
|
802
|
+
return normalizeBlockerBody(getDocumentSectionBody(document, "blockers"));
|
|
803
|
+
}
|
|
804
|
+
function hasMeaningfulBlockerBody(body) {
|
|
805
|
+
return hasMeaningfulBlockerLines(normalizeBlockerBody(body));
|
|
806
|
+
}
|
|
807
|
+
function normalizeBlockerBody(body) {
|
|
808
|
+
return body.replace(/<!--[\s\S]*?-->/g, "").split("\n").map((line) => line.trim()).filter(Boolean);
|
|
741
809
|
}
|
|
742
810
|
function hasMeaningfulBlockers(content) {
|
|
743
|
-
|
|
811
|
+
return hasMeaningfulBlockerLines(extractBlockerLines(content));
|
|
812
|
+
}
|
|
813
|
+
function hasMeaningfulBlockerLines(lines) {
|
|
744
814
|
if (lines.length === 0)
|
|
745
815
|
return false;
|
|
746
816
|
return lines.some((line) => !/^[-*]\s*(?:none)?$/i.test(line) && !/^none$/i.test(line));
|
|
747
817
|
}
|
|
748
818
|
function detectDeltaSections(content) {
|
|
749
|
-
const
|
|
819
|
+
const document = parseRspDocument(content, CHANGE_DOCUMENT_SCHEMA);
|
|
820
|
+
const body = getDocumentSectionBody(document, "spec");
|
|
750
821
|
return {
|
|
751
822
|
added: /^###\s*ADDED/im.test(body),
|
|
752
823
|
modified: /^###\s*MODIFIED/im.test(body),
|
|
@@ -755,8 +826,9 @@ function detectDeltaSections(content) {
|
|
|
755
826
|
}
|
|
756
827
|
function collectArchiveReadiness(content, options = {}) {
|
|
757
828
|
const warnings = [];
|
|
758
|
-
const
|
|
759
|
-
const
|
|
829
|
+
const document = parseRspDocument(content, CHANGE_DOCUMENT_SCHEMA);
|
|
830
|
+
const tasksSection = getDocumentSectionBody(document, "tasks");
|
|
831
|
+
const verifySection = getDocumentSectionBody(document, "verify");
|
|
760
832
|
const taskTodos = getOpenCheckboxes(tasksSection);
|
|
761
833
|
if (taskTodos.length > 0)
|
|
762
834
|
warnings.push(`${taskTodos.length} task item(s) still incomplete`);
|
|
@@ -835,7 +907,7 @@ var ARCHIVES_DIR = join4(RSP_DIR, "archives");
|
|
|
835
907
|
var LOCK_PATH = join4(RSP_DIR, ".lock");
|
|
836
908
|
var CONFIG_PATH = join4(RSP_DIR, "config.yaml");
|
|
837
909
|
var VALID_KINDS = ["feature", "fix", "refactor", "docs", "ops", "research"];
|
|
838
|
-
var DEFAULT_REQUIRED_SECTIONS =
|
|
910
|
+
var DEFAULT_REQUIRED_SECTIONS = getCanonicalSectionHeadings(CHANGE_DOCUMENT_SCHEMA);
|
|
839
911
|
var DEFAULT_MANAGE_POLICY = { activation: "explicit", closeout: "local" };
|
|
840
912
|
var FAILED_CLOSED_MANAGE_POLICY = { activation: "explicit", closeout: "manual" };
|
|
841
913
|
var MANAGE_ACTIVATIONS = ["explicit", "auto"];
|
|
@@ -865,6 +937,7 @@ async function inspectRspConfig() {
|
|
|
865
937
|
const decisions = parsed.decisions && typeof parsed.decisions === "object" && !Array.isArray(parsed.decisions) ? parsed.decisions : void 0;
|
|
866
938
|
const decisionPath = decisions?.path;
|
|
867
939
|
const manageValue = parsed.manage && typeof parsed.manage === "object" && !Array.isArray(parsed.manage) ? parsed.manage : void 0;
|
|
940
|
+
const languageValue = parsed.language && typeof parsed.language === "object" && !Array.isArray(parsed.language) ? parsed.language : void 0;
|
|
868
941
|
const activation = MANAGE_ACTIVATIONS.includes(manageValue?.activation) ? manageValue?.activation : void 0;
|
|
869
942
|
const closeout = MANAGE_CLOSEOUTS.includes(manageValue?.closeout) ? manageValue?.closeout : void 0;
|
|
870
943
|
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 +946,8 @@ async function inspectRspConfig() {
|
|
|
873
946
|
config: {
|
|
874
947
|
kinds,
|
|
875
948
|
decisions: decisionRecordsIssue === null && validateDecisionRecordsPath(decisionPath) === null ? { path: normalizeDecisionRecordsPath(String(decisionPath)) } : void 0,
|
|
876
|
-
manage: manageValue ? { activation, closeout } : void 0
|
|
949
|
+
manage: manageValue ? { activation, closeout } : void 0,
|
|
950
|
+
language: parseLanguageConfig(languageValue)
|
|
877
951
|
},
|
|
878
952
|
issues
|
|
879
953
|
};
|
|
@@ -882,7 +956,7 @@ async function inspectRspConfig() {
|
|
|
882
956
|
}
|
|
883
957
|
function validateRspConfig(parsed) {
|
|
884
958
|
const issues = [];
|
|
885
|
-
const supported = /* @__PURE__ */ new Set(["kinds", "decisions", "manage"]);
|
|
959
|
+
const supported = /* @__PURE__ */ new Set(["kinds", "decisions", "manage", "language"]);
|
|
886
960
|
for (const key of Object.keys(parsed)) {
|
|
887
961
|
if (key === "required_sections") {
|
|
888
962
|
issues.push('config.yaml field "required_sections" is no longer supported');
|
|
@@ -921,6 +995,24 @@ function validateRspConfig(parsed) {
|
|
|
921
995
|
issues.push('config.yaml field "manage.closeout" must be one of: manual, lifecycle, local');
|
|
922
996
|
}
|
|
923
997
|
}
|
|
998
|
+
if ("language" in parsed) {
|
|
999
|
+
if (!parsed.language || typeof parsed.language !== "object" || Array.isArray(parsed.language)) {
|
|
1000
|
+
issues.push('config.yaml field "language" must be a YAML mapping');
|
|
1001
|
+
} else {
|
|
1002
|
+
const language = parsed.language;
|
|
1003
|
+
const supportedLanguageFields = /* @__PURE__ */ new Set(["default", "artifacts", "commit"]);
|
|
1004
|
+
for (const key of Object.keys(language)) {
|
|
1005
|
+
if (!supportedLanguageFields.has(key))
|
|
1006
|
+
issues.push(`config.yaml field "language.${key}" is not supported`);
|
|
1007
|
+
}
|
|
1008
|
+
if (!("default" in language))
|
|
1009
|
+
issues.push('config.yaml field "language.default" is required when language is configured');
|
|
1010
|
+
for (const key of supportedLanguageFields) {
|
|
1011
|
+
if (key in language && canonicalizeLanguageTag(language[key]) === null)
|
|
1012
|
+
issues.push(`config.yaml field "language.${key}" must be a non-empty valid BCP 47 language tag`);
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
924
1016
|
return issues;
|
|
925
1017
|
}
|
|
926
1018
|
function clearConfigCache() {
|
|
@@ -940,6 +1032,37 @@ function resolveManagePolicy(config, options = {}) {
|
|
|
940
1032
|
closeout: config.manage?.closeout ?? DEFAULT_MANAGE_POLICY.closeout
|
|
941
1033
|
};
|
|
942
1034
|
}
|
|
1035
|
+
function resolveLanguagePolicy(config, options = {}) {
|
|
1036
|
+
if (options.configValid === false || !config.language)
|
|
1037
|
+
return { artifacts: null, commit: null };
|
|
1038
|
+
return {
|
|
1039
|
+
artifacts: config.language.artifacts ?? config.language.default,
|
|
1040
|
+
commit: config.language.commit ?? config.language.default
|
|
1041
|
+
};
|
|
1042
|
+
}
|
|
1043
|
+
function parseLanguageConfig(value) {
|
|
1044
|
+
if (!value)
|
|
1045
|
+
return void 0;
|
|
1046
|
+
const defaultLanguage = canonicalizeLanguageTag(value.default);
|
|
1047
|
+
if (defaultLanguage === null)
|
|
1048
|
+
return void 0;
|
|
1049
|
+
const artifacts = canonicalizeLanguageTag(value.artifacts);
|
|
1050
|
+
const commit = canonicalizeLanguageTag(value.commit);
|
|
1051
|
+
return {
|
|
1052
|
+
default: defaultLanguage,
|
|
1053
|
+
artifacts: artifacts ?? void 0,
|
|
1054
|
+
commit: commit ?? void 0
|
|
1055
|
+
};
|
|
1056
|
+
}
|
|
1057
|
+
function canonicalizeLanguageTag(value) {
|
|
1058
|
+
if (typeof value !== "string" || value.trim() === "")
|
|
1059
|
+
return null;
|
|
1060
|
+
try {
|
|
1061
|
+
return Intl.getCanonicalLocales(value.trim())[0] ?? null;
|
|
1062
|
+
} catch {
|
|
1063
|
+
return null;
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
943
1066
|
async function getVersion() {
|
|
944
1067
|
const { version } = JSON.parse(await readFile2(join4(PKG_ROOT, "package.json"), "utf-8"));
|
|
945
1068
|
return version;
|
|
@@ -975,6 +1098,19 @@ export {
|
|
|
975
1098
|
resolveDecisionRecordsPath,
|
|
976
1099
|
ensureDecisionRecordsDirectory,
|
|
977
1100
|
validateDecisionRecordsFilesystemPath,
|
|
1101
|
+
CHANGE_DOCUMENT_SCHEMA,
|
|
1102
|
+
GROUP_BRIEF_DOCUMENT_SCHEMA,
|
|
1103
|
+
DocumentSectionCardinalityError,
|
|
1104
|
+
parseRspDocument,
|
|
1105
|
+
getDocumentTitles,
|
|
1106
|
+
getDocumentTitle,
|
|
1107
|
+
getDocumentSections,
|
|
1108
|
+
getDocumentSectionBody,
|
|
1109
|
+
getDocumentSectionDefinitionByHeading,
|
|
1110
|
+
getCanonicalSectionHeadings,
|
|
1111
|
+
renderDocumentSectionHeading,
|
|
1112
|
+
renderDocumentTitle,
|
|
1113
|
+
appendDocumentSectionItem,
|
|
978
1114
|
toErrorMessage,
|
|
979
1115
|
emitJson,
|
|
980
1116
|
recordRuntimeDiagnostic,
|
|
@@ -992,8 +1128,8 @@ export {
|
|
|
992
1128
|
detectProjectName,
|
|
993
1129
|
generateDesignContent,
|
|
994
1130
|
generateSpecContent,
|
|
995
|
-
extractSection,
|
|
996
1131
|
extractBlockerLines,
|
|
1132
|
+
hasMeaningfulBlockerBody,
|
|
997
1133
|
hasMeaningfulBlockers,
|
|
998
1134
|
detectDeltaSections,
|
|
999
1135
|
collectArchiveReadiness,
|
|
@@ -1018,5 +1154,6 @@ export {
|
|
|
1018
1154
|
resolveKinds,
|
|
1019
1155
|
resolveRequiredSections,
|
|
1020
1156
|
resolveManagePolicy,
|
|
1157
|
+
resolveLanguagePolicy,
|
|
1021
1158
|
getVersion
|
|
1022
1159
|
};
|