@christang/keel 5.20.0 → 5.39.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.
- package/README.md +28 -9
- package/assets/bootstrap/AGENTS.md +1 -1
- package/assets/openspec/schemas/keel-spec-driven/templates/tasks.md +4 -1
- package/bin/keel.js +127 -22
- package/package.json +1 -1
- package/plugins/keel/.claude-plugin/plugin.json +1 -1
- package/plugins/keel/.codex-plugin/plugin.json +1 -1
- package/plugins/keel/skills/keel-align-expectations/SKILL.md +4 -18
- package/plugins/keel/skills/keel-review-checklist/SKILL.md +1 -1
- package/scripts/install_to_repo.py +45 -2
- package/scripts/validate_plugin.py +3120 -204
- package/src/core/config.js +192 -26
- package/src/core/context.js +9 -0
- package/src/core/gates.js +257 -40
- package/src/core/guard.js +77 -16
- package/src/core/task-contract.js +85 -3
|
@@ -518,6 +518,67 @@ function collisionHint(repo, change, capability) {
|
|
|
518
518
|
);
|
|
519
519
|
}
|
|
520
520
|
|
|
521
|
+
// One phrasing, so an author who has read the over-segmented refusal recognizes
|
|
522
|
+
// the unresolved one. It used to be reachable only by writing too many
|
|
523
|
+
// segments, which withheld it from the reference people actually write wrong.
|
|
524
|
+
const COVERS_HIERARCHY =
|
|
525
|
+
"the hierarchy is capability / requirement, or capability / requirement "
|
|
526
|
+
+ "/ scenario";
|
|
527
|
+
|
|
528
|
+
// Say which segment failed. The candidate specs were opened by the caller and
|
|
529
|
+
// the name the author typed is very often a heading one level below where they
|
|
530
|
+
// put it — the shipped task template taught exactly that reference — so the
|
|
531
|
+
// refusal can name the requirement it belongs to instead of handing the
|
|
532
|
+
// reference back. Reporting what a spec contains is not heuristic matching: the
|
|
533
|
+
// reference still fails, and no near miss is resolved on the author's behalf.
|
|
534
|
+
function unresolvedDetail(repo, change, capability, name) {
|
|
535
|
+
const candidates = specCandidatePaths(repo, change, capability);
|
|
536
|
+
const existing = candidates.filter((specPath) => fs.existsSync(specPath));
|
|
537
|
+
if (existing.length === 0) {
|
|
538
|
+
const looked = candidates
|
|
539
|
+
.map((specPath) => path.relative(repo, specPath).replace(/\\/g, "/"))
|
|
540
|
+
.join(" and ");
|
|
541
|
+
return ` No spec declares capability ${capability}; ${looked} do not exist.`;
|
|
542
|
+
}
|
|
543
|
+
const parents = [];
|
|
544
|
+
for (const specPath of existing) {
|
|
545
|
+
const content = fs.readFileSync(specPath, "utf8");
|
|
546
|
+
for (const requirement of headingSections(
|
|
547
|
+
content,
|
|
548
|
+
/^### Requirement:\s*(.+?)\s*$/
|
|
549
|
+
)) {
|
|
550
|
+
const holdsName = headingSections(
|
|
551
|
+
requirement.content,
|
|
552
|
+
/^#### Scenario:\s*(.+?)\s*$/
|
|
553
|
+
).some((item) => item.title === name);
|
|
554
|
+
if (holdsName && !parents.includes(requirement.title)) {
|
|
555
|
+
parents.push(requirement.title);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
if (parents.length === 1) {
|
|
560
|
+
return (
|
|
561
|
+
` "${name}" is a Scenario of Requirement "${parents[0]}", not a `
|
|
562
|
+
+ `Requirement; ${COVERS_HIERARCHY}. Write it as: `
|
|
563
|
+
+ `${capability} / ${parents[0]} / ${name}.`
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
if (parents.length > 1) {
|
|
567
|
+
// No single reference corrects this one, so none is offered: sending the
|
|
568
|
+
// author to a reference that fails as ambiguous would cost them the round
|
|
569
|
+
// this diagnostic exists to save.
|
|
570
|
+
const named = parents.map((title) => `"${title}"`).join(", ");
|
|
571
|
+
return (
|
|
572
|
+
` "${name}" is a Scenario of more than one Requirement — ${named} — so `
|
|
573
|
+
+ `no single reference corrects it; ${COVERS_HIERARCHY}.`
|
|
574
|
+
);
|
|
575
|
+
}
|
|
576
|
+
return (
|
|
577
|
+
` Capability ${capability} declares no Requirement or Scenario named `
|
|
578
|
+
+ `"${name}"; ${COVERS_HIERARCHY}.`
|
|
579
|
+
);
|
|
580
|
+
}
|
|
581
|
+
|
|
521
582
|
function specAuthority(repo, change, reference) {
|
|
522
583
|
const parts = reference.split("/").map((part) => part.trim());
|
|
523
584
|
const [capability, requirementName, scenarioName] = parts;
|
|
@@ -534,9 +595,8 @@ function specAuthority(repo, change, reference) {
|
|
|
534
595
|
diagnostic: {
|
|
535
596
|
code: "unresolved-covers",
|
|
536
597
|
message:
|
|
537
|
-
`Covers reference has ${parts.length} segments;
|
|
538
|
-
+
|
|
539
|
-
+ `scenario: ${reference}.`
|
|
598
|
+
`Covers reference has ${parts.length} segments; `
|
|
599
|
+
+ `${COVERS_HIERARCHY}: ${reference}.`
|
|
540
600
|
+ collisionHint(repo, change, capability),
|
|
541
601
|
},
|
|
542
602
|
};
|
|
@@ -603,6 +663,7 @@ function specAuthority(repo, change, reference) {
|
|
|
603
663
|
code: "unresolved-covers",
|
|
604
664
|
message:
|
|
605
665
|
`Covers reference could not be resolved: ${reference}.`
|
|
666
|
+
+ unresolvedDetail(repo, change, capability, requirementName)
|
|
606
667
|
+ collisionHint(repo, change, capability),
|
|
607
668
|
},
|
|
608
669
|
};
|
|
@@ -632,6 +693,26 @@ function criticalAuthority(repo, change, reference) {
|
|
|
632
693
|
),
|
|
633
694
|
];
|
|
634
695
|
if (matches.length !== 1) {
|
|
696
|
+
// Zero matches is ambiguous: the identifier may never appear in design.md,
|
|
697
|
+
// or it may appear in some other shape (bulleted, bold) that the strict
|
|
698
|
+
// regex above does not accept. A whole-word scan tells those apart so the
|
|
699
|
+
// message sends the author to the actual defect — a shape fix, not a
|
|
700
|
+
// statement that already exists — instead of collapsing both into "Missing".
|
|
701
|
+
if (
|
|
702
|
+
matches.length === 0
|
|
703
|
+
&& new RegExp(`\\b${reference}\\b`).test(content)
|
|
704
|
+
) {
|
|
705
|
+
return {
|
|
706
|
+
diagnostic: {
|
|
707
|
+
code: "unresolved-covers",
|
|
708
|
+
message:
|
|
709
|
+
`Unparsed Covers critical statement: ${reference}. It appears in `
|
|
710
|
+
+ "design.md but not in the required shape — write it starting "
|
|
711
|
+
+ `the line as \`${reference} — one-line statement\` (no leading `
|
|
712
|
+
+ "`-`, `**`, or other decoration) so it can be resolved.",
|
|
713
|
+
},
|
|
714
|
+
};
|
|
715
|
+
}
|
|
635
716
|
return {
|
|
636
717
|
diagnostic: {
|
|
637
718
|
code: matches.length > 1 ? "ambiguous-covers" : "unresolved-covers",
|
|
@@ -1049,4 +1130,5 @@ module.exports = {
|
|
|
1049
1130
|
loadTaskContract,
|
|
1050
1131
|
parseTasks,
|
|
1051
1132
|
taskStartContractProblems,
|
|
1133
|
+
unfilledToken,
|
|
1052
1134
|
};
|