@lifeaitools/rdc-skills 0.35.26 → 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.
@@ -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.
@@ -142,9 +142,35 @@ A session with **no contract** — one that only read, planned or answered — s
142
142
  gets the evidence checks: tracked changes it left uncommitted, and a claimed work
143
143
  item's open DoD. How its final message is worded is never judged.
144
144
 
145
+ **Setting work aside.** A session ends one of three ways: the work is proved, it waits,
146
+ or someone else takes it. Say which — never leave the Stop to repeat an open DoD:
147
+
148
+ ```bash
149
+ # It waits: the database marks the item blocked, with the reason in its notes.
150
+ node "$LIFEAI_ENV/bin/rdc-work.mjs" hold --work-item <uuid> --reason "<why it waits>" --session <id>
151
+ # Pick it up later, from this session or any other: in_progress again, declared on the contract.
152
+ node "$LIFEAI_ENV/bin/rdc-work.mjs" resume --work-item <uuid> --session <id>
153
+ # Someone else takes it:
154
+ node "$LIFEAI_ENV/bin/rdc-work.mjs" handoff --work-item <uuid> --to "<who takes it over>" \
155
+ --reason "<why this session is not finishing it>" [--note <handoff doc>] --session <id>
156
+ ```
157
+
158
+ Only work the session holds — declared with `--work-item`, or claimed — can be set aside.
159
+ A held item's open DoD stops holding the Stop **while the database says `blocked`**; a
160
+ hold the database does not confirm lifts nothing and the Stop says so. A handed-off item
161
+ shows as `[>] handed off … theirs now: <rows>`. Neither is ever counted as proof. Drop the
162
+ rows this session will not prove, and commit or discard your own changes first — setting
163
+ work aside moves the obligation, not unsaved work, and uncommitted changes still hold the Stop.
164
+
165
+ The held Stop lists only what is still owed: proved and dropped rows are counted, not
166
+ listed, and rows stale only because of a later edit in the same repository collapse to
167
+ one line. An edit in a different repository does not stale this contract's proofs.
168
+
145
169
  Enforcement is bounded: a Stop held identically three times, or six times in a row,
146
170
  releases, and the unproved rows stay in the contract and in the compaction snapshot.
147
- A defect in the gate itself — an unreadable contract, an error — reports and never holds.
171
+ A released or resolved checklist is shown once, not after every later turn; a changed
172
+ one is shown again. A defect in the gate itself — an unreadable contract, an error —
173
+ reports and never holds.
148
174
 
149
175
  ## Target resolution — never assume regen-root
150
176
 
@@ -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.35.26",
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)}`);
@@ -72,7 +72,7 @@ for (let i = 0; i < args.length; i++) {
72
72
  /**
73
73
  * Resolve the regen-root monorepo checkout this run should validate against.
74
74
  *
75
- * 2026-07-26 (lesson 2026-07-26-deploy-worktree-validator-root): hardcoding
75
+ * 2026-07-26 (retired lesson 2026-07-26-deploy-worktree-validator-root): hardcoding
76
76
  * 'C:/Dev/regen-root' made every check run against the main tree even when
77
77
  * this validator was invoked from a registered worktree lane
78
78
  * (`C:/Dev/regen-root.wt/<lane>`), so a newly-landed app's PUBLISH.md read as
@@ -390,7 +390,7 @@ Read the task title and description, then:
390
390
  ### ⛔ Foreign concurrent session guard — `git status` BEFORE the build
391
391
  Worktree isolation protects against THIS build's own agents, not against a
392
392
  DIFFERENT session (another cell, a Codex run, a human) already committing on
393
- the same shared tree (lesson 2026-06-16-build-concurrent-session-shared-tree-commit-corruption:
393
+ the same shared tree (retired lesson 2026-06-16-build-concurrent-session-shared-tree-commit-corruption:
394
394
  a foreign session's staged-but-uncommitted files were swept into this build's
395
395
  commit under the wrong message). Before dispatching any wave, run `git status`
396
396
  to detect foreign-dirty files you did not create. If foreign-dirty files are
@@ -572,7 +572,7 @@ Read the task title and description, then:
572
572
  rely on hoisted monorepo deps."*
573
573
  - **A server/MCP/API task is NOT done without a committed automated test that
574
574
  ships in the SAME commit and exercises EVERY surface.** Manual curl / a single
575
- `/health` 200 is a proxy, not coverage (lesson 2026-06-10-build-weak-dod-no-tests).
575
+ `/health` 200 is a proxy, not coverage (retired lesson 2026-06-10-build-weak-dod-no-tests).
576
576
  For a collection (MCP skills, API routes, CLI commands): loop over ALL items
577
577
  and assert `output == source` — never a single spot check. Wire an npm script
578
578
  and run it green before the item leaves `review`. Agent prompt line: *"This
@@ -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
 
@@ -747,4 +750,10 @@ NEVER run pnpm build or pnpm turbo. Use npx vitest run only.
747
750
 
748
751
  ## Capture lessons (exit step)
749
752
 
750
- Before the final verdict line, follow `.rdc/guides/lessons-learned-spec.md` § Capture procedure. If this run taught something non-obvious — a first root-cause theory that turned out wrong, the documented/standard path not working, a missing gate or check that cost a round, or a surprising tool/infra behavior — write one `.rdc/lessons/<YYYY-MM-DD>-build-<short-slug>.md` per lesson using the schema in that spec. Set `scope` (`simple` | `architectural`) and `status` (`open`, or `applied` if you shipped the fix in this same run, with the commit linked). Commit the lesson file(s) on `develop` alongside the run's other commits, and note "N lessons captured" in your verdict/summary. A run that taught nothing writes nothing — absence is the default.
753
+ Before the final verdict line, follow `.rdc/guides/lessons-learned-spec.md` § Capture procedure. If this run taught something non-obvious — a first root-cause theory that turned out wrong, the documented/standard path not working, a missing gate or check that cost a round, or a surprising tool/infra behavior — record each lesson where its fix belongs:
754
+
755
+ - **Fix encoded in this run** (a guard, test or script shipped, commit linked): the lesson goes in that commit's message. No file, no issue.
756
+ - **Fix not encoded**: submit it to the repository the fix belongs to, which opens one `lesson` + `proposal` issue there (an identical title opens nothing):
757
+ `node "$LIFEAI_ENV/bin/rdc-lesson.mjs" submit --repo <owning repo slug> --title "…" --what "…" --root-cause "…" --fix "…"`
758
+
759
+ No lesson file is written. Note "N lessons captured (E encoded, S submitted)" with the issue URLs in your verdict/summary. A run that taught nothing records nothing — absence is the default.
@@ -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
@@ -462,7 +462,7 @@ An agreement is a **Decision**. Route it by kind:
462
462
  |---|---|
463
463
  | Settled decision, constraint, or policy | The governing document — plan / ARCHITECTURE.md / rule. AKG ingests from there. |
464
464
  | Work to perform | `insert_work_item` via RPC, per `.claude/rules/work-items-rpc.md` |
465
- | A genuine hard-won episode (a wrong theory, a surprising infra behaviour) | `.rdc/lessons/` per `guides/lessons-learned-spec.md` |
465
+ | A genuine hard-won episode (a wrong theory, a surprising infra behaviour) | A `lesson` issue in the repository the fix belongs to, per `guides/lessons-learned-spec.md` |
466
466
  | A stated, unresolved disagreement | Escalate — Step 6 |
467
467
 
468
468
  **Do not file a settled agreement as a lesson.** That is the single most common
@@ -576,8 +576,12 @@ and avoids acting on a misread premise (lesson
576
576
  ## Capture lessons (exit step)
577
577
 
578
578
  Before the final verdict line, follow `guides/lessons-learned-spec.md` § Capture
579
- procedure. Write a lesson only for a genuine **episode** — a first root-cause
579
+ procedure. Record a lesson only for a genuine **episode** — a first root-cause
580
580
  theory that proved wrong, a documented path that did not work, a surprising
581
- tool/infra behaviour. **A settled agreement is not a lesson** (see Step 5). Set
582
- `scope` and `status`; commit alongside the run's other commits; note "N lessons
583
- captured" in the verdict. A run that taught nothing writes nothing.
581
+ tool/infra behaviour. **A settled agreement is not a lesson** (see Step 5). A
582
+ lesson whose fix shipped as a guard, test or script this run goes in that
583
+ commit's message; any other lesson is submitted to the repository the fix
584
+ belongs to, as one `lesson` + `proposal` issue:
585
+ `node "$LIFEAI_ENV/bin/rdc-lesson.mjs" submit --repo <owning repo slug> --title "…" --what "…" --root-cause "…" --fix "…"`.
586
+ No lesson file is written. Note "N lessons captured" with the issue URLs in the
587
+ verdict. A run that taught nothing records nothing.
@@ -0,0 +1,178 @@
1
+ ---
2
+ name: convo
3
+ description: "rdc:convo — fair attributed discussion using six reusable templates, shared sources, explicit dissent and durable decisions; not an implementation dispatch."
4
+ ---
5
+
6
+ > **OUTPUT CONTRACT:** `guides/output-contract.md`. Show the selected template,
7
+ > discussion progress and decision boundary, not transport logs.
8
+ > **Sandbox contract:** Under `RDC_TEST=1`, do not contact live peers or mutate
9
+ > external state. Return a labeled simulated discussion packet; absent peer
10
+ > responses remain UNKNOWN, never simulated agreement.
11
+
12
+ # rdc:convo — shared deliberation, explicit decisions
13
+
14
+ Invoke `rdc:convo <template> <topic>`. Templates: `outcome-exploration`,
15
+ `design-comparison`, `troubleshooting`, `plan-critique`,
16
+ `disagreement-resolution`, `work-handoff`.
17
+ All six templates are included here so MCP callers receive the complete contract.
18
+ Use for a requested discussion, co-design or second opinion. Ordinary questions
19
+ do not need a peer round. Implementation stays in `rdc:plan` / `rdc:build`.
20
+
21
+ ## Shared contract — applies to every template
22
+
23
+ 1. Preserve the user's exact outcome, scope and permissions. Choose the closest
24
+ template; announce a template switch without resetting the round count.
25
+ Default budget: at most three completed rounds, up to 600 words per peer per
26
+ round, equal opportunity to respond. These are defaults, not authority to
27
+ shorten content the user explicitly requested. Finish early when resolved.
28
+ 2. Name participants by actual engine/person and session identity; use UNKNOWN
29
+ for unavailable identity or version. Roles (proposer, challenger, recorder,
30
+ decision owner) come from the task, not the engine brand. A workflow layer is
31
+ context, not a participant. No automatic Claude-supervisor/Codex-worker split.
32
+ 3. Build one source packet: verbatim user scope; template id and skill version
33
+ (plugin version and/or repository revision, never guessed);
34
+ repository/revision or document version; explicit file excerpts and observation
35
+ list with as-of times; assumptions; authorized decision owner; recorder and
36
+ record destination; round budget. Hash the exact UTF-8 packet with SHA-256.
37
+ Send identical bytes to each peer and the hash separately. Each echoes the
38
+ received hash; if it can independently hash the bytes, record that separately.
39
+ A mismatch pauses decision-making until corrected; do not count an echo as an
40
+ independent integrity check. Reissue a changed packet with a new hash to all.
41
+ 4. Use an existing verified transport: `rdc:co-develop` for addressed peer turns,
42
+ or `rdc:collab` for an already available native/relay participant. Load the
43
+ selected transport procedure. Reuse its send/reply and session correlation,
44
+ not its default role or write permissions. A person's reply in the current
45
+ user session is also a valid transport for that person. This contract grants no
46
+ implementation, commit, deployment, approval or task-completion authority.
47
+ Do not create another queue, scheduler or transport. Never invent a reply.
48
+ 5. Every turn carries discussion/session id, turn id, reply-to id, participant,
49
+ round, packet hash and as-of. Replies distinguish AGREE, AMEND, DISAGREE,
50
+ UNKNOWN and DEFER per point, with reasons and source references. Observed facts,
51
+ supplied-source analysis and inference stay distinct. Preserve exact peer text;
52
+ recorder summaries are separate and attributed. Silence is not agreement.
53
+ A supplied-source peer review is not a claim the peer ran tests or read files.
54
+ 6. Missing or delayed replies: inspect the same live process/session/turn handle.
55
+ A polling timeout is not terminal and does not authorize another dispatch.
56
+ Continue independent work while a confirmed live call runs. Report
57
+ PEER_UNAVAILABLE only after a terminal result or missing handle is established;
58
+ retain its evidence and unresolved points. Never replace a missing participant
59
+ with the coordinator's answer. Stop debate at its agreed budget, recording
60
+ unresolved positions rather than manufacturing consensus. An authorized
61
+ decision owner may close as UNRESOLVED while a call remains live, recording
62
+ its pending turn and handle. That does not mean PEER_UNAVAILABLE, cancel the
63
+ call, or authorize re-dispatch; the owner retains responsibility for its result.
64
+ 7. The user may name the recorder; otherwise the existing authorized coordinator
65
+ records within the task's project. Only that recorder writes the discussion
66
+ artifact, at the established project decision/report location. No authorized
67
+ destination means return the record inline without a file write. Retain turns
68
+ append-only within the record; later corrections reference the original turn.
69
+ Share only task-relevant, authorized sources; omit credentials and unrelated
70
+ private content. A durable decision contains the packet/version, attributed
71
+ turns, accepted and rejected proposals with reasons, dissent, uncertainty,
72
+ decision owner, artifact owner, next action and evidence required.
73
+ 8. Label the outcome AGREED, DECIDED_WITH_DISSENT, UNRESOLVED or PEER_UNAVAILABLE.
74
+ Only explicit assent supports AGREED. An authorized owner may decide without
75
+ consensus, retaining the other position. A disagreement never grants new
76
+ authority. For reversible in-scope decisions use existing delegation; ask the
77
+ user or authorizing principal
78
+ only when authorization or a material choice is genuinely missing.
79
+ 9. Handoff proposes a bounded plan/build brief with accepted constraints, exact
80
+ artifacts, ownership and failing acceptance checks. If execution is already
81
+ authorized, the coordinator continues through the existing governed workflow;
82
+ otherwise request that authority. Discussion does not close work items or
83
+ advance Flowable stages. In CDE-Zoe, save durable decisions/artifacts to the
84
+ authorized blackboard/project; retain conversation as transcript, not fabricated
85
+ workflow state. An unavailable persistence route is reported as unsaved.
86
+
87
+ ## Templates
88
+
89
+ Select one block and fill its inputs. The shared contract above supplies identity,
90
+ fairness, authority, recording and termination; do not duplicate or override it.
91
+
92
+ ### outcome-exploration
93
+ ```json
94
+ {
95
+ "template": "outcome-exploration",
96
+ "inputs": ["Exact desired change and beneficiary", "Company/program/project/topic scope, unknowns explicit", "Constraints and available observations"],
97
+ "process": ["Separate purpose and outcome from proposed implementation", "Compare observable success and non-goals; expose missing assumptions"],
98
+ "outputs": ["Outcome brief with acceptance evidence", "Non-goals and unresolved scope questions"],
99
+ "owner": "Authorized outcome owner; recorder names the brief owner",
100
+ "dissent": "Keep competing interpretations and whose evidence would distinguish them",
101
+ "handoff": "rdc:plan with the accepted outcome; unresolved material scope remains explicit"
102
+ }
103
+ ```
104
+
105
+ ### design-comparison
106
+ ```json
107
+ {
108
+ "template": "design-comparison",
109
+ "inputs": ["Accepted outcome and current architecture revision", "Candidate designs including reuse/no-change where viable", "Constraints and evaluation evidence"],
110
+ "process": ["Evaluate each candidate against the same outcome criteria", "Challenge assumptions, interfaces and failure/recovery behavior; state costs without invented numbers"],
111
+ "outputs": ["Chosen design or unresolved alternatives with rationale", "Required PRODUCT.md, DESIGN.md, WORKFLOW.md and plan changes, as applicable"],
112
+ "owner": "Authorized design decision owner; one owner per artifact",
113
+ "dissent": "Preserve rejected alternatives and the observation that could reopen the decision",
114
+ "handoff": "rdc:plan or existing design stage; no build admission from discussion alone"
115
+ }
116
+ ```
117
+
118
+ ### troubleshooting
119
+ ```json
120
+ {
121
+ "template": "troubleshooting",
122
+ "inputs": ["Observed symptom, time and exact affected surface", "Expected behavior and last known revision", "Logs/probes with secrets removed and hypotheses labeled"],
123
+ "process": ["Distinguish measured failure from theories", "Choose a discriminating safe probe and compare its actual result; do not repeat failed guesses"],
124
+ "outputs": ["Supported cause or remaining hypotheses", "Scoped repair proposal and regression that would fail before repair"],
125
+ "owner": "Investigation owner; separately named repair owner",
126
+ "dissent": "Retain contrary observations and unresolved causal claims",
127
+ "handoff": "Existing fix/build workflow for authorized repair; no service restart merely because observation timed out"
128
+ }
129
+ ```
130
+
131
+ ### plan-critique
132
+ ```json
133
+ {
134
+ "template": "plan-critique",
135
+ "inputs": ["User outcome and exact plan revision", "Checklist inputs, processing, outputs and evidence", "Architecture boundaries, dependencies and current implementation"],
136
+ "process": ["Trace each user requirement to a deliverable and failing check", "Challenge missing handoffs, recovery and unsupported claims; avoid padding the checklist"],
137
+ "outputs": ["Accepted plan amendments with exact affected sections", "Uncovered requirements and verification gaps"],
138
+ "owner": "Plan owner records amendments; independent reviewer retains attribution",
139
+ "dissent": "Keep disputed acceptance criteria and the evidence needed to resolve them",
140
+ "handoff": "rdc:plan amendment, then rdc:build only under existing authorization and admission"
141
+ }
142
+ ```
143
+
144
+ ### disagreement-resolution
145
+ ```json
146
+ {
147
+ "template": "disagreement-resolution",
148
+ "inputs": ["Exact disputed point and attributed positions", "Shared evidence and conflicting assumptions", "Decision owner, constraints and remaining round budget"],
149
+ "process": ["Each peer states the other's position for correction before rebuttal", "Identify a discriminating observation or explicit tradeoff; record an owner decision if consensus is absent"],
150
+ "outputs": ["Agreement, decision with dissent, or unresolved point", "Reopening condition and smallest next evidence step"],
151
+ "owner": "Existing authorized decision owner, never a winner inferred from engine identity",
152
+ "dissent": "Retain each final position; no majority vote or silence masquerading as assent",
153
+ "handoff": "Bounded evidence task or authorized plan decision; escalation only for a genuinely missing authority"
154
+ }
155
+ ```
156
+
157
+ ### work-handoff
158
+ ```json
159
+ {
160
+ "template": "work-handoff",
161
+ "inputs": ["Accepted outcome, decisions and unresolved constraints", "Exact repository, revision, work item and artifact locations", "Completed evidence, remaining work and permission boundaries"],
162
+ "process": ["Receiver restates scope, dependencies and next executable action", "Reconcile gaps without relabeling unverified work as complete"],
163
+ "outputs": ["Accepted handoff brief with one writer per artifact", "Remaining checks and restart instructions with current process handles if relevant"],
164
+ "owner": "Sender owns provenance; named receiver accepts future work explicitly",
165
+ "dissent": "Receiver lists unsupported claims, unavailable inputs and declined scope",
166
+ "handoff": "rdc:build for implementation or rdc:plan for unresolved planning; discussion never substitutes for review or validator closure"
167
+ }
168
+ ```
169
+
170
+ ## Exit check
171
+
172
+ - [ ] Selected template and shared packet identified.
173
+ - [ ] Each peer's received-hash echo recorded; mismatches resolved before deciding.
174
+ - [ ] Actual responses attributed; disagreement and uncertainty retained.
175
+ - [ ] Outcome labeled AGREED, DECIDED_WITH_DISSENT, UNRESOLVED or PEER_UNAVAILABLE.
176
+ - [ ] Decision owner and artifact owner explicit; no implied permission expansion.
177
+ - [ ] Record saved through an authorized route, or explicitly unsaved.
178
+ - [ ] Next action handed to the existing workflow; no false completion claim.
@@ -100,8 +100,8 @@ than glossed:
100
100
  The removed prose also carried real, hard-won operational knowledge — the
101
101
  `next start` `BUILD_ID` crash-loop, the `media.place.fund` hotlink referer
102
102
  allowlist, the `<head>` metadata audit, the lockfile-importer trap on a first
103
- promote to `main`. That knowledge is in git history for this file and in the
104
- named `.rdc/lessons/` entries it cited. **It was removed from here because prose
103
+ promote to `main`. That knowledge is in git history for this file, including the
104
+ lessons it cited. **It was removed from here because prose
105
105
  that duplicates behaviour drifts from it** — not because it stopped being true.
106
106
  Where such a check belongs in the pipeline, it belongs in the program or in the
107
107
  manager, where it runs, rather than in a document that hopes to be read.