@lifeaitools/rdc-skills 0.24.39 → 0.24.41
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/README.md +3 -2
- package/RELEASE.md +42 -0
- package/git-sha.json +1 -1
- package/guides/lessons-learned-spec.md +31 -25
- package/package.json +2 -1
- package/scripts/rdc-brochure.mjs +20 -2
- package/skills/build/SKILL.md +1 -1
- package/skills/collab/SKILL.md +1 -1
- package/skills/deploy/SKILL.md +1 -1
- package/skills/fixit/SKILL.md +1 -1
- package/skills/housekeeping/SKILL.md +83 -24
- package/skills/overnight/SKILL.md +1 -1
- package/skills/plan/SKILL.md +1 -1
- package/skills/preplan/SKILL.md +1 -1
- package/skills/release/SKILL.md +1 -1
- package/skills/review/SKILL.md +1 -1
- package/skills/tests/rdc-housekeeping.test.json +8 -4
- package/tests/housekeeping-lessons-triage.test.mjs +49 -0
- package/tests/lessons-pipeline-contract.test.mjs +26 -0
- package/tests/rdc-brochure.test.mjs +125 -0
- package/tests/release-contract.test.mjs +16 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rdc",
|
|
3
|
-
"version": "0.24.
|
|
3
|
+
"version": "0.24.41",
|
|
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
|
@@ -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
|
-
|
|
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.
|
|
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
|
|
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 `
|
|
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`
|
|
131
|
-
|
|
132
|
-
1.
|
|
133
|
-
2.
|
|
134
|
-
3.
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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.
|
|
3
|
+
"version": "0.24.41",
|
|
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"
|
package/scripts/rdc-brochure.mjs
CHANGED
|
@@ -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 =
|
|
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(
|
|
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 = '?';
|
package/skills/build/SKILL.md
CHANGED
|
@@ -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 `
|
|
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.
|
package/skills/collab/SKILL.md
CHANGED
|
@@ -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 `
|
|
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.
|
package/skills/deploy/SKILL.md
CHANGED
|
@@ -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 `
|
|
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
|
|
package/skills/fixit/SKILL.md
CHANGED
|
@@ -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 `
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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, but does **not** interrupt the simple remediation batch.
|
|
203
|
+
|
|
204
|
+
Create a complete interview list from that report before changing any architectural file. In attended mode, execute and verify the correlated simple batches first, then 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 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 work through RDC
|
|
209
|
+
|
|
210
|
+
After the resolution audit, route every simple 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. Route architectural clusters only after the complete architectural interview.
|
|
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, 2, and 4 for all simple clusters first, then section 3 for the complete architectural interview, then section 4 for approved architectural work, and finally section 5. 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` →
|
|
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 `
|
|
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 `
|
|
181
|
-
|
|
182
|
-
Never delete lesson files — `applied` and `wont-fix` stay as the audit trail.
|
|
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 `
|
|
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.
|
package/skills/plan/SKILL.md
CHANGED
|
@@ -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 `
|
|
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.
|
package/skills/preplan/SKILL.md
CHANGED
|
@@ -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 `
|
|
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.
|
package/skills/release/SKILL.md
CHANGED
|
@@ -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 `
|
|
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.
|
package/skills/review/SKILL.md
CHANGED
|
@@ -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 `
|
|
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, /lesson_status: open/, 'housekeeping must read 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,26 @@
|
|
|
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.indexOf('normalize every legacy lesson that has `status` but no `lesson_status`');
|
|
23
|
+
const openFilter = housekeeping.indexOf('Then read all `.rdc/lessons/*.md` with `lesson_status: open`');
|
|
24
|
+
assert.ok(legacyNormalization >= 0 && openFilter > legacyNormalization, 'legacy lessons must be normalized before the open-status filter');
|
|
25
|
+
|
|
26
|
+
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');
|