@christang/keel 5.1.2 → 5.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.
@@ -5,7 +5,12 @@
5
5
  const fs = require("fs");
6
6
  const path = require("path");
7
7
  const { spawnSync } = require("child_process");
8
- const { compileTaskContract, field, parseTasks } = require("./task-contract");
8
+ const {
9
+ ACCEPTED_REVIEW_STATUSES,
10
+ compileTaskContract,
11
+ field,
12
+ parseTasks,
13
+ } = require("./task-contract");
9
14
 
10
15
  const NEXT_ACTIONS = new Set([
11
16
  "discuss",
@@ -68,8 +73,10 @@ function taskHasCompletionEvidence(record, contract) {
68
73
  (match) => match[1]
69
74
  )
70
75
  );
71
- const reviewPassed =
72
- /^\s+- Status:\s*(?:pass|passed|complete|completed|ok)\s*$/im.test(evidence);
76
+ const reviewPassed = new RegExp(
77
+ `^\\s*-\\s*Status:\\s*(?:${ACCEPTED_REVIEW_STATUSES.join("|")})\\s*$`,
78
+ "im"
79
+ ).test(evidence);
73
80
  return (
74
81
  commandIds.length > 0
75
82
  && commandIds.every((commandId) => evidenceIds.has(commandId))
package/src/core/gates.js CHANGED
@@ -6,10 +6,12 @@ const fs = require("fs");
6
6
  const path = require("path");
7
7
  const { spawnSync } = require("child_process");
8
8
  const {
9
+ ACCEPTED_REVIEW_STATUSES,
9
10
  RED_GREEN_VERIFICATION_STRATEGIES,
10
11
  compileTaskContract,
11
12
  field,
12
13
  isConcrete,
14
+ isPassingReviewStatus,
13
15
  parseTasks,
14
16
  } = require("./task-contract");
15
17
  const { startGuard } = require("./guard");
@@ -376,16 +378,29 @@ function completionChecks(repo, task, contract = null) {
376
378
  : [["Findings", reviewFields.Findings]]
377
379
  ),
378
380
  ];
379
- const reviewPassed = /^(?:pass|passed|complete|completed|ok)$/i.test(
380
- reviewFields.Status
381
- );
381
+ const reviewPassed = isPassingReviewStatus(reviewFields.Status);
382
382
  const reviewProblems = [];
383
383
  if (reviewMissing.length > 0 || !reviewPassed) {
384
+ const details = [];
385
+ if (!reviewPassed) {
386
+ const got = isConcrete(reviewFields.Status)
387
+ ? ` (got "${String(reviewFields.Status).trim()}")`
388
+ : "";
389
+ details.push(
390
+ `Status must be one of ${ACCEPTED_REVIEW_STATUSES.join(", ")}${got}`
391
+ );
392
+ }
393
+ const otherMissing = reviewMissing.filter(([name]) => name !== "Status");
394
+ if (otherMissing.length > 0) {
395
+ details.push(
396
+ "these Review fields need concrete Evidence: "
397
+ + otherMissing.map(([name]) => name).join(", ")
398
+ );
399
+ }
384
400
  reviewProblems.push(
385
401
  problem(
386
402
  "semantic-review",
387
- "Current-agent Review requires passing Status, Acceptance check, "
388
- + "Scope check, and Findings."
403
+ `Current-agent Review is incomplete ${details.join("; ")}.`
389
404
  )
390
405
  );
391
406
  } else if (
@@ -395,8 +410,10 @@ function completionChecks(repo, task, contract = null) {
395
410
  problems.push(
396
411
  problem(
397
412
  "finding-owner",
398
- "Review findings require an OpenSpec, archive-evidence, or explicit "
399
- + "discard owner; HANDOFF is not an owner."
413
+ "Review Findings must be `none` or carry a durable owner — a "
414
+ + "`Discard reason:`/`Discard rationale:` prefix, a `keel/archive/…` "
415
+ + "path, or an existing `openspec/changes/…` artifact; "
416
+ + "`keel/HANDOFF.md` is not an owner."
400
417
  )
401
418
  );
402
419
  }
@@ -441,7 +458,9 @@ function expectationProblems(content, tasks) {
441
458
  return [
442
459
  problem(
443
460
  "expectation-coverage",
444
- "tasks.md requires an Expectation Coverage section."
461
+ "tasks.md requires a `## Expectation Coverage` section: one "
462
+ + "`- E<n>: <expectation> Covered by: <task ids>` line per "
463
+ + "expectation, or `- None.`."
445
464
  ),
446
465
  ];
447
466
  }
@@ -459,7 +478,9 @@ function expectationProblems(content, tasks) {
459
478
  return [
460
479
  problem(
461
480
  "expectation-coverage",
462
- "Expectation Coverage must declare each E<n> closure or `None`."
481
+ "Expectation Coverage must declare each `E<n>` closure "
482
+ + "`- E<n>: <expectation> Covered by: <task ids>`, a `Durable owner:` "
483
+ + "path, or a `Discard reason:` — or `- None.`."
463
484
  ),
464
485
  ];
465
486
  }
@@ -97,6 +97,25 @@ const RED_GREEN_VERIFICATION_STRATEGIES = new Set([
97
97
  "regression-first",
98
98
  ]);
99
99
 
100
+ // Single source of truth for the accepted completion Review `Status`
101
+ // vocabulary. Consumed by both the completion gate (src/core/gates.js) and the
102
+ // context "already reviewed" probe (src/core/context.js) so the two never
103
+ // diverge.
104
+ const ACCEPTED_REVIEW_STATUSES = [
105
+ "pass",
106
+ "passed",
107
+ "complete",
108
+ "completed",
109
+ "ok",
110
+ "done",
111
+ ];
112
+
113
+ function isPassingReviewStatus(value) {
114
+ return ACCEPTED_REVIEW_STATUSES.includes(
115
+ String(value == null ? "" : value).trim().toLowerCase()
116
+ );
117
+ }
118
+
100
119
  function verification(task) {
101
120
  const compact = fieldValues(task, "Verify");
102
121
  const strategyEntry = compact.find((entry) => /^Strategy:\s*/i.test(entry));
@@ -725,11 +744,13 @@ function loadTaskContract(repo, change, taskId) {
725
744
  }
726
745
 
727
746
  module.exports = {
747
+ ACCEPTED_REVIEW_STATUSES,
728
748
  RED_GREEN_VERIFICATION_STRATEGIES,
729
749
  SUPPORTED_VERIFICATION_STRATEGIES,
730
750
  compileTaskContract,
731
751
  field,
732
752
  isConcrete,
753
+ isPassingReviewStatus,
733
754
  loadTaskContract,
734
755
  parseTasks,
735
756
  taskStartContractProblems,