@lifeaitools/rdc-skills 0.24.39 → 0.24.42

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.24.39",
3
+ "version": "0.24.42",
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/README.md CHANGED
@@ -200,8 +200,9 @@ Located in `guides/agents/` — plain markdown playbooks spawned as sub-agents:
200
200
  ### Deploy
201
201
  - rdc:deploy
202
202
 
203
- ### Release
204
- - rdc:release
203
+ ### Release
204
+ - rdc:release
205
+ - Release contract: [RELEASE.md](RELEASE.md)
205
206
 
206
207
  ### Dev Loop
207
208
  - rdc:fixit, rdc:collab, rdc:co-develop, rdc:edit
package/RELEASE.md ADDED
@@ -0,0 +1,42 @@
1
+ ---
2
+ doc_type: release-runbook
3
+ status: active
4
+ package: "@lifeaitools/rdc-skills"
5
+ ---
6
+
7
+ # Release rdc-skills
8
+
9
+ `@lifeaitools/rdc-skills` is released to npm and then installed into the active
10
+ LifeAI skill profile. This runbook is the required release contract for every
11
+ version.
12
+
13
+ ## Release checklist
14
+
15
+ 1. Reconcile the release branch with its remote and confirm the intended diff.
16
+ 2. Run `npm run validate`, focused deterministic tests, and an independent RDC
17
+ review for the candidate changes.
18
+ 3. Bump the package with `npm version patch --no-git-tag-version` (or the
19
+ approved minor or major equivalent).
20
+ 4. Commit the version files with the associated RDC work-item trailer and push
21
+ the release branch.
22
+ 5. Create and push `v<version>`; the publish workflow publishes the tagged
23
+ package to npm.
24
+ 6. Wait for the publish workflow to complete, then verify it with
25
+ `npm view @lifeaitools/rdc-skills version` and
26
+ `npm view @lifeaitools/rdc-skills@<version> version`.
27
+ 7. Inspect the registry artifact with
28
+ `npm pack @lifeaitools/rdc-skills@<version>` and verify the changed skill
29
+ files are present in the tarball.
30
+ 8. Install the published package with
31
+ `npm install -g @lifeaitools/rdc-skills@<version>`.
32
+ 9. Reinstall the active profile with `rdc-skills-install --profile core`; on a
33
+ workstation intentionally configured for the LIFEAI project layout and
34
+ services, use `rdc-skills-install --profile lifeai`.
35
+ 10. Verify the global package version, the active profile copy, and the skill
36
+ smoke check before closing the release work item.
37
+
38
+ ## Deployment boundary
39
+
40
+ The LifeAI profile installation is the development delivery for this library.
41
+ Application production promotion remains separately approval-gated through
42
+ `rdc:deploy promote`; this package runbook never promotes an application.
package/git-sha.json CHANGED
@@ -1,3 +1,3 @@
1
1
  {
2
- "sha": "53c79709138b1fc115a31d82670fcb15fc80a205"
2
+ "sha": "3bc908e850bac783d6c90ab040d081e4c763274a"
3
3
  }
@@ -62,8 +62,8 @@ id: <YYYY-MM-DD>-<skill>-<short-slug>
62
62
  date: "<YYYY-MM-DD>"
63
63
  skill: build | deploy | overnight | fixit | plan | preplan | review | release | collab
64
64
  session: <session-id or short ref>
65
- scope: simple | architectural # triage routing — see § Scope gate
66
- status: open | triaged | applied | wont-fix
65
+ scope: simple | architectural # triage routing — see § Scope gate
66
+ lesson_status: open | triaged | applied | wont-fix
67
67
  area: infra | skill | guide | rule | schema | ui | content | other
68
68
  links:
69
69
  commits: [] # SHAs that relate to the lesson
@@ -78,20 +78,31 @@ links:
78
78
  <one paragraph — the evidenced cause, not a guess>
79
79
 
80
80
  ## The fix / rule
81
- <what should change so this never recurs: a rule edit, skill-doc line, code change,
82
- or a check. If already applied in the same run, say so and link the commit.>
81
+ <what should change so this never recurs: a rule edit, skill-doc line, code change,
82
+ or a check. Cite a same-run related commit as context when useful, but keep
83
+ lesson_status: open until the weekly triage audit records the final outcome.>
83
84
  ```
84
85
 
85
- `scope` is the single most important field — it routes triage:
86
+ `scope` is the single most important field — it routes triage:
86
87
 
87
88
  - **`simple`** — a doc line, a one-file fix, a config tweak, a clarifying sentence in a
88
- skill, a missing grep guard. Housekeeping applies these directly.
89
+ skill, a missing grep guard. Housekeeping routes these through `rdc:fixit` or
90
+ `rdc:plan` -> `rdc:build`; it never applies them outside an RDC work item.
89
91
  - **`architectural`** — anything matching `.claude/rules/architectural-change-approval.md`
90
92
  (rule/CLAUDE.md/ARCHITECTURE.md edits, cross-cutting refactors, schema reshape, public
91
93
  API/MCP changes, skill-contract changes affecting multiple skills). Housekeeping does
92
94
  NOT apply these; it surfaces them via `AskUserQuestion` for explicit approval first.
93
95
 
94
- When unsure, mark `architectural`.
96
+ When unsure, mark `architectural`.
97
+
98
+ ### Legacy status migration
99
+
100
+ Older lesson files may use `status` instead of `lesson_status`. During weekly
101
+ intake, before filtering or clustering, normalize every lesson that has
102
+ `status` and no `lesson_status` by moving the unchanged value to
103
+ `lesson_status` and removing the legacy key. Record each migration in the
104
+ weekly report. Do not reinterpret a legacy value or create a work item merely
105
+ because it was migrated.
95
106
 
96
107
  ---
97
108
 
@@ -116,30 +127,25 @@ in a rule/guide. If a durable user preference or correction was involved, also w
116
127
  At the end of a long skill run, before the final verdict line:
117
128
 
118
129
  1. Decide if anything qualifies (§ When to capture). If not, write nothing and move on.
119
- 2. For each lesson, write `.rdc/lessons/<date>-<skill>-<slug>.md` using the schema above.
120
- Set `status: open` (or `applied` if you already shipped the fix in this same run, with
121
- the commit linked).
130
+ 2. For each lesson, write `.rdc/lessons/<date>-<skill>-<slug>.md` using the schema above.
131
+ Set `lesson_status: open`; a captured lesson is never self-marked as applied.
122
132
  3. Set `scope` honestly (`simple` vs `architectural`).
123
133
  4. Commit the lesson file(s) on `develop` alongside the run's other commits.
124
134
  5. Mention in the verdict/summary that N lessons were captured.
125
135
 
126
136
  ---
127
137
 
128
- ## Triage procedure (rdc:housekeeping, weekly)
129
-
130
- `rdc:housekeeping` adds a **Lessons triage** phase:
131
-
132
- 1. Read all `.rdc/lessons/*.md` with `status: open`.
133
- 2. Cluster by `area` + root-cause similarity (dedupe repeats into one fix).
134
- 3. For each cluster:
135
- - `scope: simple` apply the fix directly (rule line, skill-doc edit, config, guard),
136
- commit it, set the lesson(s) `status: applied` and link the commit.
137
- - `scope: architectural` do NOT edit. Present the issue + options via
138
- `AskUserQuestion` (per `architectural-change-approval.md`). On approval, apply via the
139
- correct lifecycle (rdc-skills tag/push for skills; cited commit for rules) and set
140
- `status: applied`. If deferred, set `status: triaged` and spawn a `work_item`.
141
- - Not worth fixing → `status: wont-fix` with a one-line reason.
142
- 4. Summarize in the housekeeping report: captured / applied / escalated / deferred counts.
138
+ ## Triage procedure (rdc:housekeeping, weekly)
139
+
140
+ `rdc:housekeeping` uses this strict order. It prevents duplicate plans and fixits, gathers every architectural answer before changes, and keeps each executable change inside an RDC work item.
141
+
142
+ 1. Normalize legacy `status` fields as described above, then read all `.rdc/lessons/*.md` with `lesson_status: open` and cluster by `area` + root-cause similarity.
143
+ 2. **Resolution audit before routing:** for every cluster, inspect the relevant code, rules, skills, guides, tests, recent commits, linked work items, and existing mitigations. Record what was inspected, the evidence, and one result: `already-fixed`, `sufficiently-mitigated`, or `still-open`. Do not create an `rdc:fixit`, `rdc:plan`, or `rdc:build` item before this audit.
144
+ 3. Resolve no-work clusters from the audit: link the prior commit and set `lesson_status: applied` for `already-fixed`; set `lesson_status: wont-fix` for a sufficient mitigation with its remaining-risk reason. Leave partially mitigated clusters open.
145
+ 4. **Architectural report and interview:** before any file changes, report every still-open architectural decision with options, tradeoffs, recommendation, risks, and audit evidence. Create the complete interview list, ask each required question in attended mode, and record every question, answer, decision, rationale, and affected cluster. Gather all answers before the first fix. In unattended mode, create deduplicated `human_items` decision records and defer unresolved choices.
146
+ 5. **RDC routing:** route each approved still-open cluster through a complete work item and either `rdc:fixit` (only under its scope limit; it creates the sole work item) or `rdc:plan` -> `rdc:build`. Include cluster and lesson ids in the fixit description or planned task. No direct edits are allowed. Complete the required checklist, implementation report, review, validator closure, commit, and push. Deploy deployable targets to dev through RDC and record the evidence; record `not applicable` for non-deployable work.
147
+ 6. Run `rdc:review` across each completed action batch. Mark lessons `lesson_status: applied` only after review passes and the linked commit is pushed; mark deferred and declined clusters `triaged` or `wont-fix` with their linked evidence.
148
+ 7. Write the full weekly lessons report with cluster audit, architectural report, interview Q&A, RDC action register, deployment evidence, and counts for open, deduped, already fixed, mitigated, applied, triaged, wont-fix, deferred, fixits, builds, review passes, and dev deployments.
143
149
 
144
150
  Lessons are never silently deleted — `applied` and `wont-fix` files stay as the audit trail.
145
151
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lifeaitools/rdc-skills",
3
- "version": "0.24.39",
3
+ "version": "0.24.42",
4
4
  "description": "RDC typed-agent dispatch skill suite for Claude Code - plan, build, review, overnight builds",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -43,6 +43,7 @@
43
43
  "test:curl-surface:remote": "node tests/curl-surface.test.mjs --remote",
44
44
  "test:channel-formatter": "node tests/channel-formatter.contract.test.mjs",
45
45
  "test:channel-formatter:remote": "node tests/channel-formatter.contract.test.mjs --remote",
46
+ "test:brochure": "node tests/rdc-brochure.test.mjs",
46
47
  "mcp": "node bin/rdc-skills-mcp.mjs",
47
48
  "rebuild-mcp": "node scripts/rebuild-mcp.mjs",
48
49
  "prepack": "node scripts/prepack.mjs"
@@ -126,6 +126,7 @@ async function loadPuppeteer() {
126
126
  // If the lib cannot be resolved or installed, fall back to a platform-native
127
127
  // extractor: `unzip` on POSIX, PowerShell `Expand-Archive` on win32.
128
128
  function loadAdmZip() {
129
+ if (process.env.RDC_BROCHURE_DISABLE_ADM_ZIP === '1') return null;
129
130
  try { return createRequire(import.meta.url)('adm-zip'); } catch {}
130
131
  // Reuse the puppeteer on-demand cache dir for an isolated local install.
131
132
  const cacheDir = join(homedir(), '.cache', 'rdc-brochure');
@@ -275,6 +276,16 @@ function listTemplates() {
275
276
  .sort();
276
277
  }
277
278
 
279
+ function stripLeadingYamlFrontMatter(md) {
280
+ const text = md.replace(/\r\n/g, '\n');
281
+ if (!text.startsWith('---\n')) return text;
282
+ const lines = text.split('\n');
283
+ for (let i = 1; i < lines.length; i++) {
284
+ if (/^---\s*$/.test(lines[i])) return lines.slice(i + 1).join('\n');
285
+ }
286
+ return text;
287
+ }
288
+
278
289
  async function composeFromFolder(root, file) {
279
290
  const tpl = join(TEMPLATES_DIR, `brochure-${opts.template}.html`);
280
291
  if (!existsSync(tpl)) {
@@ -298,12 +309,13 @@ async function composeFromFolder(root, file) {
298
309
 
299
310
  const sections = mdFiles.map((p) => {
300
311
  const md = readFileSync(p, 'utf8');
312
+ const bodyMd = stripLeadingYamlFrontMatter(md);
301
313
  // Only prepend the filename heading when the markdown has no leading ATX heading
302
314
  // of its own. Avoids a redundant <h2>filename</h2> stacked above a "# Title".
303
- const firstNonEmpty = md.replace(/\r\n/g, '\n').split('\n').find((l) => l.trim() !== '') || '';
315
+ const firstNonEmpty = bodyMd.replace(/\r\n/g, '\n').split('\n').find((l) => l.trim() !== '') || '';
304
316
  const hasLeadingHeading = /^#{1,6}\s+\S/.test(firstNonEmpty.trim());
305
317
  const heading = hasLeadingHeading ? '' : `<h2>${escapeHtml(basename(p, extname(p)))}</h2>`;
306
- return `<section class="brochure-section">${heading}${mdToHtml(md, dirname(p))}</section>`;
318
+ return `<section class="brochure-section">${heading}${mdToHtml(bodyMd, dirname(p))}</section>`;
307
319
  }).join('\n');
308
320
 
309
321
  html = html.replace(/\{\{TITLE\}\}/g, escapeHtml(title)).replace(/\{\{SECTIONS\}\}/g, sections);
@@ -447,6 +459,12 @@ function defaultOutPath() {
447
459
  }
448
460
  }
449
461
 
462
+ if (process.env.RDC_BROCHURE_COMPOSE_ONLY === '1') {
463
+ console.log(`HTML: ${htmlSource}`);
464
+ console.log(`Source: ${input} → ${htmlSource.startsWith('http') ? htmlSource : relative(process.cwd(), htmlSource)}`);
465
+ return;
466
+ }
467
+
450
468
  const outPath = await render(htmlSource);
451
469
  const size = statSync(outPath).size;
452
470
  let pages = '?';
@@ -560,4 +560,4 @@ NEVER run pnpm build or pnpm turbo. Use npx vitest run only.
560
560
 
561
561
  ## Capture lessons (exit step)
562
562
 
563
- 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.
563
+ 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 `lesson_status: open`; weekly triage alone records a final lesson outcome. 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.
@@ -236,4 +236,4 @@ If Dave types in this terminal during a turn:
236
236
 
237
237
  ## Capture lessons (exit step)
238
238
 
239
- 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>-collab-<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.
239
+ 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>-collab-<short-slug>.md` per lesson using the schema in that spec. Set `scope` (`simple` | `architectural`) and `lesson_status: open`; weekly triage alone records a final lesson outcome. 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.
@@ -522,7 +522,7 @@ When diagnosing a broken deploy in Mode 3: query `coolify_events` FIRST before r
522
522
 
523
523
  ## Capture lessons (exit step)
524
524
 
525
- 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>-deploy-<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.
525
+ 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>-deploy-<short-slug>.md` per lesson using the schema in that spec. Set `scope` (`simple` | `architectural`) and `lesson_status: open`; weekly triage alone records a final lesson outcome. 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.
526
526
 
527
527
  ## References
528
528
 
@@ -162,4 +162,4 @@ Report: what was fixed, file(s) changed, commit hash. One sentence.
162
162
 
163
163
  ## Capture lessons (exit step)
164
164
 
165
- 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>-fixit-<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.
165
+ 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>-fixit-<short-slug>.md` per lesson using the schema in that spec. Set `scope` (`simple` | `architectural`) and `lesson_status: open`; weekly triage alone records a final lesson outcome. 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.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: rdc:housekeeping
3
- description: "Usage `rdc:housekeeping [--fix]` — Weekly maintenance audit: directory structure verification, PUBLISH.md URL validation, CLAUDE.md freshness, orphan detection, places compliance, and stale version scan. Produces `.rdc/reports/YYYY-MM-DD-housekeeping.md`. With `--fix`, auto-remediate safe issues."
3
+ description: "Usage `rdc:housekeeping [--fix|--lessons]` — Weekly maintenance audit and lessons-only triage with a prior-fix audit, architectural interview gate, RDC work routing, and a complete report. Produces `.rdc/reports/YYYY-MM-DD-housekeeping.md`."
4
4
  ---
5
5
 
6
6
  > **⚠️ OUTPUT CONTRACT (READ FIRST):** `guides/output-contract.md`
@@ -21,13 +21,16 @@ description: "Usage `rdc:housekeeping [--fix]` — Weekly maintenance audit: dir
21
21
  - When disoriented about repo health
22
22
  - Called by `rdc:overnight` as a pre-flight if `--housekeeping` flag set
23
23
 
24
- ## Arguments
25
- - `rdc:housekeeping` — audit only, report issues
26
- - `rdc:housekeeping --fix` — auto-remediate safe issues (scaffold missing CLAUDE.md, fix PUBLISH.md URLs, create missing tracker dirs)
24
+ ## Arguments
25
+ - `rdc:housekeeping` — audit only, report issues
26
+ - `rdc:housekeeping --fix` — auto-remediate safe issues (scaffold missing CLAUDE.md, fix PUBLISH.md URLs, create missing tracker dirs)
27
+ - `rdc:housekeeping --lessons` — process only the weekly lessons triage workflow, including its audit, decision interview, routed work, and report
27
28
 
28
- ## Procedure
29
-
30
- ### 1. Directory Structure Verification
29
+ ## Procedure
30
+
31
+ When invoked with `--lessons`, skip every non-lessons maintenance section below (including Directory Structure Verification) and begin directly at **Lessons triage (weekly)**. Produce the lessons report only.
32
+
33
+ ### 1. Directory Structure Verification
31
34
 
32
35
  Scan every deployable target for required files:
33
36
 
@@ -158,28 +161,84 @@ Write to `.rdc/reports/YYYY-MM-DD-housekeeping.md`:
158
161
  ## Verdict: CLEAN / HAS_ISSUES
159
162
  ```
160
163
 
161
- ## Lessons triage (weekly)
162
-
163
- Read all `.rdc/lessons/*.md` with `status: open` (schema + procedure: `.rdc/guides/lessons-learned-spec.md` § Triage procedure). Cluster by `area` + root-cause similarity (dedupe repeats into one fix).
164
-
165
- ### Attended mode (default a human is present)
166
-
167
- For each cluster:
168
-
169
- - `scope: simple` → apply the fix directly (rule line, skill-doc edit, config, guard), commit it, set the lesson(s) `status: applied` with the commit linked.
170
- - `scope: architectural` do NOT edit. Present the issue + options via `AskUserQuestion` (per `.claude/rules/architectural-change-approval.md`). On approval, apply via the correct lifecycle (rdc-skills tag/push for skills; cited commit for rules) and set `status: applied`. If deferred, set `status: triaged` and spawn a `work_item`.
171
- - Not worth fixing `status: wont-fix` with a one-line reason.
164
+ ## Lessons triage (weekly)
165
+
166
+ Before filtering or clustering, read every candidate lesson **in full**. Normalize every
167
+ legacy lesson that has `status` but no `lesson_status` by moving its existing value to
168
+ `lesson_status` and record the migration in the report. Validate that each candidate has
169
+ exactly one frontmatter block, canonical `lesson_status`, and the required routing fields
170
+ (`id`, `date`, `skill`, `runtime`, `scope`, `area`, `needs_claude`, `links`); a malformed
171
+ record is itself a triage finding, never an invisible parser omission.
172
+
173
+ Before asking Dave a single question or routing any fix, write four synthesis artifacts in
174
+ the weekly report: (1) a correlation table accounting for **every** candidate lesson with its
175
+ remediation train, shared root cause, systemic fix, owner/lifecycle, and closure evidence;
176
+ (2) a remediation model that collapses recurring symptoms into the smallest control set and
177
+ execution order; (3) a resolution audit; and (4) one compact architectural-decision table
178
+ with options and a recommendation for each genuine trade-off. Do not make Dave reconstruct
179
+ the model from serial lesson prompts. This phase is a controlled weekly workflow: synthesize
180
+ first, then collect the complete architectural interview, then route approved work through
181
+ RDC. It must never create a duplicate plan or fixit for a lesson that is already fixed or
182
+ sufficiently mitigated.
183
+
184
+ ### 1. Cluster
185
+
186
+ Cluster by `area` + root-cause similarity and dedupe repeats into one proposed outcome. Give every cluster a stable identifier and list its lesson ids, area, evidenced root cause, proposed change, scope, and linked commits/work items.
187
+
188
+ ### 2. Resolution audit (mandatory before routing work)
189
+
190
+ For every cluster, inspect the relevant code, rules, skills, guides, tests, recent commits, linked work items, and existing mitigations before creating any `rdc:fixit`, `rdc:plan`, or `rdc:build` work.
191
+
192
+ Record auditable evidence and classify the cluster as exactly one of:
193
+
194
+ - `already-fixed` — the needed behavior is present and backed by a cited source location, test, or shipped commit; do not create new work.
195
+ - `sufficiently-mitigated` — an existing guard, process, or documentation reduces the risk enough that no additional change is justified; do not create new work and record the remaining risk.
196
+ - `still-open` — the evidence does not fully address the root cause; only this result may enter the routing phase.
197
+
198
+ The audit must name what was inspected, the evidence found or missing, the conclusion, and the reason no duplicate work was created. Mark an already-fixed lesson `lesson_status: applied` only when its prior commit is linked; mark a sufficiently-mitigated lesson `lesson_status: wont-fix` with the mitigation and remaining-risk reason. Keep partially mitigated clusters open for routing.
199
+
200
+ ### 3. Architectural report and interview gate
201
+
202
+ After the synthesis artifacts are complete, write the full architectural report for every `still-open` architectural cluster. For each one include the decision to make, affected systems, options and tradeoffs, recommendation, unresolved risks, and the result of the resolution audit. This produces the complete decision table and is the required gate before every routed fix.
203
+
204
+ Create a complete interview list from that report before changing any file. In attended mode, ask every required architectural question via `AskUserQuestion`, one question at a time, and record the question, answer, decision, rationale, and affected cluster. Do not start simple or architectural fixes until all interview questions have answers or the corresponding cluster is explicitly deferred. Silence is not approval.
205
+
206
+ If there are no architectural decisions, record `Architectural interview: none required` in the report before continuing. In unattended mode, do not guess: create the equivalent `human_items` decision records for every unresolved architectural question, link a work item, and defer those clusters.
207
+
208
+ ### 4. Route only still-open, approved work through RDC
209
+
210
+ After the resolution audit and the complete architectural interview, route every approved executable cluster through an RDC lifecycle with a complete work item, required checklist evidence, implementation report, review status, validator closure, commit, and push. Direct edits outside an RDC work item are forbidden.
211
+
212
+ - `scope: simple` — invoke `rdc:fixit` when the change is genuinely under five files and thirty minutes; `rdc:fixit` creates and completes the sole work item. Include the cluster and lesson ids in its description and action-register entry. Otherwise use `rdc:plan` followed by `rdc:build`.
213
+ - `scope: architectural` — use `rdc:plan` followed by `rdc:build` unless the approved change is genuinely within the `rdc:fixit` threshold. The plan must contain the approved interview decision and acceptance checks.
214
+ - Deferred or declined clusters — set `lesson_status: triaged` or `lesson_status: wont-fix`, link the work item or reason, and do not make an unapproved change.
215
+
216
+ Each routed wave must run the review gate: `rdc:fixit` performs its mandatory review and every batch also completes `rdc:review` before lessons are marked applied. Deploy a changed deployable target to dev through its normal RDC lifecycle and record the deployment evidence; document `not applicable` for documentation-only, rule-only, or skill-only work.
217
+
218
+ ### 5. Complete weekly lessons report
219
+
220
+ Add a `## Weekly Lessons Triage` section to `.rdc/reports/YYYY-MM-DD-housekeeping.md` with:
221
+
222
+ 1. `### Cluster and resolution audit` — cluster ids, lessons, area/cause, inspected evidence, `already-fixed` / `sufficiently-mitigated` / `still-open` result, and duplicate-work decision.
223
+ 2. `### Architectural report` — each decision, options, tradeoffs, recommendation, risks, and audit result.
224
+ 3. `### Architectural interview` — the complete question-and-answer list, decisions, deferrals, and clusters affected; explicitly state when none was required.
225
+ 4. `### RDC action register` — work item, selected lifecycle (`rdc:fixit` or `rdc:plan` -> `rdc:build`), test/review evidence, commit/push, dev deployment or `not applicable`, and final lesson status.
226
+ 5. `### Outcome counts` — open, deduped, already fixed, sufficiently mitigated, applied, triaged, wont-fix, deferred, fixits, builds, review passes, and dev deployments.
227
+
228
+ ### Attended mode (default — a human is present)
229
+
230
+ The attended run follows sections 1 through 5 in order. The full interview list and all answers are collected before the first routed fix starts; then batch approved fixes by lifecycle and target. Set `lesson_status: applied` only after the routed work item has passed review, the commit is pushed, required dev deployment evidence is recorded, and the commit is linked.
172
231
 
173
232
  ### Unattended mode (no human — overnight / cron / `rdc:overnight`)
174
233
 
175
- When the weekly triage runs unattended, follow `.rdc/guides/lessons-learned-spec.md` § **Triage procedure — UNATTENDED weekly mode** — do not run `AskUserQuestion`. In brief (the spec is authoritative):
234
+ When the weekly triage runs unattended, perform the same cluster and resolution audit before any routing, then follow `.rdc/guides/lessons-learned-spec.md` § **Triage procedure — UNATTENDED weekly mode** — do not run `AskUserQuestion`. In brief (the spec is authoritative):
176
235
 
177
236
  - **Per-difficulty model routing** (reuses the `rdc:build` table): the run is led by `claude-opus-4-8` for clustering + scope/difficulty triage; mechanical apply → `claude-sonnet-4-6`; harder multi-file/migration fix → `claude-opus-4-6`; design/architectural fix → `claude-opus-4-8`.
178
- - `scope: simple` → apply directly or via `rdc:fixit`, commit, `status: applied`.
179
- - `scope: architectural` with a single clear correct fix (records an already-learned lesson — e.g. "document X", "add a gate", update a guide) → route through `rdc:plan` → `rdc:build` (or `rdc:fixit` if genuinely <5 files), update the documentation, set `status: applied`/`triaged`.
180
- - `scope: architectural` **and genuinely ambiguous** (multiple valid approaches, real tradeoffs) → write a `human_items` row (`item_type='decision'`, with options in `suggested_agent_prompt`, `source_type='lesson'`, `source_fingerprint` for dedupe), set `status: triaged`, spawn + link a `work_item`. Decided in the morning. This is the asynchronous equivalent of the attended interview and honors `.claude/rules/architectural-change-approval.md`.
181
-
182
- Never delete lesson files — `applied` and `wont-fix` stay as the audit trail. Report captured / applied / escalated / deferred counts in the housekeeping report.
237
+ - `scope: simple` → create a complete work item and route via `rdc:fixit` or `rdc:plan` -> `rdc:build`; never apply directly.
238
+ - `scope: architectural` with a single clear correct fix (records an already-learned lesson — e.g. "document X", "add a gate", update a guide) → route through `rdc:plan` → `rdc:build` (or `rdc:fixit` if genuinely <5 files), update the documentation, set `lesson_status: applied`/`triaged`.
239
+ - `scope: architectural` **and genuinely ambiguous** (multiple valid approaches, real tradeoffs) → write a `human_items` row (`item_type='decision'`, with options in `suggested_agent_prompt`, `source_type='lesson'`, `source_fingerprint` for dedupe), set `lesson_status: triaged`, spawn + link a `work_item`. Decided in the morning. This is the asynchronous equivalent of the attended interview and honors `.claude/rules/architectural-change-approval.md`.
240
+
241
+ Never delete lesson files — `applied` and `wont-fix` stay as the audit trail.
183
242
 
184
243
  ## Rules
185
244
  - Never run `pnpm build` — not needed for this audit
@@ -248,4 +248,4 @@ Provide the advisor with:
248
248
 
249
249
  ## Capture lessons (exit step)
250
250
 
251
- 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>-overnight-<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.
251
+ 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>-overnight-<short-slug>.md` per lesson using the schema in that spec. Set `scope` (`simple` | `architectural`) and `lesson_status: open`; weekly triage alone records a final lesson outcome. 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.
@@ -342,4 +342,4 @@ designing topology. Do not reach for the REMOTE rule by default.
342
342
 
343
343
  ## Capture lessons (exit step)
344
344
 
345
- 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>-plan-<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.
345
+ 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>-plan-<short-slug>.md` per lesson using the schema in that spec. Set `scope` (`simple` | `architectural`) and `lesson_status: open`; weekly triage alone records a final lesson outcome. 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.
@@ -87,4 +87,4 @@ direction if given. If advisor cannot resolve, log and skip to next step.
87
87
 
88
88
  ## Capture lessons (exit step)
89
89
 
90
- 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>-preplan-<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.
90
+ 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>-preplan-<short-slug>.md` per lesson using the schema in that spec. Set `scope` (`simple` | `architectural`) and `lesson_status: open`; weekly triage alone records a final lesson outcome. 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.
@@ -137,4 +137,4 @@ Use `--profile lifeai` only on a workstation that intentionally has the LIFEAI p
137
137
 
138
138
  ## Capture lessons (exit step)
139
139
 
140
- 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>-release-<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.
140
+ 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>-release-<short-slug>.md` per lesson using the schema in that spec. Set `scope` (`simple` | `architectural`) and `lesson_status: open`; weekly triage alone records a final lesson outcome. 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.
@@ -149,4 +149,4 @@ description: "Usage `rdc:review [--unattended]` — Post-build quality gate: tsc
149
149
 
150
150
  ## Capture lessons (exit step)
151
151
 
152
- 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>-review-<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.
152
+ 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>-review-<short-slug>.md` per lesson using the schema in that spec. Set `scope` (`simple` | `architectural`) and `lesson_status: open`; weekly triage alone records a final lesson outcome. 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.
@@ -15,10 +15,14 @@
15
15
  "acceptance": {
16
16
  "output_contains": [
17
17
  "Housekeeping",
18
- "PUBLISH.md",
19
- "CLAUDE.md",
20
- "orphan",
21
- ".rdc/reports"
18
+ "PUBLISH.md",
19
+ "CLAUDE.md",
20
+ "orphan",
21
+ ".rdc/reports",
22
+ "resolution audit",
23
+ "architectural report",
24
+ "interview",
25
+ "RDC action register"
22
26
  ],
23
27
  "output_not_contains": [
24
28
  "auto-remediate",
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env node
2
+ import assert from 'node:assert/strict';
3
+ import { readFileSync } from 'node:fs';
4
+ import { dirname, join, resolve } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+
7
+ const root = resolve(dirname(fileURLToPath(import.meta.url)), '..');
8
+ const skill = readFileSync(join(root, 'skills', 'housekeeping', 'SKILL.md'), 'utf8');
9
+ const guide = readFileSync(join(root, 'guides', 'lessons-learned-spec.md'), 'utf8');
10
+
11
+ function mustAppearInOrder(text, fragments, name) {
12
+ let previous = -1;
13
+ for (const fragment of fragments) {
14
+ const index = text.indexOf(fragment);
15
+ assert.notEqual(index, -1, `${name} is missing: ${fragment}`);
16
+ assert.ok(index > previous, `${name} orders ${fragment} before its required predecessor`);
17
+ previous = index;
18
+ }
19
+ }
20
+
21
+ assert.match(skill, /canonical `lesson_status`/, 'housekeeping must normalize to the current lesson status field');
22
+ assert.doesNotMatch(skill, /(?:set|mark) `status: (?:open|triaged|applied|wont-fix)/, 'housekeeping must use lesson_status for triage outcomes');
23
+ assert.match(skill, /do not create new work/i, 'the audit must prevent duplicate work');
24
+ assert.doesNotMatch(skill, /scope: simple.*apply the fix directly/s, 'simple lessons cannot bypass RDC routing');
25
+ mustAppearInOrder(skill, [
26
+ '### 1. Cluster',
27
+ '### 2. Resolution audit',
28
+ '### 3. Architectural report and interview gate',
29
+ '### 4. Route only still-open, approved work through RDC',
30
+ '### 5. Complete weekly lessons report',
31
+ ], 'housekeeping workflow');
32
+ assert.match(skill, /complete work item/, 'routed work must have a complete work item');
33
+ assert.match(skill, /rdc:fixit/, 'housekeeping must route small work through fixit');
34
+ assert.match(skill, /rdc:fixit` creates and completes the sole work item/, 'fixit must own its single work item');
35
+ assert.match(skill, /rdc:plan` followed by `rdc:build/, 'housekeeping must route larger work through build');
36
+ assert.match(skill, /rdc:review/, 'each lesson action batch must be reviewed');
37
+ assert.match(skill, /dev deployment/, 'deployable work must record dev deployment evidence');
38
+ assert.match(skill, /question-and-answer list/, 'the weekly report must preserve architectural interview answers');
39
+
40
+ assert.match(guide, /lesson_status: open \| triaged \| applied \| wont-fix/, 'guide schema must match housekeeping');
41
+ mustAppearInOrder(guide, [
42
+ 'Resolution audit before routing',
43
+ 'Architectural report and interview',
44
+ 'RDC routing',
45
+ 'Run `rdc:review`',
46
+ ], 'shared lessons guide');
47
+ assert.match(guide, /Do not create an `rdc:fixit`, `rdc:plan`, or `rdc:build` item before this audit/, 'guide must block premature routing');
48
+
49
+ console.log('housekeeping weekly lessons triage contract - PASS');
@@ -0,0 +1,27 @@
1
+ import assert from 'node:assert/strict';
2
+ import { readFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+
6
+ const root = join(fileURLToPath(new URL('..', import.meta.url)));
7
+ const captureSkills = ['build', 'deploy', 'overnight', 'fixit', 'plan', 'preplan', 'review', 'release', 'collab'];
8
+
9
+ for (const skill of captureSkills) {
10
+ const text = readFileSync(join(root, 'skills', skill, 'SKILL.md'), 'utf8');
11
+ assert.match(text, /lesson_status: open/, `${skill} must capture the canonical status key`);
12
+ assert.doesNotMatch(text, /and `status` \(`open`/, `${skill} must not emit the legacy status key`);
13
+ }
14
+
15
+ const guide = readFileSync(join(root, 'guides', 'lessons-learned-spec.md'), 'utf8');
16
+ assert.match(guide, /Legacy status migration/);
17
+ assert.match(guide, /`status` and no `lesson_status`/);
18
+
19
+ const housekeeping = readFileSync(join(root, 'skills', 'housekeeping', 'SKILL.md'), 'utf8');
20
+ assert.match(housekeeping, /When invoked with `--lessons`, skip every non-lessons maintenance section/);
21
+ assert.match(housekeeping, /including Directory Structure Verification/);
22
+ const legacyNormalization = housekeeping.search(/normalize every\s+legacy lesson that has `status` but no `lesson_status`/i);
23
+ const clustering = housekeeping.search(/### 1\. Cluster/);
24
+ assert.ok(legacyNormalization >= 0 && clustering > legacyNormalization, 'legacy lessons must be normalized before clustering');
25
+ assert.match(housekeeping, /all answers are collected before the first routed fix starts/i);
26
+
27
+ console.log('lessons pipeline contract test passed');
@@ -0,0 +1,125 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawnSync } from 'node:child_process';
4
+ import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
5
+ import { tmpdir } from 'node:os';
6
+ import { dirname, join, resolve } from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
8
+
9
+ const __dirname = dirname(fileURLToPath(import.meta.url));
10
+ const REPO_ROOT = resolve(__dirname, '..');
11
+ const SCRIPT = join(REPO_ROOT, 'scripts', 'rdc-brochure.mjs');
12
+
13
+ let failures = 0;
14
+ function assert(cond, msg) {
15
+ if (cond) console.log(` ✓ ${msg}`);
16
+ else {
17
+ failures += 1;
18
+ console.error(` ✗ ${msg}`);
19
+ }
20
+ }
21
+
22
+ function runBrochure(args, env = {}) {
23
+ return spawnSync(process.execPath, [SCRIPT, ...args], {
24
+ cwd: REPO_ROOT,
25
+ encoding: 'utf8',
26
+ env: {
27
+ ...process.env,
28
+ RDC_BROCHURE_COMPOSE_ONLY: '1',
29
+ ...env,
30
+ },
31
+ });
32
+ }
33
+
34
+ function composedHtmlPath(stdout) {
35
+ const m = String(stdout || '').match(/^HTML:\s+(.+)$/m);
36
+ return m ? m[1].trim() : null;
37
+ }
38
+
39
+ function crc32(buf) {
40
+ let crc = 0xffffffff;
41
+ for (const byte of buf) {
42
+ crc ^= byte;
43
+ for (let i = 0; i < 8; i++) crc = (crc >>> 1) ^ (0xedb88320 & -(crc & 1));
44
+ }
45
+ return (crc ^ 0xffffffff) >>> 0;
46
+ }
47
+
48
+ function u16(n) {
49
+ const b = Buffer.alloc(2);
50
+ b.writeUInt16LE(n);
51
+ return b;
52
+ }
53
+
54
+ function u32(n) {
55
+ const b = Buffer.alloc(4);
56
+ b.writeUInt32LE(n >>> 0);
57
+ return b;
58
+ }
59
+
60
+ function writeStoredZip(zipPath, entryName, content) {
61
+ const name = Buffer.from(entryName);
62
+ const body = Buffer.from(content);
63
+ const crc = crc32(body);
64
+ const local = Buffer.concat([
65
+ u32(0x04034b50), u16(20), u16(0), u16(0), u16(0), u16(0),
66
+ u32(crc), u32(body.length), u32(body.length), u16(name.length), u16(0),
67
+ name, body,
68
+ ]);
69
+ const centralOffset = local.length;
70
+ const central = Buffer.concat([
71
+ u32(0x02014b50), u16(20), u16(20), u16(0), u16(0), u16(0), u16(0),
72
+ u32(crc), u32(body.length), u32(body.length), u16(name.length), u16(0),
73
+ u16(0), u16(0), u16(0), u32(0), u32(0), name,
74
+ ]);
75
+ const end = Buffer.concat([
76
+ u32(0x06054b50), u16(0), u16(0), u16(1), u16(1),
77
+ u32(central.length), u32(centralOffset), u16(0),
78
+ ]);
79
+ writeFileSync(zipPath, Buffer.concat([local, central, end]));
80
+ }
81
+
82
+ const root = mkdtempSync(join(tmpdir(), 'rdc-brochure-test-'));
83
+
84
+ try {
85
+ console.log('Test 1: folder compose suppresses synthetic headings after YAML front matter');
86
+ const folder = join(root, 'frontmatter-folder');
87
+ mkdirSync(folder, { recursive: true });
88
+ writeFileSync(join(folder, '01-overview.md'), '---\ntitle: Overview\n---\n\n# Real Title\n\nBody.\n', 'utf8');
89
+ writeFileSync(join(folder, '02-h6.md'), '###### Small Heading\n\nTiny body.\n', 'utf8');
90
+ const composed = runBrochure([folder]);
91
+ assert(composed.status === 0, `compose-only folder exits 0 (got ${composed.status})`);
92
+ const htmlPath = composedHtmlPath(composed.stdout);
93
+ assert(htmlPath && existsSync(htmlPath), 'compose-only prints a readable HTML path');
94
+ const html = htmlPath ? readFileSync(htmlPath, 'utf8') : '';
95
+ assert(!html.includes('<h2>01-overview</h2>'), 'front-matter-led # heading has no duplicate filename h2');
96
+ assert(!html.includes('<h2>02-h6</h2>'), 'ATX h6 heading has no duplicate filename h2');
97
+ assert(html.includes('<h1>Real Title</h1>'), 'front-matter body still renders the real h1');
98
+
99
+ console.log('Test 2: invalid templates fail for folder and markdown compose paths');
100
+ const folderBad = runBrochure([folder, '--template', 'missing-template']);
101
+ assert(folderBad.status !== 0, 'folder compose with invalid template exits non-zero');
102
+ assert(/unknown template "missing-template"/.test(folderBad.stderr), 'folder compose reports unknown template');
103
+ assert(/Available template\(s\): studio-default/.test(folderBad.stderr), 'folder compose lists available templates');
104
+ const mdFile = join(root, 'single.md');
105
+ writeFileSync(mdFile, '# Single\n\nBody.\n', 'utf8');
106
+ const mdBad = runBrochure([mdFile, '--template', 'missing-template']);
107
+ assert(mdBad.status !== 0, 'single markdown compose with invalid template exits non-zero');
108
+ assert(/unknown template "missing-template"/.test(mdBad.stderr), 'single markdown compose reports unknown template');
109
+
110
+ console.log('Test 3: native unzip fallback rejects zip-slip entries before extraction');
111
+ const zipPath = join(root, 'zip-slip.zip');
112
+ writeStoredZip(zipPath, '../evil.md', '# Evil\n');
113
+ const badZip = runBrochure([zipPath], { RDC_BROCHURE_DISABLE_ADM_ZIP: '1' });
114
+ assert(badZip.status !== 0, 'zip-slip archive exits non-zero');
115
+ assert(/zip-slip: entry/.test(badZip.stderr), 'zip-slip error is reported');
116
+ assert(!existsSync(join(root, 'evil.md')), 'zip-slip member was not extracted outside the destination');
117
+ } finally {
118
+ rmSync(root, { recursive: true, force: true });
119
+ }
120
+
121
+ if (failures > 0) {
122
+ console.error(`\nFAILED: ${failures} assertion(s)`);
123
+ process.exit(1);
124
+ }
125
+ console.log('\nALL PASS');
@@ -0,0 +1,16 @@
1
+ import assert from 'node:assert/strict';
2
+ import { readFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+
6
+ const root = join(fileURLToPath(new URL('..', import.meta.url)));
7
+ const release = readFileSync(join(root, 'RELEASE.md'), 'utf8');
8
+
9
+ assert.match(release, /npm version patch --no-git-tag-version/);
10
+ assert.match(release, /npm view @lifeaitools\/rdc-skills@<version> version/);
11
+ assert.match(release, /npm pack @lifeaitools\/rdc-skills@<version>/);
12
+ assert.match(release, /npm install -g @lifeaitools\/rdc-skills@<version>/);
13
+ assert.match(release, /rdc-skills-install --profile core/);
14
+ assert.match(release, /rdc-skills-install --profile lifeai/);
15
+
16
+ console.log('release contract test passed');