placeholder-suppression completeness (release-0.4.7): audit mini-reqs arm + INTENT section join the one scaffold grammar

Micro-bundle, three items, zero new copy:
- A: twin module gains hasAuthoredNumberedItem (dot-or-paren + non-placeholder,
  the ONE authored-numbered-item grammar); compose hasAuthoredMiniReqs
  delegates (single-parse pin governs the parse; the twin fn is the line
  grammar); both scope-audit mini-reqs arms consume it — healing the audit's
  placeholder-blindness AND the dot/paren grammar divergence (a live
  audit-absent/review-present R3 case for '1)' items) in one predicate.
  Finding message unchanged.
- B: twin module gains isPlaceholderOnlyBlock (per-line; null/empty -> false);
  a placeholder-only Intent section is treated as absent at the source so the
  existing 'no intent recorded' degrade fires. No new string; UI + freeze
  inherit.
- C: prose/bullet-only mini-reqs suppression gets its test pin + doc-comment
  disclosure (reviewer-L1 ruling); zero behavior change, asserted on base
  behavior first.

Twin pairs byte-identical (parity-pinned via existing CLASSIFIER_FILES).
Tests: unit vectors (block + numbered-item grammars), audit-arm matrix,
cross-reader agreement incl. the paren-heal shown RED at base (disclosed
expected-change), intent degrade + byte-identity carve, suppression pins.

Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
mvschwarzandClaude Fable 5 committed 2026-07-11 01:40:03 -07:00
1 parent b8c1153559
commit 8757593fe2
8 files changed
+273 -13

No files matched your search

@@ -27,6 +27,41 @@ export function isScaffoldPlaceholderText(text: string): boolean {
return /^\[.*\]$/.test(text.trim());
}
/** release-0.4.7 placeholder-suppression completeness — true when `body`
* carries at least one numbered item (`1.` OR `1)` form) whose text is NOT a
* scaffold placeholder: the ONE authored-numbered-item grammar. Consumers:
* review compose `hasAuthoredMiniReqs` + BOTH scope-audit mini-reqs arms —
* one predicate heals the IF-3 dot/paren grammar divergence and the audit
* arm's placeholder-blindness together (arch MB-AR-1 primary shape).
* Prose-only or bullet-only bodies are deliberately NOT authored — the
* numbered tier is where approval starts (reviewer-L1 ruling 2026-07-11,
* intent-stage gate). `null` → false. */
export function hasAuthoredNumberedItem(body: string | null): boolean {
if (body === null) return false;
for (const line of body.split("\n")) {
const m = line.match(/^\s*\d+[.)]\s+(.+)$/);
if (m && !isScaffoldPlaceholderText(m[1]!.trim())) return true;
}
return false;
}
/** release-0.4.7 placeholder-suppression completeness — true when EVERY
* non-blank line of `block` is a scaffold placeholder: the section-level
* "nothing authored here" test. Per-LINE deliberately, not a whole-string
* trim — a two-line `[a]\n[b]` body is placeholder-only per line but would
* fail the single-line regex (the multi-line honest case, arch MB-AR-2).
* `null`/empty/blank-only → false: absence is its own state; callers keep
* their existing null handling. */
export function isPlaceholderOnlyBlock(block: string | null): boolean {
if (block === null) return false;
const lines = block
.split("\n")
.map((l) => l.trim())
.filter((l) => l.length > 0);
if (lines.length === 0) return false;
return lines.every((l) => isScaffoldPlaceholderText(l));
}
/** The generic acceptance triple scaffolded by
* `packages/cli/src/lib/scope-templates/slice-progress.md` — exact trimmed
* literals, sync-tested against the shipped template so constant/template
+7 -2
View File
@@ -1,6 +1,6 @@
import * as YAML from "yaml";
import { isMissionDotId, isSliceDotId } from "./dot-id.js";
import { isScaffoldPlaceholderText } from "./scaffold-placeholder.js";
import { isScaffoldPlaceholderText, hasAuthoredNumberedItem } from "./scaffold-placeholder.js";
export type RailStatus = "present" | "missing" | "malformed" | "readme-only";
export type FindingSeverity = "high" | "medium" | "low" | "info";
@@ -420,7 +420,12 @@ export function classifyScopeItem(input: ScopeAuditInput): ScopeAuditResult {
// at least one numbered list item; a heading over prose-only is
// malformed (no usable requirements projection).
const miniBody = h2Body(contractSource.content, "Mini-requirements");
const hasNumberedItem = miniBody !== null && /^\s*\d+\.\s+\S/m.test(miniBody);
// release-0.4.7 micro-bundle A: an AUTHORED numbered item — the twin
// module's ONE authored-numbered-item grammar, shared with review
// compose (heals the dot/paren grammar split AND the placeholder
// blindness in one predicate; the finding message below already reads
// honestly for both and stays unchanged).
const hasNumberedItem = hasAuthoredNumberedItem(miniBody);
if (!hasNumberedItem) {
findings.push({
kind: "mini_requirements_missing_or_malformed",
+21 -9
View File
@@ -49,7 +49,11 @@ import {
type VerdictTone,
type VerifyLineage,
} from "./types.js";
import { isScaffoldPlaceholderText } from "../scope/scaffold-placeholder.js";
import {
isScaffoldPlaceholderText,
hasAuthoredNumberedItem,
isPlaceholderOnlyBlock,
} from "../scope/scaffold-placeholder.js";
// --- Fixed, visible v1 thresholds (markdown-steered tuning is a named fast-follow) ---
export const IDLE_WITH_WORK_THRESHOLD_MIN = 30;
@@ -343,14 +347,16 @@ export function extractMiniReqs(prd: string | null): string | null {
* SINGLE-PARSE PIN (arch AR-2): the derivePhase `prdAuthored` signal AND the
* PLAN concise render decision MUST both derive from this ONE parse of the
* ONE extractMiniReqs extraction — a second mini-reqs grammar anywhere in
* this file would recreate the seam map's R3 divergence class intra-file. */
* this file would recreate the seam map's R3 divergence class intra-file.
*
* release-0.4.7 micro-bundle A: the LINE grammar now lives in the twin
* module's `hasAuthoredNumberedItem` (shared with both scope-audit mini-reqs
* arms — the IF-3 dot/paren heal, arch MB-AR-1); this function keeps the
* parse-side contract above and delegates the grammar. Prose/bullet-only is
* deliberately not-authored — reviewer-L1 ruling 2026-07-11, intent-stage
* gate: the numbered tier is where approval starts. */
export function hasAuthoredMiniReqs(miniReqs: string | null): boolean {
if (miniReqs === null) return false;
for (const line of miniReqs.split("\n")) {
const m = line.match(/^\s*\d+[.)]\s+(.+)$/);
if (m && !isScaffoldPlaceholderText(m[1]!.trim())) return true;
}
return false;
return hasAuthoredNumberedItem(miniReqs);
}
/** One promised deliverable from the D2 `## Proof contract` (§3.1): the item
@@ -1027,7 +1033,13 @@ export function composeSliceReview(inputs: SliceComposeInputs): ComposedSliceRev
const deliveredLock = lockFrom(inputs.approval.delivery);
const escaping = new Set<string>();
const intentText = extractSection(inputs.readme, "Intent");
// release-0.4.7 micro-bundle B: a placeholder-only Intent section (the
// shipped template scaffolds it as one fully-bracket-wrapped line) is
// treated as ABSENT at the source, so the EXISTING "no intent recorded"
// degrade fires — no new string, UI/freeze inherit (R7). Any authored line
// in the block keeps the section verbatim (byte-identity carve).
const intentTextRaw = extractSection(inputs.readme, "Intent");
const intentText = intentTextRaw !== null && !isPlaceholderOnlyBlock(intentTextRaw) ? intentTextRaw : null;
const intentMedia = sectionMedia(intentText, escaping);
const miniReqs = extractMiniReqs(inputs.prd);
const planMedia = dedupMedia([
@@ -27,6 +27,41 @@ export function isScaffoldPlaceholderText(text: string): boolean {
return /^\[.*\]$/.test(text.trim());
}
/** release-0.4.7 placeholder-suppression completeness — true when `body`
* carries at least one numbered item (`1.` OR `1)` form) whose text is NOT a
* scaffold placeholder: the ONE authored-numbered-item grammar. Consumers:
* review compose `hasAuthoredMiniReqs` + BOTH scope-audit mini-reqs arms —
* one predicate heals the IF-3 dot/paren grammar divergence and the audit
* arm's placeholder-blindness together (arch MB-AR-1 primary shape).
* Prose-only or bullet-only bodies are deliberately NOT authored — the
* numbered tier is where approval starts (reviewer-L1 ruling 2026-07-11,
* intent-stage gate). `null` → false. */
export function hasAuthoredNumberedItem(body: string | null): boolean {
if (body === null) return false;
for (const line of body.split("\n")) {
const m = line.match(/^\s*\d+[.)]\s+(.+)$/);
if (m && !isScaffoldPlaceholderText(m[1]!.trim())) return true;
}
return false;
}
/** release-0.4.7 placeholder-suppression completeness — true when EVERY
* non-blank line of `block` is a scaffold placeholder: the section-level
* "nothing authored here" test. Per-LINE deliberately, not a whole-string
* trim — a two-line `[a]\n[b]` body is placeholder-only per line but would
* fail the single-line regex (the multi-line honest case, arch MB-AR-2).
* `null`/empty/blank-only → false: absence is its own state; callers keep
* their existing null handling. */
export function isPlaceholderOnlyBlock(block: string | null): boolean {
if (block === null) return false;
const lines = block
.split("\n")
.map((l) => l.trim())
.filter((l) => l.length > 0);
if (lines.length === 0) return false;
return lines.every((l) => isScaffoldPlaceholderText(l));
}
/** The generic acceptance triple scaffolded by
* `packages/cli/src/lib/scope-templates/slice-progress.md` — exact trimmed
* literals, sync-tested against the shipped template so constant/template
@@ -1,6 +1,6 @@
import * as YAML from "yaml";
import { isMissionDotId, isSliceDotId } from "./dot-id.js";
import { isScaffoldPlaceholderText } from "./scaffold-placeholder.js";
import { isScaffoldPlaceholderText, hasAuthoredNumberedItem } from "./scaffold-placeholder.js";
export type RailStatus = "present" | "missing" | "malformed" | "readme-only";
export type FindingSeverity = "high" | "medium" | "low" | "info";
@@ -420,7 +420,12 @@ export function classifyScopeItem(input: ScopeAuditInput): ScopeAuditResult {
// at least one numbered list item; a heading over prose-only is
// malformed (no usable requirements projection).
const miniBody = h2Body(contractSource.content, "Mini-requirements");
const hasNumberedItem = miniBody !== null && /^\s*\d+\.\s+\S/m.test(miniBody);
// release-0.4.7 micro-bundle A: an AUTHORED numbered item — the twin
// module's ONE authored-numbered-item grammar, shared with review
// compose (heals the dot/paren grammar split AND the placeholder
// blindness in one predicate; the finding message below already reads
// honestly for both and stays unchanged).
const hasNumberedItem = hasAuthoredNumberedItem(miniBody);
if (!hasNumberedItem) {
findings.push({
kind: "mini_requirements_missing_or_malformed",
@@ -661,3 +661,59 @@ describe("T7 — byte-identity carve (compose half): authored slices project ide
expect(composeSliceReview(baseInputs({ artifacts: fullGate() })).phase).toBe("review");
});
});
// ---------------------------------------------------------------------------
// release-0.4.7 placeholder-suppression completeness — T-B1 (INTENT section:
// placeholder-only projects the existing honest degrade; authored intent
// byte-identical) + T-C compose leg (prose/bullet-only mini-reqs suppression
// pin — asserts shipped b8c11535 behavior, zero behavior change intended).
// ---------------------------------------------------------------------------
describe("T-B1 — INTENT placeholder-only projects the honest degrade (micro-bundle B)", () => {
it("pristine template README (bracket-wrapped Intent) → intent.text null + 'no intent recorded'", () => {
const r = composeSliceReview(baseInputs({ readme: pristine.readme, prd: pristine.prd, proofMd: pristine.proof }));
expect(r.intent.text).toBeNull();
expect(r.intent.degrade).toBe("no intent recorded");
});
it("two-line `[a]\\n[b]` placeholder Intent → degrade (per-line block grammar)", () => {
const readme = "---\ntitle: t\n---\n\n# F\n\n## Intent\n\n[a]\n[b]\n";
const r = composeSliceReview(baseInputs({ readme }));
expect(r.intent.text).toBeNull();
expect(r.intent.degrade).toBe("no intent recorded");
});
it("AUTHORED intent projects byte-identically (the carve — filter never eats a real intent)", () => {
const r = composeSliceReview(baseInputs());
expect(r.intent.text).toContain("The founder's exact words.");
expect(r.intent.degrade).toBeNull();
const mixed = composeSliceReview(baseInputs({ readme: baseInputs().readme!.replace("The founder's exact words.", "[looks-bracketed] but authored words follow") }));
expect(mixed.intent.text).toContain("[looks-bracketed] but authored words follow");
});
it("ABSENT Intent section keeps its own state (null → same degrade, unchanged contract)", () => {
const r = composeSliceReview(baseInputs({ readme: "---\nt: 1\n---\n\n# F\n\nno intent heading here\n" }));
expect(r.intent.text).toBeNull();
expect(r.intent.degrade).toBe("no intent recorded");
});
});
describe("T-C — prose/bullet-only mini-reqs suppression pin (reviewer-L1 ruling; shipped behavior)", () => {
it("prose-only mini-reqs → PLAN '— not planned yet' (concise text null)", () => {
const prd = "## Mini-requirements\n\nJust prose, no numbered tier.\n\n## Proof contract\n\n- [ ] real item\n";
const r = composeSliceReview(baseInputs({ prd }));
expect(r.plan.concise.text).toBeNull();
});
it("bullet-only mini-reqs → concise text null", () => {
const prd = "## Mini-requirements\n\n- bullet one\n- bullet two\n\n## Proof contract\n\n- [ ] real item\n";
const r = composeSliceReview(baseInputs({ prd }));
expect(r.plan.concise.text).toBeNull();
});
it("mixed prose + ONE authored numbered item → renders", () => {
const prd = "## Mini-requirements\n\nContext prose.\n\n1. One real outcome.\n\n## Proof contract\n\n- [ ] real item\n";
const r = composeSliceReview(baseInputs({ prd }));
expect(r.plan.concise.text).toContain("1. One real outcome.");
});
});
@@ -128,3 +128,50 @@ describe("T5 — shared-helper agreement across the three readers (the R3 pin, p
expect(extractProofContract(prd).map((i) => i.text)).toEqual(["phone journey video"]);
});
});
// ---------------------------------------------------------------------------
// release-0.4.7 placeholder-suppression completeness — T-A1 (audit mini-reqs
// arm learns the merged grammar) + T-A2 (the IF-3 paren-grammar HEAL:
// RED at base b8c11535 proves today's audit-absent/review-present divergence;
// green post-change proves audit joined compose's already-ratified grammar —
// an arch-framed expected-change disclosure, not a regression).
// ---------------------------------------------------------------------------
function auditFor(prd: string) {
return classifyScopeItem({
id: "OPR.T.96",
path: "/fixture/96-minireqs",
readmeFrontmatterRaw: "id: OPR.T.96",
progressFileExists: true,
readmeOnlyMarker: false,
isActiveRelease: true,
level: "slice",
readmeContent: "# 96\n\n## Intent\n\nauthored\n",
implementationPrdContent: prd,
});
}
describe("T-A1 — audit mini-reqs arm counts only AUTHORED numbered items", () => {
it("placeholder-only mini-reqs → mini_requirements finding FIRES (was: silent)", () => {
const audit = auditFor(tpl.prd); // pristine template: `1. [...]` placeholder + placeholder contract
expect(audit.findings.map((f) => f.kind)).toContain("mini_requirements_missing_or_malformed");
});
it("authored dot-form mini-reqs → no mini_requirements finding", () => {
const prd = tpl.prd.replace(/^1\. \[.*\]$/m, "1. One real observable outcome.");
const audit = auditFor(prd);
expect(audit.findings.map((f) => f.kind)).not.toContain("mini_requirements_missing_or_malformed");
});
});
describe("T-A2 — the IF-3 heal: `1)` paren-form authored items — audit joins compose's grammar", () => {
it("paren-form authored mini-reqs: audit finding ABSENT and compose reads authored (agreement)", async () => {
const prd = tpl.prd.replace(/^1\. \[.*\]$/m, "1) One real observable outcome.");
// Reader 1 — audit: no malformed finding (RED at base: dot-only regex misses `1)`).
const audit = auditFor(prd);
expect(audit.findings.map((f) => f.kind)).not.toContain("mini_requirements_missing_or_malformed");
// Reader 2 — compose: authored TRUE via the same shared grammar.
const { extractMiniReqs, hasAuthoredMiniReqs } = await import("../src/domain/review/compose.js");
expect(hasAuthoredMiniReqs(extractMiniReqs(prd))).toBe(true);
});
});
@@ -122,3 +122,68 @@ describe("T6 — GENERIC_SCAFFOLD_ACCEPTANCE stays in sync with the shipped slic
expect(checkboxTexts(body!)).toEqual([...GENERIC_SCAFFOLD_ACCEPTANCE]);
});
});
// ---------------------------------------------------------------------------
// release-0.4.7 placeholder-suppression completeness micro-bundle —
// T-B2 (isPlaceholderOnlyBlock unit vectors), T-A grammar
// (hasAuthoredNumberedItem unit vectors), T-C (prose/bullet-only suppression
// pin — asserts the arch-ruled, reviewer-L1 behavior).
// ---------------------------------------------------------------------------
import {
hasAuthoredNumberedItem,
isPlaceholderOnlyBlock,
} from "../src/domain/scope/scaffold-placeholder.js";
describe("T-B2 — isPlaceholderOnlyBlock (block-level 'nothing authored here')", () => {
it("null/empty → false (absence is its own state; callers keep null handling)", () => {
expect(isPlaceholderOnlyBlock(null)).toBe(false);
expect(isPlaceholderOnlyBlock("")).toBe(false);
expect(isPlaceholderOnlyBlock(" \n \n")).toBe(false);
});
it("single fully-bracket-wrapped line → true (the shipped template Intent scaffold)", () => {
expect(isPlaceholderOnlyBlock("[The recorded intent, verbatim — what was asked for and why.]")).toBe(true);
expect(isPlaceholderOnlyBlock("\n [padded placeholder] \n")).toBe(true);
});
it("multi-line per-LINE case: `[a]\\n[b]` → true (the whole-string trim would miss this)", () => {
expect(isPlaceholderOnlyBlock("[a]\n[b]")).toBe(true);
});
it("blank lines are ignored between placeholder lines", () => {
expect(isPlaceholderOnlyBlock("[a]\n\n[b]\n")).toBe(true);
});
it("ANY authored line makes the block authored (mixed → false)", () => {
expect(isPlaceholderOnlyBlock("[a]\nreal authored words")).toBe(false);
expect(isPlaceholderOnlyBlock("The founder's exact words.")).toBe(false);
});
});
describe("T-A grammar — hasAuthoredNumberedItem (the ONE authored-numbered-item grammar)", () => {
it("dot-form and paren-form authored items both count (`1.` / `1)`)", () => {
expect(hasAuthoredNumberedItem("1. Drawer opens from the right side.")).toBe(true);
expect(hasAuthoredNumberedItem("1) Drawer opens from the right side.")).toBe(true);
});
it("placeholder-only numbered item → false (template `1. [...]` scaffold)", () => {
expect(hasAuthoredNumberedItem("1. [The concise one-glance requirement tier.]")).toBe(false);
});
it("null → false", () => {
expect(hasAuthoredNumberedItem(null)).toBe(false);
});
it("T-C: prose-only body → false (reviewer-L1 ruling — deliberately not-authored)", () => {
expect(hasAuthoredNumberedItem("Some prose describing intent without structure.")).toBe(false);
});
it("T-C: bullet-only body → false (bullets are not the numbered requirement tier)", () => {
expect(hasAuthoredNumberedItem("- bullet item one\n- bullet item two")).toBe(false);
});
it("T-C: mixed prose + one authored numbered item → true", () => {
expect(hasAuthoredNumberedItem("Context prose first.\n\n1. One real observable outcome.")).toBe(true);
});
});