shapeup-sdlc 3.17.1 → 3.17.2

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "shapeup-sdlc-plugin",
3
3
  "displayName": "ShapeUp SDLC Plugin",
4
- "version": "3.17.1",
4
+ "version": "3.17.2",
5
5
  "description": "Shape Up SDLC harness for Claude Code: shaping, intake, orient, scope-mapping, building (T0-verified, sandboxed, scope-contracted), evaluation and QA skills orchestrated by a tech-lead.",
6
6
  "author": {
7
7
  "name": "Liberty Nguyen",
package/AGENTS.md CHANGED
@@ -62,7 +62,7 @@ Everything discovered funnels into `.shapeup/<slug>/discovery/ledger.md` (Orient
62
62
  - **Ledger = single source of truth** — every discovery flow writes only its own section.
63
63
  - **QA is a level-up, not a gate** — `--no-qa` skips it; circuit breaker outranks the Hunter.
64
64
  - **Role separation** — Evaluator grades, task-executor fixes, QA discovers.
65
- - **The requirements matrix is a projection, never a verdict** — `REQ → AC → criterion → verdict` is derived from files for one named run (the registry, the board's `covers:` clauses, the run's verdict rows, the T0 citations), never narrated and never passed in. `covers:` is the authoritative join: a criterion anchored to a requirement no AC covers is printed for reconciliation and counted as nothing. L4 reads one line off it, GATE H's census takes the clauses with no evidence, `REPORT.md` freezes the table — and none of that blocks a ship. A clause with no PASS evidence is a fact the baseline comparison weighs, not a veto.
65
+ - **The requirements matrix is a projection, never a verdict** — `REQ → AC → criterion → verdict` is derived from files for one named run (the registry, the board's `covers:` clauses, the run's verdict rows, the T0 citations), never narrated and never passed in. `covers:` is the authoritative join: a criterion anchored to a requirement no AC covers is printed for reconciliation and counted as nothing. A verdict that anchors no criterion at all, beside a board whose ACs carry `covers:`, is sent back to the judge once. L4 reads one line off it, GATE H's census takes the clauses with no evidence, `REPORT.md` freezes the table — and none of that blocks a ship. A clause with no PASS evidence is a fact the baseline comparison weighs, not a veto.
66
66
  - **Hill phase is mechanical ✦** — derived only from T0/T1 artifacts, never self-reported, and a T0-green from a round whose build gate ⚙ is red moves no dot (a green fixture in a round the feature did not build is evidence about the fixture); the evaluator cites a T0 artifact it re-hashes itself, from the list its order carries. A scoped verdict citing none is refused: its round stays open and is evaluated again, never advanced.
67
67
  - **Envelope port (v1.0)** — every dispatch is WorkOrder in / WorkResult out; shared state has exactly one writer (the ingest step); malformed envelopes are hook-denied. Workers: stateless, craft-only, pipeline-blind.
68
68
 
@@ -36,6 +36,8 @@ import { join, resolve, resolve as resolvePath, sep } from "node:path";
36
36
  import { createHash } from "node:crypto";
37
37
  import { runArgs } from "../lib/argv.mjs";
38
38
  import { resultsDir, scopesDir, readRunId, verdictsDir, specDir } from "../lib/paths.mjs";
39
+ import { readBoard } from "../compile.mjs";
40
+ import { coveringAcs } from "./requirements.mjs";
39
41
 
40
42
  /** Longest `reason` reported. A deviation is prose written by a worker and can run to paragraphs. */
41
43
  const REASON_MAX = 400;
@@ -253,6 +255,33 @@ export function coverageProblem(cwd, slug, verdict) {
253
255
  `each row is its own criterion, its id in the criterion or its traces_to; ungraded: ${shown}`;
254
256
  }
255
257
 
258
+ /**
259
+ * Whether a verdict anchors any criterion to a requirement when the board says which ones it could.
260
+ *
261
+ * `traces_to` is copied from the `(covers: REQ-…)` clauses of the acceptance criteria a criterion
262
+ * grades, and the requirements matrix is joined along it. Two judges on the same spec and the same
263
+ * instruction differed: one anchored every row and the matrix read 5/5, the next anchored none and it
264
+ * read 0/5 over the same passes. Held only to the all-empty case: which requirement a criterion
265
+ * traces to is the judge's reading, but none at all, beside a board that covers some, is a copy
266
+ * step skipped.
267
+ *
268
+ * @param {string} cwd - Project root.
269
+ * @param {string} slug - Feature slug.
270
+ * @param {object} verdict - The WorkResult's `verdict`.
271
+ * @returns {(string|null)} A reason phrased for the judge, or null.
272
+ */
273
+ export function tracesProblem(cwd, slug, verdict) {
274
+ if (verdict?.overall !== "PASS" && verdict?.overall !== "FAIL") return null;
275
+ const criteria = Array.isArray(verdict.criteria) ? verdict.criteria : [];
276
+ if (!criteria.length) return null;
277
+ if (criteria.some((c) => Array.isArray(c?.traces_to) && c.traces_to.length)) return null;
278
+ let covered = 0;
279
+ try { covered = coveringAcs(readBoard(cwd, slug)).size; } catch { return null; }
280
+ if (!covered) return null;
281
+ return `the verdict anchors none of its ${criteria.length} criteria to a requirement while the board's acceptance ` +
282
+ `criteria cover ${covered} — copy each graded AC's (covers: REQ-…) clause into that criterion's traces_to`;
283
+ }
284
+
256
285
  export function citationProblem(cwd, slug, verdict, { round = null } = {}) {
257
286
  if (verdict?.overall !== "PASS" && verdict?.overall !== "FAIL") return null;
258
287
  if (!isScoped(cwd, slug)) return null;
@@ -297,7 +326,7 @@ export function evalVerdict(cwd, slug, round) {
297
326
  ? `the evaluator returned ${status || "no status"}: ${first}`
298
327
  : `status ${status || "unknown"} with no PASS/FAIL verdict`), status);
299
328
  }
300
- const problem = verdictProblem(v) || coverageProblem(cwd, slug, v) || citationProblem(cwd, slug, v, { round });
329
+ const problem = verdictProblem(v) || coverageProblem(cwd, slug, v) || tracesProblem(cwd, slug, v) || citationProblem(cwd, slug, v, { round });
301
330
  if (problem) return unfit(problem, status, overall);
302
331
  return {
303
332
  found: true,
@@ -37,7 +37,7 @@ import { fileURLToPath } from "node:url";
37
37
  import { validate } from "../verify/envelope.mjs";
38
38
  import { runArgs } from "../lib/argv.mjs";
39
39
  import { tasksDir, localRoot, dispatchReceipts, legLedger, readRunId, qaDir } from "../lib/paths.mjs";
40
- import { citationProblem, coverageProblem, verdictProblem } from "../probe/eval.mjs";
40
+ import { citationProblem, coverageProblem, tracesProblem, verdictProblem } from "../probe/eval.mjs";
41
41
  import { huntProblem } from "../probe/hunt.mjs";
42
42
 
43
43
  const HERE = dirname(fileURLToPath(import.meta.url));
@@ -702,7 +702,7 @@ export async function cli(rawArgv) {
702
702
  const evalSlug = String(result.order_id).split("/")[0];
703
703
  const evalRound = Number((String(result.order_id).match(/-r(\d+)$/) || [])[1]) || null;
704
704
  const problem = verdictProblem(result.verdict) || coverageProblem(cwd, evalSlug, result.verdict)
705
- || citationProblem(cwd, evalSlug, result.verdict, { round: evalRound });
705
+ || tracesProblem(cwd, evalSlug, result.verdict) || citationProblem(cwd, evalSlug, result.verdict, { round: evalRound });
706
706
  if (problem) {
707
707
  console.error(`ingest-result: result refused — ${problem}.`);
708
708
  console.error(` The round stays open: re-dispatch the evaluator against its order, which lists`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shapeup-sdlc",
3
- "version": "3.17.1",
3
+ "version": "3.17.2",
4
4
  "description": "Shape Up for coding agents \u2014 with gates the agent can't talk its way past. Harness for Claude Code.",
5
5
  "bin": {
6
6
  "shapeup-sdlc": "bin/init.mjs"
@@ -188,7 +188,7 @@ clause yields no anchor — leave the array empty rather than guessing, and neve
188
188
  supply one. This changes nothing you grade: the anchor is a navigation path, never a grading input,
189
189
  and a criterion passes or fails on its evidence exactly as before. It matters downstream because
190
190
  the requirement matrix at GATE L4 and the census at GATE H are projected from these anchors; a
191
- verdict that drops them grades the build and says nothing about what the pitch asked for.
191
+ verdict that drops them grades the build and says nothing about what the pitch asked for. Ingest refuses a verdict that anchors no criterion at all while the board's ACs carry `covers:` clauses, and you are sent back once.
192
192
 
193
193
  **Every FAIL criterion's `evidence` MUST carry a `file:line` locator** — schema-enforced, not
194
194
  advice: the envelope is validated against `work-result.schema.json` at ingest and a locatorless