@lifeaitools/rdc-skills 0.36.1 → 0.37.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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rdc",
3
- "version": "0.36.1",
3
+ "version": "0.37.0",
4
4
  "description": "RDC typed-agent dispatch skill suite for Claude Code — plan, build, review, overnight unattended builds with work-item tracking and TDD enforcement.",
5
5
  "author": {
6
6
  "name": "LIFEAI",
package/CHANGELOG.md CHANGED
@@ -7,6 +7,38 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ---
9
9
 
10
+ ## v0.37.0 — review gates run the deterministic validators, in 1–2 minutes
11
+
12
+ ### Changed
13
+ - The code-review step in `rdc:build` (9b), `rdc:fixit` (5.5), `rdc:review`
14
+ (8b), `rdc:release` and `rdc:overnight` now runs one deterministic gate,
15
+ `guides/review-gate.md`:
16
+ - `rdc-solid-score --diff <base>`;
17
+ - the repo linter on changed files (`--write`, then check);
18
+ - `tsc`;
19
+ - the touched package's tests;
20
+ - the UI spec when a route changed.
21
+
22
+ On the PRT launch build it took 9.3 s over 33 changed code files, where each
23
+ open-ended `pr-review-toolkit:code-reviewer` pass had taken 4–10 minutes. The
24
+ reviewer agent remains, but only for a named question the validators cannot
25
+ answer.
26
+ - `rdc-clean-code-score` is documented as advisory. It has no `--diff`, and in a
27
+ monorepo it needs `--project-root <package>`; its default dead-export scope ran
28
+ out of 4 GB of heap on regen-root.
29
+
30
+ ### Fixed
31
+ - `rdc-solid-score --diff` exited 2 on any diff that added a file. Git's
32
+ "exists on disk, but not in <ref>" wording was not recognized as absent at the
33
+ ref; it now is. A bad ref or a path-form mismatch is still rethrown.
34
+ - `rdc-solid-score --diff` exited 2 with ENOENT on any diff that deleted a file.
35
+ Deletions are now split out with `--diff-filter=D` and reported in a new
36
+ `removedFiles` output field.
37
+ - The README File Structure omitted `convo` (shipped in 0.36.0), which failed
38
+ `npm run test:acceptance`.
39
+
40
+ ---
41
+
10
42
  ## v0.35.11 — Design Review is explicit opt-in
11
43
 
12
44
  ### Changed
package/README.md CHANGED
@@ -321,6 +321,7 @@ skills/
321
321
  co-develop/SKILL.md (peer-aware Claude/Codex co-development)
322
322
  collab/SKILL.md (bidirectional claude.ai ↔ Claude Code relay)
323
323
  convert/SKILL.md (Office/Markdown conversion)
324
+ convo/SKILL.md (attributed discussion that converges to a decision)
324
325
  deploy/SKILL.md (Coolify ops: deploy, new, diagnose, audit)
325
326
  design/SKILL.md (RDC/Studio design, Palette Library, Rampa CLI)
326
327
  edit/SKILL.md (local website editor launcher)
@@ -8,4 +8,8 @@ description: rdc:compare-compliance <path> --diff <ref> — gate a change agains
8
8
  Use Skill tool with skill: "solid-validator", passing the path and a
9
9
  required `--diff <ref>` as args. Read `regressions` and `boundaryViolations`
10
10
  from the output — either non-empty means the change made compliance worse
11
- than the base ref, not merely "still imperfect."
11
+ than the base ref, not merely "still imperfect." `removedFiles` lists what the
12
+ change deleted; those are reported, never scored.
13
+
14
+ This is step 1 of the review gate. For the whole gate (lint, tsc, tests and UI
15
+ spec as well), follow `guides/review-gate.md`.
@@ -7,4 +7,6 @@ description: rdc:quick-check <path> — fast mechanical pass, SOLID score and bo
7
7
 
8
8
  Use Skill tool with skill: "solid-validator", passing the path as args.
9
9
  This is the mechanical-only subset — no judgment dispatch, meant to run in
10
- seconds during active editing. Use `full-analysis` before a merge.
10
+ seconds during active editing. Before a merge, run the review gate in
11
+ `guides/review-gate.md`, which takes one to two minutes. `full-analysis` is a
12
+ deeper advisory pass, not the merge gate.
package/git-sha.json CHANGED
@@ -1,3 +1,3 @@
1
1
  {
2
- "sha": "a5325ff86c953a4f64b3deef96551975d530f528"
2
+ "sha": "15c0b76b2fa48b6ab335478041940337a84da588"
3
3
  }
@@ -0,0 +1,133 @@
1
+ # Review Gate — deterministic validators on the diff
2
+
3
+ > The code-review gate used by `rdc:build` (step 9b), `rdc:fixit` (5.5),
4
+ > `rdc:review` (8b), `rdc:release` and `rdc:overnight`. It runs the specific
5
+ > validators over the changed files and finishes in one to two minutes.
6
+ >
7
+ > Approved: direct operator instruction — "we need to tune our code reviews to
8
+ > be specific -- if you ran the validator for solid and lint etc... it should
9
+ > take 1-2 minutes". 2026-09-21, Dave, in session.
10
+
11
+ ## Why this replaced the open-ended reviewer
12
+
13
+ The previous default dispatched one `pr-review-toolkit:code-reviewer` agent to
14
+ read the whole diff. On the PRT launch build (2026-09-21) four such passes took
15
+ 4 to 10 minutes and roughly 200–300k tokens each. Each one was a new judgment
16
+ call, so two runs could not be compared.
17
+
18
+ The gate below took **9.3 seconds** over the same build's 33 changed code files.
19
+ It found two things that had shipped:
20
+
21
+ - a SOLID regression: `investors/page.tsx` went from 96.3 to 85, on OCP;
22
+ - 45 lint diagnostics in 14 touched files. Most were import order. Three were
23
+ accessibility defects: two buttons with no `type`, and an SVG with no title.
24
+
25
+ A number that two runs agree on can block a merge. An opinion cannot.
26
+
27
+ ## The gate
28
+
29
+ Run every step and report every result. A red step does not stop the others,
30
+ because the reader needs the whole picture in one pass.
31
+
32
+ ```bash
33
+ BASE=<ref the change is measured against> # wave base sha, origin/develop, last release tag
34
+ SCOPE=<package or app dir the change touches> # never the monorepo root
35
+ CHANGED=$(git diff --name-only --diff-filter=d "$BASE" -- "$SCOPE" | grep -E '\.(ts|tsx|js|jsx|mjs|cjs)$')
36
+ ```
37
+
38
+ | # | Check | Command | Blocks when |
39
+ |---|---|---|---|
40
+ | 1 | **FORM**: SOLID and boundaries | `rdc-solid-score "$SCOPE" --diff "$BASE" --format json` (add `--config <repo>/.solid-score.yml` if the repo has one) | `regressions`, `boundaryViolations` or `boundaryMisconfigured` is non-empty |
41
+ | 2 | **LINT**: changed files only | the repo's own linter; fix first, then check: `npx biome check --write $CHANGED` then `npx biome check $CHANGED` (ESLint: `npx eslint --fix` then `npx eslint`) | the second command exits non-zero |
42
+ | 3 | **TYPES** | `npx tsc --noEmit -p <package>/tsconfig.json`, once per touched package | exit non-zero |
43
+ | 4 | **TESTS**, scoped | the touched package's own test command, e.g. `pnpm --filter <pkg> exec vitest run` | any failure |
44
+ | 5 | **RENDERED UI**, if a route changed | the project's pinned `test:ui` Playwright spec against a fresh app-owned server (`rdc:review` step 9a) | any red assertion, or no harness |
45
+
46
+ Read the scorer output fully:
47
+
48
+ - `removedFiles` lists deletions. They are reported, not scored: a deleted file has no units left that can regress.
49
+ - `unresolvedLanguages` lists files no plugin parses, such as `.css`. They are unmeasured, never passed.
50
+
51
+ ### Rules that keep it honest
52
+
53
+ - **Lint is fixed, never baselined.** Diagnostics already in a touched file
54
+ are fixed in the same change. Run `--write` first, and most of them clear
55
+ mechanically. Do not add an ignore list or a count ratchet.
56
+ - **SOLID is a ratchet by design.** `--diff` compares each unit with itself at
57
+ `$BASE`. OCP and LSP report `low` confidence, and that confidence is data. A
58
+ regression still blocks. The usual fix for an OCP drop is to move the
59
+ branching into a named module; for `investors/page.tsx` that means taking the
60
+ data read and FAQ shaping out into a loader.
61
+ - **Stay inside the time budget by narrowing scope.** Changed files and touched
62
+ packages only. If the gate takes more than two minutes, it is scanning too
63
+ wide. Narrow `$SCOPE` rather than dropping a step.
64
+ - **Never edit what a gate measures to turn it green.** A red check is either
65
+ fixed properly or reported as a finding.
66
+
67
+ ### New UI tests: prove them before trusting them
68
+
69
+ Three lessons from the same build:
70
+
71
+ - **Negative-control every new UI assertion.** Run it once against the broken
72
+ or old state and watch it go red. A test that has never failed has not
73
+ proved anything. The CTA-clipping spec was trusted only after it failed on
74
+ the old CSS.
75
+ - **Measure settled layout.** Take bounding boxes after motion finishes, using
76
+ reduced motion or waiting for the animation to end. A panel measured
77
+ mid-slide reports the wrong size.
78
+ - **Include a 1280×800 viewport.** A laptop screen is where a tall panel
79
+ clips its call-to-action. 1440 and 390 alone missed it.
80
+
81
+ ## When an LLM reviewer is still right
82
+
83
+ Only for a **named question the validators cannot answer**, scoped to the
84
+ files that question touches. Examples:
85
+
86
+ - "Does this middleware bypass skip auth on any protected path?"
87
+ - "Does any public string state a return figure?"
88
+
89
+ ```
90
+ Agent({
91
+ subagent_type: "pr-review-toolkit:code-reviewer",
92
+ description: "<the question, in five words>",
93
+ prompt: "Answer one question about <files>: <question>. Read only those files.
94
+ Return CODE_REVIEW_COMPLETE with { critical_count, high_count, medium_count,
95
+ low_count, findings: [{severity, file:line, issue, suggested_fix}] }."
96
+ })
97
+ ```
98
+
99
+ A reviewer asked to "review the diff" with no question is the pattern this
100
+ guide retired. Critical and high findings from a named-question pass block, the
101
+ same as a red gate step.
102
+
103
+ ## What is not in the default gate
104
+
105
+ | Tool | Why it is not in the default gate | When to run it |
106
+ |---|---|---|
107
+ | `rdc-clean-code-score` | No `--diff`, so it reports absolute counts. On the PRT build it reported 475 findings in 17 files, 459 of them N4 "magic number" on SVG coordinates. Its default dead-export scope is the whole git repo, which ran out of 4 GB of heap on regen-root. | Advisory, in `rdc:full-analysis`. Always pass `--project-root <package dir>`; that run took 7.9 s. |
108
+ | `rdc-architecture-score` | No `--diff`. With no `layers:` config, every file is `unclassified`, so it measures nothing. | When the repo declares layers. |
109
+ | `rdc-duplication-score` | Absolute, not a delta. | Advisory. |
110
+
111
+ ## Review is not closure
112
+
113
+ The gate replaces the per-wave LLM code review. It does **not** replace the
114
+ validator.
115
+
116
+ A non-epic work item still reaches `done` only through
117
+ `update_work_item_status(..., 'done', ..., <validator session>, 'validator')`,
118
+ and the database refuses an actor session equal to the item's own. A forked
119
+ agent shares its parent's session id, so a fork cannot validate the parent's
120
+ item. Dispatch a fresh agent for that.
121
+
122
+ ## Receipt
123
+
124
+ Emit this line. The skill that ran the gate quotes it in the implementation
125
+ report or review report:
126
+
127
+ ```
128
+ REVIEW_GATE: { base, scope, files, solid: { regressions, boundaryViolations, removedFiles, unresolved },
129
+ lint: <exit>, tsc: <exit>, tests: "<N passed>", ui: "<spec + viewports> | n/a",
130
+ elapsed_s, verdict: "CLEAN|HAS_ISSUES" }
131
+ ```
132
+
133
+ Under `RDC_TEST=1`: echo `[RDC_TEST] skipping review gate` and continue.
@@ -156,6 +156,17 @@ function isGitCommit(command) {
156
156
  function extractCommitMessage(command) {
157
157
  const cmd = String(command || '');
158
158
 
159
+ // `-m "$(cat <<'EOF' … EOF)"` / `-m "$(cat <<-'EOF' … EOF)"`: the plain -m
160
+ // regexes below match the FIRST unrelated quote inside the substitution
161
+ // (the opening `'` of `<<'EOF'` reads as a closing delimiter to the
162
+ // non-greedy pattern) and return early with literal shell text as the
163
+ // "message" -- before the heredoc fallback ever runs. This is the same
164
+ // defect class the 2026-08-29 fix addressed for a bare heredoc, just one
165
+ // wrapper layer further out: detect the substitution shape first and
166
+ // extract from its OWN heredoc body.
167
+ const mSub = cmd.match(/-m\s+"\$\(\s*cat\s+<<-?\s*(['"]?)([A-Za-z_][A-Za-z0-9_]*)\1\s*\n([\s\S]*?)\n[ \t]*\2\s*\)"/);
168
+ if (mSub) return { message: mSub[3], readable: true };
169
+
159
170
  const msgMatch = cmd.match(/-m\s+["']([^"']+)["']/s) ||
160
171
  cmd.match(/-m\s+"([\s\S]+?)"\s*(?:&&|$)/);
161
172
  if (msgMatch) return { message: msgMatch[1], readable: true };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lifeaitools/rdc-skills",
3
- "version": "0.36.1",
3
+ "version": "0.37.0",
4
4
  "description": "RDC typed-agent dispatch skill suite for Claude Code - plan, build, review, overnight builds",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -141,6 +141,23 @@ function walk(dir, exclude, out = []) {
141
141
  return out;
142
142
  }
143
143
 
144
+ /**
145
+ * Does this `git show <ref>:<path>` stderr mean "the file is not at <ref>"?
146
+ *
147
+ * Git words it two ways, and the common one was missing until 2026-09-21:
148
+ * - `fatal: path 'p' does not exist in 'ref'` — absent at ref AND on disk
149
+ * - `fatal: path 'p' exists on disk, but not in 'ref'` — a NEW file in the
150
+ * working tree, i.e. every `--diff` over a change that adds a file
151
+ * Matching only the first made `--diff` crash on any diff with a new file.
152
+ * Anything else (a bad ref, a path-form mismatch) is not "absent" and must be
153
+ * rethrown, so a real error never passes silently as a new unit.
154
+ */
155
+ export function isAbsentAtRef(stderr) {
156
+ const msg = String(stderr ?? '');
157
+ if (!msg.includes('fatal: path')) return false;
158
+ return msg.includes('does not exist in') || msg.includes('exists on disk, but not in');
159
+ }
160
+
144
161
  /** Score a file's text as it existed at a given git ref, without touching the working tree. */
145
162
  function scoreAtRef(repoRoot, filePath, ref, weights) {
146
163
  const relPath = relative(repoRoot, filePath).split('\\').join('/');
@@ -151,10 +168,9 @@ function scoreAtRef(repoRoot, filePath, ref, weights) {
151
168
  // `git show` fails both when the file is genuinely new at `ref` AND for
152
169
  // unrelated reasons (a path-form mismatch, a bad ref). Collapsing both
153
170
  // into "it's new" hides the second case — the exact class of silent
154
- // path-form bug found three times in this file already tonight. Only a
155
- // "does not exist" message is treated as new; anything else is rethrown.
156
- const msg = String(err.stderr ?? err.message ?? '');
157
- if (msg.includes('does not exist in') || msg.includes('fatal: path') && msg.includes('does not exist')) return null;
171
+ // path-form bug found three times in this file already. Only git's two
172
+ // "not at this ref" messages are treated as new; anything else is rethrown.
173
+ if (isAbsentAtRef(err.stderr ?? err.message)) return null;
158
174
  throw err;
159
175
  }
160
176
  const plugin = pluginFor(filePath);
@@ -237,6 +253,7 @@ async function main() {
237
253
  }
238
254
 
239
255
  let files;
256
+ let removedFiles = [];
240
257
  if (diffRef) {
241
258
  // `git diff --name-only <ref> -- <path>` (no `...HEAD`) compares `<ref>`
242
259
  // directly against the WORKING TREE — committed history AND uncommitted
@@ -244,8 +261,16 @@ async function main() {
244
261
  // diff` form, so they're unioned in separately. Before this fix, only
245
262
  // `<ref>...HEAD` (committed history alone) was scored — a dirty tree
246
263
  // with real regressions scored zero files and exited 0.
247
- const committed = execFileSync('git', ['diff', '--name-only', diffRef, '--', targetPath], { cwd: repoRoot, encoding: 'utf8' })
264
+ //
265
+ // Deleted paths are split out by git's own account (`--diff-filter=D`),
266
+ // not by an existsSync guess, so a path-form bug can never pass as a
267
+ // deletion. A deleted file has no current units and so nothing that can
268
+ // regress; before 2026-09-21 it reached extractUnits and crashed the gate
269
+ // with ENOENT. It is reported in `removedFiles`, never dropped silently.
270
+ const nameOnly = (filter) => execFileSync('git', ['diff', '--name-only', `--diff-filter=${filter}`, diffRef, '--', targetPath], { cwd: repoRoot, encoding: 'utf8' })
248
271
  .split('\n').filter(Boolean);
272
+ const committed = nameOnly('d');
273
+ removedFiles = nameOnly('D');
249
274
  const untracked = execFileSync('git', ['ls-files', '--others', '--exclude-standard', '--', targetPath], { cwd: repoRoot, encoding: 'utf8' })
250
275
  .split('\n').filter(Boolean);
251
276
  files = [...new Set([...committed, ...untracked])]
@@ -285,7 +310,7 @@ async function main() {
285
310
  const boundaryViolations = boundaryFindings.filter((f) => !f.satisfied);
286
311
 
287
312
  const output = {
288
- results, regressions, boundaryFindings, boundaryViolations, boundaryMisconfigured, unresolvedLanguages,
313
+ results, regressions, removedFiles, boundaryFindings, boundaryViolations, boundaryMisconfigured, unresolvedLanguages,
289
314
  config: { weights: config.weights, diff: config.diff, boundaryRuleCount: (config.boundaries ?? []).length },
290
315
  configPath, configLoaded,
291
316
  parser: parserName,
@@ -312,6 +337,10 @@ async function main() {
312
337
  console.log('\nREGRESSIONS:');
313
338
  for (const r of regressions) console.log(` ${r.file} :: ${r.unit} — ${r.reason} ${JSON.stringify(r)}`);
314
339
  }
340
+ if (removedFiles.length) {
341
+ console.log(`\n${removedFiles.length} file(s) deleted since ${diffRef} — no units left to score:`);
342
+ for (const f of removedFiles) console.log(` ${f}`);
343
+ }
315
344
  if (unresolvedLanguages.length) {
316
345
  console.log(`\n${unresolvedLanguages.length} file(s) matched no registered language plugin — skipped, not silently passed:`);
317
346
  for (const f of unresolvedLanguages) console.log(` ${relative(repoRoot, f)}`);
@@ -644,27 +644,30 @@ Read the task title and description, then:
644
644
 
645
645
  ⛔ **NO wave may dispatch until the previous wave's code-review pass clears.** Memory `feedback_code_review_per_wave.md` — Davesend 2026-05-24 incident.
646
646
 
647
- Dispatch ONE `pr-review-toolkit:code-reviewer` agent with the wave's merged diff:
648
-
649
- ```
650
- Agent({
651
- subagent_type: "pr-review-toolkit:code-reviewer",
652
- description: "Wave <N> code review",
653
- prompt: "Review the diff `git diff <wave-base-sha>..HEAD` on develop.
654
- Focus on: bugs, logic errors, security vulnerabilities, project-convention adherence
655
- (.claude/rules/*, CLAUDE.md). Apply confidence-based filtering — high-confidence
656
- findings only. Report severity per finding: critical | high | medium | low.
657
- Return CODE_REVIEW_COMPLETE with: { critical_count, high_count, medium_count,
658
- low_count, findings: [{severity, file:line, issue, suggested_fix}] }."
659
- })
660
- ```
661
-
662
- **Severity gate (default — high+ blocks):**
663
- - `critical` or `high` findings → reopen affected work items to `todo` with finding text in notes; fix in a new wave before continuing
664
- - `medium` or `low` findings → append to each work item's `implementation_report.flags`; do NOT reopen; validator sees them
665
- - Zero findings → log `CODE_REVIEW: CLEAN` and proceed to next wave
666
-
667
- Under `RDC_TEST=1`: echo `[RDC_TEST] skipping code-review dispatch` and proceed.
647
+ Run the deterministic review gate in `guides/review-gate.md` over the wave's
648
+ merged diff, with `BASE=<wave-base-sha>` and `SCOPE` set to each package or
649
+ app the wave touched. That covers `rdc-solid-score --diff`, the repo linter on
650
+ changed files, `tsc`, scoped tests, and the UI spec when a route changed. It
651
+ takes one to two minutes; an open-ended reviewer agent took 4–10.
652
+ Approved: direct operator instruction — "we need to tune our code reviews to be
653
+ specific -- if you ran the validator for solid and lint etc... it should take 1-2
654
+ minutes". 2026-09-21, Dave, in session.
655
+
656
+ Dispatch `pr-review-toolkit:code-reviewer` only for a **named question** the
657
+ validators cannot answer, scoped to the files it touches (the guide's
658
+ "When an LLM reviewer is still right" section). Never dispatch it with a bare
659
+ "review the diff".
660
+
661
+ **Gate outcome:**
662
+ - A red gate step, or a critical/high finding from a named-question pass →
663
+ reopen the affected work items to `todo` with the `REVIEW_GATE` receipt and
664
+ the failing output in notes. Fix in a new wave before continuing.
665
+ - Medium/low findings from a named-question pass → append them to each work
666
+ item's `implementation_report.flags`. Do not reopen; the validator sees them.
667
+ - All steps green → log the `REVIEW_GATE` receipt with `verdict: "CLEAN"` and
668
+ proceed to the next wave.
669
+
670
+ Under `RDC_TEST=1`: echo `[RDC_TEST] skipping review gate` and proceed.
668
671
 
669
672
  10. **Mandatory validator gate (runs after ALL waves complete — before any work item closes):**
670
673
 
@@ -65,6 +65,12 @@ matches the numeric-suffix heuristic) alongside real hits (`data`, `temp`,
65
65
  `bin.rdc-clean-code-score`; falls back to
66
66
  `node <rdc-skills-install-path>/scripts/clean-code-score.mjs`.)
67
67
 
68
+ **In a monorepo, always pass `--project-root <package or app dir>`.** Without
69
+ it, the dead-export scan walks the whole git repo. On regen-root that ran out
70
+ of 4 GB of heap after 18 s (exit 134); scoped to `apps/prtrust` it took 7.9 s.
71
+ This scorer has no `--diff` and reports absolute counts, so it is advisory,
72
+ not part of the merge gate (`guides/review-gate.md`).
73
+
68
74
  2. **Read `results`** — per-unit findings for N1 (cryptic names), N2 (heuristic
69
75
  meaningless-distinction names, low confidence), N4 (magic numbers), N7
70
76
  (generic class/function names), F1 (>20 statements), F2 (>3 params), E1
@@ -111,26 +111,27 @@ fi
111
111
 
112
112
  ⛔ **No fixit closes without a code-review pass.** Even single-file changes go through review.
113
113
 
114
- Dispatch ONE `pr-review-toolkit:code-reviewer` agent on the fixit commit:
115
-
116
- ```
117
- Agent({
118
- subagent_type: "pr-review-toolkit:code-reviewer",
119
- description: "fixit code review",
120
- prompt: "Review `git show HEAD` on the development branch. Focus on:
121
- bugs, logic errors, security, project-convention adherence (.claude/rules/*).
122
- Confidence-based filtering — high-confidence findings only.
123
- Return CODE_REVIEW_COMPLETE with: { critical_count, high_count, medium_count,
124
- low_count, findings: [{severity, file:line, issue, suggested_fix}] }."
125
- })
126
- ```
127
-
128
- **Severity gate:**
129
- - `critical` or `high` findings → fix in this same fixit session (do not escalate to rdc:build for the fix itself; the original fixit owns the cleanup), re-commit, re-run review until clean
130
- - `medium` or `low` findings → record in `implementation_report.flags`; proceed to close
131
- - Zero findings → proceed to close
132
-
133
- Under `RDC_TEST=1`: echo `[RDC_TEST] skipping code-review dispatch` and proceed.
114
+ Run the deterministic review gate in `guides/review-gate.md` with
115
+ `BASE=HEAD~1` (or the commit before the fixit's first commit) and `SCOPE` set
116
+ to the touched package. For a fixit this usually takes seconds.
117
+ Approved: direct operator instruction — "we need to tune our code reviews to be
118
+ specific -- if you ran the validator for solid and lint etc... it should take 1-2
119
+ minutes". 2026-09-21, Dave, in session.
120
+
121
+ Dispatch `pr-review-toolkit:code-reviewer` only for a **named question** the
122
+ validators cannot answer, scoped to the files it touches. Never dispatch it with
123
+ a bare "review the commit".
124
+
125
+ **Gate outcome:**
126
+ - A red gate step, or a critical/high finding from a named-question pass → fix
127
+ it in this same fixit session. Do not escalate the fix to rdc:build; the
128
+ original fixit owns the cleanup. Re-commit and re-run the gate until it is clean.
129
+ - Medium/low findings from a named-question pass → record them in
130
+ `implementation_report.flags` and proceed to close.
131
+ - All steps green → quote the `REVIEW_GATE` receipt in the implementation report
132
+ and proceed to close.
133
+
134
+ Under `RDC_TEST=1`: echo `[RDC_TEST] skipping review gate` and proceed.
134
135
 
135
136
  ### 5.7 Deliver to done (MANDATORY — a fixit is not done until the change is LIVE)
136
137
 
@@ -144,7 +144,7 @@ After each wave: check `BUILD_STATUS`. If `escalated: true`, log the escalation
144
144
  in the overnight doc and continue — don't stop the loop.
145
145
  After each wave and after every resumed epic, require the `runOrchestrator()` receipt from `rdc:build` **where the orchestrator applies** — the target contains `corpus/_shared/build/phase-manifest.json` (today: regen-root; see `rdc:build` §1a). Elsewhere, require the contract checklist (`rdc-work check`) instead; an absent orchestrator is not a hold. `admission_refocus` or `pipeline_blocked` means the epic is held for an explicitly requested Design Review or validator closure; log that state and do not hand-reconstruct a dispatch wave.
146
146
 
147
- **Mandatory code-review gate inherited from rdc:build (Step 9b).** Every wave inside `rdc:build` runs a `pr-review-toolkit:code-reviewer` pass before the next wave dispatches. Critical/high findings reopen the affected work items to `todo` and the next wave fixes them. Overnight does not skip or weaken this gate. If a wave's code-review escalates twice, advisor decides; otherwise the loop continues.
147
+ **Mandatory code-review gate inherited from rdc:build (Step 9b).** Every wave inside `rdc:build` runs the deterministic review gate (`guides/review-gate.md`: solid-score --diff, linter on changed files, tsc, scoped tests, UI spec) before the next wave dispatches. It takes one to two minutes, where an open-ended reviewer took 4–10, which matters most across an overnight queue. A red step reopens the affected work items to `todo` and the next wave fixes them. Overnight does not skip or weaken this gate. If a wave's gate escalates twice, the advisor decides; otherwise the loop continues.
148
148
 
149
149
  ### 3d. Review
150
150
 
@@ -41,7 +41,7 @@ rdc:release: <repo> vX.Y.Z -> vA.B.C
41
41
  [ ] Dry-run gate handled
42
42
  [ ] Version files updated
43
43
  [ ] Tests/self-test passed
44
- [ ] Mandatory release code-review (pr-review-toolkit:code-reviewer on `git diff <last-released-tag>..HEAD`). Block release on `critical`/`high` findings; record `medium`/`low` in the release notes and proceed.
44
+ [ ] Mandatory release review gate (`guides/review-gate.md`, BASE=<last-released-tag>): solid-score --diff, linter on changed files, tsc, scoped tests. Block the release on any red step; LLM reviewer only for a named question.
45
45
  [ ] Commit created
46
46
  [ ] Tag created
47
47
  [ ] Branch and tag pushed
@@ -12,8 +12,9 @@ description: rdc:review () - [--unattended] — post-build gate over tsc, tests,
12
12
  > **Sandbox contract:** This skill honors `RDC_TEST=1` per `guides/agent-bootstrap.md` § RDC_TEST Sandbox Contract. Destructive external calls short-circuit under the flag.
13
13
 
14
14
  > **Standard being enforced:** `docs/CODING-STANDARDS.md` (SOLID/Clean-Architecture —
15
- > regen-root; skip if absent). Step 8c is where it's actually checked, via this package's
15
+ > regen-root; skip if absent). Steps 8b and 8c check it, via this package's
16
16
  > own `rdc-solid-score` (also invoked by ATF's Test-Ladder as a consumer, not an owner).
17
+ > Step 8b runs the deterministic review gate, `guides/review-gate.md`.
17
18
 
18
19
 
19
20
  # rdc:review — Quality Gate
@@ -84,41 +85,47 @@ description: rdc:review () - [--unattended] — post-build gate over tsc, tests,
84
85
  - Never silently leave orphaned tasks
85
86
  - **⛔ Raw `UPDATE work_items SET parent_id = ...` is forbidden** — bypasses RLS and all constraint checks
86
87
 
87
- 8b. **Mandatory code-review gate — dispatch pr-review-toolkit:code-reviewer:**
88
-
89
- ⛔ **No CLEAN verdict without a code-review pass.** This is the second half of the quality gate; tsc/vitest catch type and behavior errors, code-reviewer catches logic, security, and convention drift.
90
-
91
- Dispatch ONE `pr-review-toolkit:code-reviewer` agent on the full diff under review:
92
-
93
- ```
94
- Agent({
95
- subagent_type: "pr-review-toolkit:code-reviewer",
96
- description: "rdc:review code-review pass",
97
- prompt: "Review `git diff origin/main...HEAD` on the development branch.
98
- Focus on: bugs, logic errors, security vulnerabilities, project-convention
99
- adherence (.claude/rules/*, CLAUDE.md, engineering-behavior.md).
100
- Confidence-based filtering — high-confidence findings only.
101
- Return CODE_REVIEW_COMPLETE with: { critical_count, high_count, medium_count,
102
- low_count, findings: [{severity, file:line, issue, suggested_fix}] }."
103
- })
104
- ```
105
-
106
- **Severity gate:**
107
- - `critical` or `high` → verdict cannot be CLEAN. Interactive: report and pause. Unattended: auto-fix if mechanical (rename, missing await, obvious null check), otherwise escalate via advisor and mark HAS_ISSUES.
108
- - `medium` or `low` → record in the review report's "Issues" section; verdict can still be CLEAN.
109
- - Zero findings → log `CODE_REVIEW: CLEAN` and continue.
110
-
111
- Under `RDC_TEST=1`: echo `[RDC_TEST] skipping code-review dispatch` and continue.
88
+ 8b. **Mandatory code-review gate — deterministic validators on the diff:**
89
+
90
+ ⛔ **No CLEAN verdict without a review-gate pass.** Run `guides/review-gate.md`
91
+ over the diff under review, with `BASE` set to the review's base ref and `SCOPE`
92
+ set to each modified package from step 1. Its checks are
93
+ `rdc-solid-score --diff`, the repo linter on changed files (`--write`, then
94
+ check), `tsc`, scoped tests, and the UI spec for changed routes. It finishes in
95
+ one to two minutes and gives the same answer twice.
96
+ Approved: direct operator instruction — "we need to tune our code reviews to be
97
+ specific -- if you ran the validator for solid and lint etc... it should take 1-2
98
+ minutes". 2026-09-21, Dave, in session.
99
+
100
+ Dispatch `pr-review-toolkit:code-reviewer` only for a **named question** the
101
+ validators cannot answer, such as auth bypass on a protected path or a return
102
+ figure in public copy. Scope it to the files that question touches, following
103
+ the guide's "When an LLM reviewer is still right" section. A bare "review the
104
+ diff" dispatch is retired.
105
+
106
+ **Gate outcome:**
107
+ - A red gate step, or a critical/high finding from a named-question pass →
108
+ the verdict cannot be CLEAN. Interactive: report and pause. Unattended:
109
+ fix it if the fix is mechanical (`--write` lint fixes, a missing await, an
110
+ obvious null check); otherwise escalate via the advisor and mark HAS_ISSUES.
111
+ - Medium/low findings from a named-question pass → record them in the review
112
+ report's "Issues" section. The verdict can still be CLEAN.
113
+ - All steps green → quote the `REVIEW_GATE` receipt and continue.
114
+
115
+ Under `RDC_TEST=1`: echo `[RDC_TEST] skipping review gate` and continue.
112
116
 
113
117
  8c. **Form/fit/function gate — solid-validator + architecture-reviewer:**
114
118
 
115
- ⛔ **No CLEAN verdict without this pass either.** Step 8b catches logic,
116
- security, and convention drift. This step catches a different failure
117
- shape entirely: code that is logically correct and convention-clean but
118
- architecturally wrong — the `Harness` case (a use-case orchestrator that
119
- reimplements what sibling packages exist to do, scoring fine on every
120
- generic metric while failing the one check that names the actual rule).
119
+ ⛔ **No CLEAN verdict without this pass either.** Step 8b's lint, tsc and
120
+ tests catch convention drift and broken behavior. This step catches a
121
+ different failure shape: code that is logically correct and
122
+ convention-clean but architecturally wrong. The `Harness` case is the
123
+ example: a use-case orchestrator that reimplements what sibling packages
124
+ exist to do, scores fine on every generic metric, and fails the one check
125
+ that names the actual rule.
121
126
 
127
+ Reuse the `rdc-solid-score` JSON from step 8b rather than running the scorer
128
+ twice. If 8b ran without the repo's boundary config, re-run it with the config:
122
129
  ```bash
123
130
  rdc-solid-score <modified-package-path> --diff origin/main --config <repo>/.solid-score.yml --format json
124
131
  ```
@@ -0,0 +1,103 @@
1
+ import assert from 'node:assert/strict';
2
+ import { execFileSync, spawnSync } from 'node:child_process';
3
+ import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
4
+ import { tmpdir } from 'node:os';
5
+ import { dirname, join } from 'node:path';
6
+ import test from 'node:test';
7
+ import { fileURLToPath } from 'node:url';
8
+
9
+ import { isAbsentAtRef } from '../../scripts/solid-score.mjs';
10
+
11
+ const SCORER = join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'scripts', 'solid-score.mjs');
12
+
13
+ // ── isAbsentAtRef: git's two "not at this ref" messages, and nothing else ──
14
+
15
+ test('isAbsentAtRef: "does not exist in" (absent at ref and on disk) is absent', () => {
16
+ assert.equal(isAbsentAtRef("fatal: path 'src/a.ts' does not exist in 'origin/develop'\n"), true);
17
+ });
18
+
19
+ test('isAbsentAtRef: "exists on disk, but not in" (a NEW working-tree file) is absent', () => {
20
+ // The message every --diff over a change that adds a file produces; it was
21
+ // unmatched until 2026-09-21 and crashed the gate.
22
+ assert.equal(isAbsentAtRef("fatal: path 'apps/x/tests/ui/home.spec.ts' exists on disk, but not in 'origin/develop'\n"), true);
23
+ });
24
+
25
+ test('isAbsentAtRef: a bad ref is NOT absent — it must be rethrown', () => {
26
+ assert.equal(isAbsentAtRef("fatal: invalid object name 'no-such-ref'.\n"), false);
27
+ });
28
+
29
+ test('isAbsentAtRef: a path-form mismatch is NOT absent — it must be rethrown', () => {
30
+ assert.equal(
31
+ isAbsentAtRef("fatal: path 'src/a.ts' exists, but not 'a.ts'\nhint: Did you mean 'HEAD:src/a.ts' aka 'HEAD:./a.ts'?\n"),
32
+ false,
33
+ );
34
+ });
35
+
36
+ test('isAbsentAtRef: empty or missing stderr is NOT absent', () => {
37
+ assert.equal(isAbsentAtRef(''), false);
38
+ assert.equal(isAbsentAtRef(undefined), false);
39
+ });
40
+
41
+ // ── end to end: --diff over a change that adds a file ──
42
+
43
+ function git(cwd, ...args) {
44
+ return execFileSync('git', ['-c', 'user.email=t@example.com', '-c', 'user.name=t', '-c', 'commit.gpgsign=false', ...args], {
45
+ cwd,
46
+ encoding: 'utf8',
47
+ });
48
+ }
49
+
50
+ const CLEAN_CLASS = (name) => `export class ${name} {\n private n = 0;\n add(x: number): number {\n this.n += x;\n return this.n;\n }\n}\n`;
51
+
52
+ test('solid-score --diff scores a new file as a new unit instead of crashing', () => {
53
+ const dir = mkdtempSync(join(tmpdir(), 'solid-diff-'));
54
+ try {
55
+ git(dir, 'init', '-q');
56
+ writeFileSync(join(dir, 'a.ts'), CLEAN_CLASS('Alpha'));
57
+ git(dir, 'add', 'a.ts');
58
+ git(dir, 'commit', '-q', '-m', 'base');
59
+
60
+ // One untracked new file, and one committed-after-base new file: both are
61
+ // "exists on disk, but not in <ref>" at the base ref.
62
+ writeFileSync(join(dir, 'b.ts'), CLEAN_CLASS('Beta'));
63
+ writeFileSync(join(dir, 'c.ts'), CLEAN_CLASS('Gamma'));
64
+ git(dir, 'add', 'c.ts');
65
+ git(dir, 'commit', '-q', '-m', 'add c');
66
+
67
+ const run = spawnSync(process.execPath, [SCORER, dir, '--diff', 'HEAD~1', '--format', 'json'], { encoding: 'utf8' });
68
+ assert.equal(run.status, 0, `scorer exited ${run.status}: ${run.stderr}`);
69
+ const out = JSON.parse(run.stdout);
70
+ const scored = out.results.map((r) => r.file).sort();
71
+ assert.deepEqual(scored, ['b.ts', 'c.ts'], 'both new files are scored');
72
+ assert.deepEqual(out.regressions, [], 'clean new units are not regressions');
73
+ assert.deepEqual(out.removedFiles, [], 'nothing was deleted');
74
+ } finally {
75
+ rmSync(dir, { recursive: true, force: true });
76
+ }
77
+ });
78
+
79
+ test('solid-score --diff reports a deleted file in removedFiles instead of crashing', () => {
80
+ // A deleted file is listed by `git diff --name-only <ref>`; before
81
+ // 2026-09-21 it went to extractUnits and the gate died with ENOENT.
82
+ const dir = mkdtempSync(join(tmpdir(), 'solid-diff-del-'));
83
+ try {
84
+ git(dir, 'init', '-q');
85
+ writeFileSync(join(dir, 'a.ts'), CLEAN_CLASS('Alpha'));
86
+ writeFileSync(join(dir, 'gone.ts'), CLEAN_CLASS('Gone'));
87
+ git(dir, 'add', 'a.ts', 'gone.ts');
88
+ git(dir, 'commit', '-q', '-m', 'base');
89
+
90
+ // One deletion committed after base, one edit left uncommitted.
91
+ git(dir, 'rm', '-q', 'gone.ts');
92
+ git(dir, 'commit', '-q', '-m', 'remove gone');
93
+ writeFileSync(join(dir, 'a.ts'), `${CLEAN_CLASS('Alpha')}\nexport const touched = 1;\n`);
94
+
95
+ const run = spawnSync(process.execPath, [SCORER, dir, '--diff', 'HEAD~1', '--format', 'json'], { encoding: 'utf8' });
96
+ assert.equal(run.status, 0, `scorer exited ${run.status}: ${run.stderr}`);
97
+ const out = JSON.parse(run.stdout);
98
+ assert.deepEqual(out.removedFiles, ['gone.ts'], 'the deletion is reported, not dropped');
99
+ assert.deepEqual(out.results.map((r) => r.file), ['a.ts'], 'only the surviving changed file is scored');
100
+ } finally {
101
+ rmSync(dir, { recursive: true, force: true });
102
+ }
103
+ });
@@ -219,8 +219,17 @@ EOF
219
219
  ['heredoc with a quoted delimiter passes', 'git commit -F - <<"MSG"\nfix(a): b\nMSG', 'pass'],
220
220
  ['heredoc carrying only a UUID passes', "git commit -F - <<'EOF'\nwip\n\nWork-Item: f27ff5fa-0000-4000-8000-000000000000\nEOF", 'pass'],
221
221
  ['-m still passes', 'git commit -m "fix(x): y"', 'pass'],
222
+ // -m "$(cat <<'EOF' … EOF)" — the -m regex used to match the FIRST
223
+ // unrelated quote inside the substitution (the heredoc delimiter's own
224
+ // opening quote) and return early with literal shell text as the
225
+ // "message", never reaching the heredoc fallback below. Same defect
226
+ // class as the 2026-08-29 fix, one wrapper layer further out. Filed as
227
+ // regen-root#243.
228
+ ['-m "$(cat <<EOF …)" substitution passes', "git commit -m \"$(cat <<'EOF'\nfix(guards): correct the heredoc substitution match\n\nBody line two.\nEOF\n)\"", 'pass'],
229
+ ['-m "$(cat <<-EOF …)" dash-form substitution passes', "git commit -m \"$(cat <<-'EOF'\nfix(a): b\nEOF\n)\"", 'pass'],
222
230
  // Negative controls — reading the message is not a bypass.
223
231
  ['heredoc without a type or UUID still blocks', "git commit -F - <<'EOF'\nrandom words\nEOF", 'block'],
232
+ ['-m "$(cat <<EOF …)" without a type or UUID still blocks', "git commit -m \"$(cat <<'EOF'\nrandom words\nEOF\n)\"", 'block'],
224
233
  ['a bare commit still blocks', 'git commit', 'block'],
225
234
  // Honest about what it cannot see, rather than calling it absent.
226
235
  ['a real stdin pipe reports unreadable', 'cat msg.txt | git commit -q -F -', 'unreadable'],
@@ -1,24 +0,0 @@
1
- ---
2
- id: 2026-08-17-fixit-codex-mcp-only-skills
3
- date: "2026-08-17"
4
- skill: fixit
5
- session: codex-mcp-startup-20260817
6
- scope: simple
7
- status: applied
8
- area: skill
9
- links:
10
- commits:
11
- - 263231a
12
- memory: []
13
- work_items:
14
- - dc076f35-236b-4f8d-be3f-646a66514ba5
15
- ---
16
-
17
- ## What happened
18
- Codex's real numbered-lane startup completed MCP initialization but emitted a red skill-budget error on every launch. The machine had 36 RDC skill directories in each of the global `.codex/skills` and `.agents/skills` roots while the same 36-skill catalog was already registered through the RDC-Skills MCP endpoint.
19
-
20
- ## Root cause
21
- `scripts/install-rdc-skills.js` registered the MCP endpoint and then copied the full RDC catalog into every detected Codex skill directory. Codex loaded those duplicate surfaces together, inflating startup metadata to 113 skills. The same install also left a legacy RDC marketplace block that Codex rejected as an unsupported plugin source.
22
-
23
- ## The fix / rule
24
- Codex receives RDC skills exclusively through MCP. The installer now verifies the MCP config before purging legacy file copies, fails closed if the replacement cannot be established, removes the obsolete marketplace/plugin blocks with CRLF coverage, and verifies that live Codex skill roots contain zero RDC duplicates. Implemented in commit `263231a`.
@@ -1,34 +0,0 @@
1
- ---
2
- id: 2026-09-17-release-mcp-registry-split
3
- date: "2026-09-17"
4
- skill: release
5
- session: 01a07b0d-e74e-7601-af20-9caae7e94b71
6
- scope: architectural
7
- status: open
8
- area: infra
9
- links:
10
- commits: [d39104709bf0035f84a8001cc6c30e5c5e0d9c13]
11
- memory: []
12
- work_items: [5f7618b4-cf32-4460-8a3a-761f0161b847]
13
- ---
14
-
15
- ## What happened
16
-
17
- rdc-skillsv0.36.0 tag triggered npm workflow35178348993: packing succeeded,
18
- registryPUT returned404, and npm version lookup remained404. Independently,
19
- the existing public MCP tag updater served0.36.0 at the exacttagSHA with45skills;
20
- connected convo retrieval and remote tests passed. Prior0.35.26 workflow also
21
- shows a PUT404 although that version is now in npm.
22
-
23
- ## Root cause
24
-
25
- Two release consumers advance independently: the MCP host follows mergedtags,
26
- while npm requires a successful authenticatedpublish. A live MCP cannot prove
27
- npm distribution, nor does failednpm publication prove the MCP is stale. The
28
- underlying registry permission/trustedpublisher cause is not established by404.
29
-
30
- ## The fix / rule
31
-
32
- Keep runtimeSHA/catalog and npmversion/provenance as distinct release evidence.
33
- Preserve the failedjob; inspect publisherconfiguration with the packageowner
34
- before changing permissions or retrying. No authboundary repair shipped here.