@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.
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +32 -0
- package/README.md +1 -0
- package/commands/compare-compliance.md +5 -1
- package/commands/quick-check.md +3 -1
- package/git-sha.json +1 -1
- package/guides/review-gate.md +133 -0
- package/hooks/require-work-item-on-commit.js +11 -0
- package/package.json +1 -1
- package/scripts/solid-score.mjs +35 -6
- package/skills/build/SKILL.md +24 -21
- package/skills/clean-code-analyzer/SKILL.md +6 -0
- package/skills/fixit/SKILL.md +21 -20
- package/skills/overnight/SKILL.md +1 -1
- package/skills/release/SKILL.md +1 -1
- package/skills/review/SKILL.md +39 -32
- package/tests/lib/solid-score-diff.test.mjs +103 -0
- package/tests/require-work-item-on-commit.test.mjs +9 -0
- package/.rdc/lessons/2026-08-17-fixit-codex-mcp-only-skills.md +0 -24
- package/.rdc/lessons/2026-09-17-release-mcp-registry-split.md +0 -34
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`.
|
package/commands/quick-check.md
CHANGED
|
@@ -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.
|
|
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
|
@@ -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
package/scripts/solid-score.mjs
CHANGED
|
@@ -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
|
|
155
|
-
// "
|
|
156
|
-
|
|
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
|
-
|
|
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)}`);
|
package/skills/build/SKILL.md
CHANGED
|
@@ -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
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
-
|
|
666
|
-
|
|
667
|
-
|
|
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
|
package/skills/fixit/SKILL.md
CHANGED
|
@@ -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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
-
|
|
130
|
-
|
|
131
|
-
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
|
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
|
|
package/skills/release/SKILL.md
CHANGED
|
@@ -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
|
|
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
|
package/skills/review/SKILL.md
CHANGED
|
@@ -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).
|
|
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 —
|
|
88
|
-
|
|
89
|
-
⛔ **No CLEAN verdict without a
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
|
116
|
-
|
|
117
|
-
shape
|
|
118
|
-
architecturally wrong
|
|
119
|
-
|
|
120
|
-
|
|
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.
|