@lifeaitools/rdc-skills 0.24.38 → 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.
Files changed (196) hide show
  1. package/.claude/settings.json +15 -15
  2. package/.claude-plugin/marketplace.json +21 -21
  3. package/.claude-plugin/plugin.json +1371 -1371
  4. package/.github/workflows/publish.yml +34 -34
  5. package/.github/workflows/self-test.yml +58 -58
  6. package/CHANGELOG.md +310 -310
  7. package/LICENSE +21 -21
  8. package/MANIFEST.md +221 -221
  9. package/README.md +376 -375
  10. package/README.sandbox.md +3 -3
  11. package/RELEASE.md +42 -0
  12. package/assets/watcher/viewer.html +164 -164
  13. package/bin/rdc-skills-mcp.mjs +316 -316
  14. package/commands/build.md +183 -183
  15. package/commands/collab.md +180 -180
  16. package/commands/deploy.md +152 -152
  17. package/commands/design.md +31 -31
  18. package/commands/edit.md +28 -28
  19. package/commands/fixit.md +124 -124
  20. package/commands/handoff.md +173 -173
  21. package/commands/help.md +95 -95
  22. package/commands/overnight.md +220 -220
  23. package/commands/plan.md +158 -158
  24. package/commands/preplan.md +131 -131
  25. package/commands/prototype.md +145 -145
  26. package/commands/release.md +49 -49
  27. package/commands/report.md +99 -99
  28. package/commands/review.md +120 -120
  29. package/commands/self-test.md +113 -113
  30. package/commands/status.md +86 -86
  31. package/commands/watch.md +98 -98
  32. package/commands/workitems.md +137 -137
  33. package/git-sha.json +1 -1
  34. package/guides/agent-bootstrap.md +295 -295
  35. package/guides/agents/backend.md +104 -104
  36. package/guides/agents/content.md +94 -94
  37. package/guides/agents/cs2.md +56 -56
  38. package/guides/agents/data.md +87 -87
  39. package/guides/agents/design.md +77 -77
  40. package/guides/agents/frontend.md +92 -92
  41. package/guides/agents/infrastructure.md +81 -81
  42. package/guides/agents/setup.md +281 -281
  43. package/guides/agents/verify.md +151 -151
  44. package/guides/agents/viz.md +106 -106
  45. package/guides/backend.md +146 -146
  46. package/guides/content.md +147 -147
  47. package/guides/cs2.md +190 -190
  48. package/guides/data.md +123 -123
  49. package/guides/design.md +116 -116
  50. package/guides/engineering-behavior.md +43 -43
  51. package/guides/escalation-protocol.md +125 -125
  52. package/guides/frontend.md +151 -151
  53. package/guides/history-md-spec.md +297 -297
  54. package/guides/infrastructure.md +179 -179
  55. package/guides/lessons-learned-spec.md +151 -145
  56. package/guides/output-contract.md +108 -108
  57. package/guides/publish-md-spec.md +289 -289
  58. package/guides/rdc-skills-startup.md +30 -30
  59. package/guides/verify.md +11 -11
  60. package/hooks/check-cwd.js +31 -31
  61. package/hooks/check-rdc-environment.js +164 -164
  62. package/hooks/check-services.js +6 -6
  63. package/hooks/check-stale-work-items.js +19 -19
  64. package/hooks/foreground-process-gate.js +128 -128
  65. package/hooks/gate-watchdog-selfcheck.js +257 -257
  66. package/hooks/hook-logger.js +25 -25
  67. package/hooks/lib/run-evidence-gate.mjs +241 -241
  68. package/hooks/no-stop-open-epics.js +127 -127
  69. package/hooks/post-tool-batch-gate.js +203 -203
  70. package/hooks/post-work-check.js +21 -21
  71. package/hooks/postcompact-log.js +13 -13
  72. package/hooks/precompact-log.js +13 -13
  73. package/hooks/rate-limit-retry.js +46 -46
  74. package/hooks/rdc-invocation-marker.js +157 -157
  75. package/hooks/rdc-output-contract-gate.js +94 -94
  76. package/hooks/require-work-item-on-commit.js +294 -294
  77. package/hooks/restart-brief.js +19 -19
  78. package/hooks/run-hidden-hook.ps1 +47 -47
  79. package/hooks/task-completed-gate.js +274 -274
  80. package/hooks/work-item-exit-gate.js +944 -944
  81. package/lib/catalog.mjs +236 -236
  82. package/lib/cloud-rewrite.mjs +155 -155
  83. package/package.json +57 -56
  84. package/rules/work-items-rpc.md +520 -520
  85. package/scaffold/templates/HISTORY.md.template +39 -39
  86. package/scaffold/templates/PUBLISH.md.template +21 -21
  87. package/scaffold/templates/brochure-studio-default.html +70 -70
  88. package/scripts/acceptance.mjs +502 -502
  89. package/scripts/fixtures/guides/bad-guide.md +15 -15
  90. package/scripts/fixtures/guides-clean/good-guide.md +16 -16
  91. package/scripts/install-rdc-skills.js +1289 -1289
  92. package/scripts/install.ps1 +202 -202
  93. package/scripts/install.sh +132 -132
  94. package/scripts/lib/assertions.mjs +287 -287
  95. package/scripts/lib/manifest-schema.mjs +754 -754
  96. package/scripts/lib/runner.mjs +465 -465
  97. package/scripts/lib/sandbox.mjs +435 -435
  98. package/scripts/prepack.mjs +32 -32
  99. package/scripts/rdc-brochure.mjs +482 -464
  100. package/scripts/rdc-design-cli.mjs +134 -134
  101. package/scripts/rebuild-mcp.mjs +107 -107
  102. package/scripts/self-test.mjs +1460 -1460
  103. package/scripts/stamp-git-sha.mjs +29 -29
  104. package/scripts/test-guide-validator.mjs +196 -196
  105. package/scripts/test-rdc-hooks.mjs +145 -145
  106. package/scripts/uninstall.ps1 +77 -77
  107. package/scripts/uninstall.sh +69 -69
  108. package/scripts/update.ps1 +43 -43
  109. package/scripts/update.sh +43 -43
  110. package/scripts/validate-place-histories.js +461 -461
  111. package/scripts/validate-publish-manifests.js +424 -424
  112. package/scripts/watch-init.mjs +100 -100
  113. package/skills/brochure/SKILL.md +107 -107
  114. package/skills/build/SKILL.md +563 -563
  115. package/skills/channel-formatter/SKILL.md +533 -533
  116. package/skills/co-develop/SKILL.md +196 -196
  117. package/skills/collab/SKILL.md +239 -239
  118. package/skills/convert/SKILL.md +140 -140
  119. package/skills/deploy/SKILL.md +541 -541
  120. package/skills/design/SKILL.md +211 -211
  121. package/skills/design/reference/ownership.md +16 -16
  122. package/skills/design/reference/rampa.md +92 -92
  123. package/skills/design/reference/studio-model.md +153 -153
  124. package/skills/edit/SKILL.md +98 -98
  125. package/skills/fixit/SKILL.md +165 -165
  126. package/skills/fs-mcp/SKILL.md +148 -148
  127. package/skills/handoff/SKILL.md +236 -200
  128. package/skills/help/SKILL.md +143 -143
  129. package/skills/housekeeping/SKILL.md +219 -160
  130. package/skills/lifeai-brochure-author/SKILL.md +340 -340
  131. package/skills/overnight/SKILL.md +251 -251
  132. package/skills/plan/SKILL.md +345 -345
  133. package/skills/preplan/SKILL.md +90 -90
  134. package/skills/prototype/SKILL.md +150 -150
  135. package/skills/rdc-brochurify/SKILL.md +245 -245
  136. package/skills/rdc-extract-verifier-rules/SKILL.md +191 -191
  137. package/skills/release/SKILL.md +140 -140
  138. package/skills/report/SKILL.md +100 -100
  139. package/skills/review/SKILL.md +152 -152
  140. package/skills/rpms-filemap/SKILL.cloud.md +111 -111
  141. package/skills/rpms-filemap/SKILL.md +111 -111
  142. package/skills/self-test/SKILL.md +132 -132
  143. package/skills/status/SKILL.md +99 -99
  144. package/skills/terminal-config/SKILL.md +62 -62
  145. package/skills/tests/MATRIX.md +54 -54
  146. package/skills/tests/README.md +47 -47
  147. package/skills/tests/rdc-brochure.test.json +34 -34
  148. package/skills/tests/rdc-build.test.json +36 -36
  149. package/skills/tests/rdc-channel-formatter.test.json +45 -45
  150. package/skills/tests/rdc-co-develop.test.json +29 -29
  151. package/skills/tests/rdc-collab.test.json +29 -29
  152. package/skills/tests/rdc-convert.test.json +35 -35
  153. package/skills/tests/rdc-deploy.test.json +30 -30
  154. package/skills/tests/rdc-design.test.json +27 -27
  155. package/skills/tests/rdc-edit.test.json +29 -29
  156. package/skills/tests/rdc-fixit.test.json +36 -36
  157. package/skills/tests/rdc-fs-mcp.test.json +36 -36
  158. package/skills/tests/rdc-handoff.test.json +28 -28
  159. package/skills/tests/rdc-help.test.json +29 -29
  160. package/skills/tests/rdc-housekeeping.test.json +32 -28
  161. package/skills/tests/rdc-lifeai-brochure-author.test.json +35 -35
  162. package/skills/tests/rdc-overnight.test.json +37 -37
  163. package/skills/tests/rdc-plan.test.json +27 -27
  164. package/skills/tests/rdc-preplan.test.json +31 -31
  165. package/skills/tests/rdc-prototype.test.json +28 -28
  166. package/skills/tests/rdc-rdc-brochurify.test.json +23 -23
  167. package/skills/tests/rdc-rdc-extract-verifier-rules.test.json +34 -34
  168. package/skills/tests/rdc-release.test.json +29 -29
  169. package/skills/tests/rdc-report.test.json +28 -28
  170. package/skills/tests/rdc-review.test.json +29 -29
  171. package/skills/tests/rdc-rpms-filemap.test.json +28 -28
  172. package/skills/tests/rdc-self-test.test.json +24 -24
  173. package/skills/tests/rdc-status.test.json +29 -29
  174. package/skills/tests/rdc-terminal-config.test.json +29 -29
  175. package/skills/tests/rdc-watch.test.json +24 -24
  176. package/skills/tests/rdc-workitems.test.json +27 -27
  177. package/skills/watch/SKILL.md +97 -97
  178. package/skills/workitems/SKILL.md +151 -151
  179. package/tests/acceptance.test.mjs +59 -59
  180. package/tests/channel-formatter.contract.test.mjs +251 -251
  181. package/tests/curl-surface.test.mjs +289 -289
  182. package/tests/harness-gates.test.mjs +325 -325
  183. package/tests/help-surface.test.mjs +61 -61
  184. package/tests/housekeeping-lessons-triage.test.mjs +49 -0
  185. package/tests/install-rdc-skills.test.mjs +49 -49
  186. package/tests/lessons-pipeline-contract.test.mjs +26 -0
  187. package/tests/manifest-contract-fields.test.mjs +78 -78
  188. package/tests/mcp.test.mjs +271 -271
  189. package/tests/rdc-brochure.test.mjs +125 -0
  190. package/tests/release-contract.test.mjs +16 -0
  191. package/tests/require-work-item-on-commit.test.mjs +162 -162
  192. package/tests/run-evidence-gate.test.mjs +82 -82
  193. package/tests/skill-test-matrix.test.mjs +66 -66
  194. package/tests/validate-skills.js +27 -27
  195. package/tests/work-item-exit-gate-l2.test.mjs +368 -368
  196. package/tests/work-item-exit-gate-l3.test.mjs +197 -197
@@ -1,61 +1,61 @@
1
- #!/usr/bin/env node
2
- import assert from 'node:assert/strict';
3
- import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
4
- import { dirname, join, resolve } from 'node:path';
5
- import { fileURLToPath } from 'node:url';
6
-
7
- const __dirname = dirname(fileURLToPath(import.meta.url));
8
- const root = resolve(__dirname, '..');
9
-
10
- const files = {
11
- readme: join(root, 'README.md'),
12
- skillHelp: join(root, 'skills', 'help', 'SKILL.md'),
13
- commandHelp: join(root, 'commands', 'help.md'),
14
- };
15
-
16
- const docs = Object.fromEntries(
17
- Object.entries(files).map(([key, file]) => [key, readFileSync(file, 'utf8')]),
18
- );
19
-
20
- for (const [name, text] of Object.entries(docs)) {
21
- assert.match(text, /https:\/\/rdc-skills\.regendevcorp\.com\/mcp/, `${name} must expose production MCP endpoint`);
22
- assert.match(text, /Accept: application\/json, text\/event-stream/, `${name} must show Streamable HTTP Accept header`);
23
- assert.match(text, /data:/, `${name} must explain Streamable HTTP SSE data lines`);
24
- assert.match(text, /result\.content\[0\]\.text/, `${name} must explain where tool text lives`);
25
- assert.match(text, /sed -n 's\/\^data: \/\/p'/, `${name} must include a plain curl SSE extraction example`);
26
- assert.match(text, /rdc_skill_list/, `${name} must mention rdc_skill_list`);
27
- assert.match(text, /rdc_skill_search/, `${name} must mention rdc_skill_search`);
28
- assert.match(text, /rdc_skill_get/, `${name} must mention rdc_skill_get`);
29
- assert.match(text, /output_contract/, `${name} must describe rdc_skill_list metadata fields`);
30
- assert.match(text, /codeflow_required/, `${name} must describe CodeFlow metadata`);
31
- assert.match(text, /variants/, `${name} must describe supported skill variants`);
32
- assert.match(text, /"format":"json"/, `${name} must show structured rdc_skill_get format=json`);
33
- assert.match(text, /turn this article into social posts/, `${name} must include a natural-language search example`);
34
- assert.match(text, /"name":"rdc:build"/, `${name} must show rdc_skill_get accepts visible slash names`);
35
- assert.doesNotMatch(text, /https:\/\/rdc-skills\.dev\.regendevcorp\.com\/mcp/, `${name} must not point callers at dev MCP`);
36
- }
37
-
38
- assert.match(docs.readme, /30 MCP skills organized into 8 manifest categories/, 'README should use manifest category count');
39
- assert.match(docs.readme, /Nineteen[\s\S]*\/rdc:\*` command shorthands/i, 'README should distinguish slash-command shorthands from full MCP skills');
40
- assert.match(docs.readme, /Use `rdc_skill_list` for the authoritative live catalog/, 'README should point callers to live MCP catalog');
41
- assert.doesNotMatch(docs.readme, /All user-invocable skills become available as slash commands/, 'README must not imply all MCP skills are slash commands');
42
- assert.doesNotMatch(docs.readme, /29 skills organized into 6 categories/, 'README must not carry stale category count');
43
- assert.match(docs.commandHelp, /menu of all MCP skills/, 'command help should refer to MCP skill catalog');
44
- assert.match(docs.skillHelp, /Show all MCP skills/, 'skill help should refer to MCP skill catalog');
45
- assert.match(docs.skillHelp, /manifest-driven/i, 'skill help should be manifest-driven');
46
- assert.match(docs.commandHelp, /manifest-driven/i, 'command help should be manifest-driven');
47
- assert.doesNotMatch(docs.commandHelp, /Print the full usage menu below verbatim/, 'command help must not use stale static menu wording');
48
- assert.doesNotMatch(docs.commandHelp, /get\/<service>/, 'command help must use current clauth /v/<service> wording');
49
-
50
- const skillDirs = readdirSync(join(root, 'skills'))
51
- .filter((name) => {
52
- const dir = join(root, 'skills', name);
53
- return statSync(dir).isDirectory() && existsSync(join(dir, 'SKILL.md'));
54
- })
55
- .sort();
56
- const readmeSkillDirs = [...docs.readme.matchAll(/^\s{2}([A-Za-z0-9_-]+)\/SKILL\.md/gm)]
57
- .map((match) => match[1])
58
- .sort();
59
- assert.deepEqual(readmeSkillDirs, skillDirs, 'README File Structure must list every skill directory exactly once');
60
-
61
- console.log('help surface tests — PASS');
1
+ #!/usr/bin/env node
2
+ import assert from 'node:assert/strict';
3
+ import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
4
+ import { dirname, join, resolve } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+ const root = resolve(__dirname, '..');
9
+
10
+ const files = {
11
+ readme: join(root, 'README.md'),
12
+ skillHelp: join(root, 'skills', 'help', 'SKILL.md'),
13
+ commandHelp: join(root, 'commands', 'help.md'),
14
+ };
15
+
16
+ const docs = Object.fromEntries(
17
+ Object.entries(files).map(([key, file]) => [key, readFileSync(file, 'utf8')]),
18
+ );
19
+
20
+ for (const [name, text] of Object.entries(docs)) {
21
+ assert.match(text, /https:\/\/rdc-skills\.regendevcorp\.com\/mcp/, `${name} must expose production MCP endpoint`);
22
+ assert.match(text, /Accept: application\/json, text\/event-stream/, `${name} must show Streamable HTTP Accept header`);
23
+ assert.match(text, /data:/, `${name} must explain Streamable HTTP SSE data lines`);
24
+ assert.match(text, /result\.content\[0\]\.text/, `${name} must explain where tool text lives`);
25
+ assert.match(text, /sed -n 's\/\^data: \/\/p'/, `${name} must include a plain curl SSE extraction example`);
26
+ assert.match(text, /rdc_skill_list/, `${name} must mention rdc_skill_list`);
27
+ assert.match(text, /rdc_skill_search/, `${name} must mention rdc_skill_search`);
28
+ assert.match(text, /rdc_skill_get/, `${name} must mention rdc_skill_get`);
29
+ assert.match(text, /output_contract/, `${name} must describe rdc_skill_list metadata fields`);
30
+ assert.match(text, /codeflow_required/, `${name} must describe CodeFlow metadata`);
31
+ assert.match(text, /variants/, `${name} must describe supported skill variants`);
32
+ assert.match(text, /"format":"json"/, `${name} must show structured rdc_skill_get format=json`);
33
+ assert.match(text, /turn this article into social posts/, `${name} must include a natural-language search example`);
34
+ assert.match(text, /"name":"rdc:build"/, `${name} must show rdc_skill_get accepts visible slash names`);
35
+ assert.doesNotMatch(text, /https:\/\/rdc-skills\.dev\.regendevcorp\.com\/mcp/, `${name} must not point callers at dev MCP`);
36
+ }
37
+
38
+ assert.match(docs.readme, /30 MCP skills organized into 8 manifest categories/, 'README should use manifest category count');
39
+ assert.match(docs.readme, /Nineteen[\s\S]*\/rdc:\*` command shorthands/i, 'README should distinguish slash-command shorthands from full MCP skills');
40
+ assert.match(docs.readme, /Use `rdc_skill_list` for the authoritative live catalog/, 'README should point callers to live MCP catalog');
41
+ assert.doesNotMatch(docs.readme, /All user-invocable skills become available as slash commands/, 'README must not imply all MCP skills are slash commands');
42
+ assert.doesNotMatch(docs.readme, /29 skills organized into 6 categories/, 'README must not carry stale category count');
43
+ assert.match(docs.commandHelp, /menu of all MCP skills/, 'command help should refer to MCP skill catalog');
44
+ assert.match(docs.skillHelp, /Show all MCP skills/, 'skill help should refer to MCP skill catalog');
45
+ assert.match(docs.skillHelp, /manifest-driven/i, 'skill help should be manifest-driven');
46
+ assert.match(docs.commandHelp, /manifest-driven/i, 'command help should be manifest-driven');
47
+ assert.doesNotMatch(docs.commandHelp, /Print the full usage menu below verbatim/, 'command help must not use stale static menu wording');
48
+ assert.doesNotMatch(docs.commandHelp, /get\/<service>/, 'command help must use current clauth /v/<service> wording');
49
+
50
+ const skillDirs = readdirSync(join(root, 'skills'))
51
+ .filter((name) => {
52
+ const dir = join(root, 'skills', name);
53
+ return statSync(dir).isDirectory() && existsSync(join(dir, 'SKILL.md'));
54
+ })
55
+ .sort();
56
+ const readmeSkillDirs = [...docs.readme.matchAll(/^\s{2}([A-Za-z0-9_-]+)\/SKILL\.md/gm)]
57
+ .map((match) => match[1])
58
+ .sort();
59
+ assert.deepEqual(readmeSkillDirs, skillDirs, 'README File Structure must list every skill directory exactly once');
60
+
61
+ console.log('help surface tests — PASS');
@@ -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');
@@ -1,49 +1,49 @@
1
- #!/usr/bin/env node
2
- import assert from 'node:assert/strict';
3
- import { spawnSync } from 'node:child_process';
4
- import { readFileSync } from 'node:fs';
5
- import { dirname, join, resolve } from 'node:path';
6
- import { fileURLToPath } from 'node:url';
7
-
8
- const __dirname = dirname(fileURLToPath(import.meta.url));
9
- const REPO_ROOT = resolve(__dirname, '..');
10
- const script = join(REPO_ROOT, 'scripts', 'install-rdc-skills.js');
11
-
12
- const syntax = spawnSync(process.execPath, ['--check', script], { encoding: 'utf8' });
13
- assert.equal(syntax.status, 0, syntax.stderr);
14
-
15
- const toml = spawnSync(process.execPath, [script, '--self-test-codex-mcp-toml'], {
16
- cwd: REPO_ROOT,
17
- encoding: 'utf8',
18
- });
19
- assert.equal(toml.status, 0, `${toml.stdout}\n${toml.stderr}`);
20
- assert.match(toml.stdout, /PASS/);
21
-
22
- const source = readFileSync(script, 'utf8');
23
- const plugin = JSON.parse(readFileSync(join(REPO_ROOT, '.claude-plugin', 'plugin.json'), 'utf8'));
24
- const skillCount = Array.isArray(plugin.skills_meta)
25
- ? plugin.skills_meta.length
26
- : Object.keys(plugin.skills_meta || {}).length;
27
- assert.equal(skillCount, 30, 'test fixture should expose all 30 MCP skills from plugin skills_meta');
28
- assert.match(
29
- source,
30
- /Available MCP skills.*\/rdc:\* command shorthands/,
31
- 'installer should distinguish the full MCP skill catalog from slash-command shorthands',
32
- );
33
- assert.match(
34
- source,
35
- /Object\.keys\(plugin\.skills_meta\)\.length/,
36
- 'installer should count object-shaped skills_meta manifests',
37
- );
38
- assert.match(
39
- source,
40
- /rdc_skill_list, rdc_skill_search, and rdc_skill_get/,
41
- 'installer should point raw callers at the MCP discovery tools',
42
- );
43
- assert.match(
44
- source,
45
- /no plugin upload needed for MCP/,
46
- 'installer should not imply claude.ai MCP usage requires uploading an artifact',
47
- );
48
-
49
- console.log('install-rdc-skills tests — PASS');
1
+ #!/usr/bin/env node
2
+ import assert from 'node:assert/strict';
3
+ import { spawnSync } from 'node:child_process';
4
+ import { readFileSync } from 'node:fs';
5
+ import { dirname, join, resolve } from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+
8
+ const __dirname = dirname(fileURLToPath(import.meta.url));
9
+ const REPO_ROOT = resolve(__dirname, '..');
10
+ const script = join(REPO_ROOT, 'scripts', 'install-rdc-skills.js');
11
+
12
+ const syntax = spawnSync(process.execPath, ['--check', script], { encoding: 'utf8' });
13
+ assert.equal(syntax.status, 0, syntax.stderr);
14
+
15
+ const toml = spawnSync(process.execPath, [script, '--self-test-codex-mcp-toml'], {
16
+ cwd: REPO_ROOT,
17
+ encoding: 'utf8',
18
+ });
19
+ assert.equal(toml.status, 0, `${toml.stdout}\n${toml.stderr}`);
20
+ assert.match(toml.stdout, /PASS/);
21
+
22
+ const source = readFileSync(script, 'utf8');
23
+ const plugin = JSON.parse(readFileSync(join(REPO_ROOT, '.claude-plugin', 'plugin.json'), 'utf8'));
24
+ const skillCount = Array.isArray(plugin.skills_meta)
25
+ ? plugin.skills_meta.length
26
+ : Object.keys(plugin.skills_meta || {}).length;
27
+ assert.equal(skillCount, 30, 'test fixture should expose all 30 MCP skills from plugin skills_meta');
28
+ assert.match(
29
+ source,
30
+ /Available MCP skills.*\/rdc:\* command shorthands/,
31
+ 'installer should distinguish the full MCP skill catalog from slash-command shorthands',
32
+ );
33
+ assert.match(
34
+ source,
35
+ /Object\.keys\(plugin\.skills_meta\)\.length/,
36
+ 'installer should count object-shaped skills_meta manifests',
37
+ );
38
+ assert.match(
39
+ source,
40
+ /rdc_skill_list, rdc_skill_search, and rdc_skill_get/,
41
+ 'installer should point raw callers at the MCP discovery tools',
42
+ );
43
+ assert.match(
44
+ source,
45
+ /no plugin upload needed for MCP/,
46
+ 'installer should not imply claude.ai MCP usage requires uploading an artifact',
47
+ );
48
+
49
+ console.log('install-rdc-skills tests — 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');
@@ -1,78 +1,78 @@
1
- #!/usr/bin/env node
2
- import assert from 'node:assert/strict';
3
- import { existsSync, readdirSync, readFileSync } from 'node:fs';
4
- import { dirname, join, resolve } from 'node:path';
5
- import { fileURLToPath } from 'node:url';
6
-
7
- const __dirname = dirname(fileURLToPath(import.meta.url));
8
- const root = resolve(__dirname, '..');
9
- const manifest = JSON.parse(readFileSync(join(root, '.claude-plugin', 'plugin.json'), 'utf8'));
10
- const skillDirs = readdirSync(join(root, 'skills'), { withFileTypes: true })
11
- .filter((entry) => entry.isDirectory() && entry.name !== 'tests')
12
- .map((entry) => entry.name)
13
- .sort();
14
- const metaNames = Object.keys(manifest.skills_meta).sort();
15
- const manifestSlashes = new Set(Object.values(manifest.skills_meta).map((meta) => meta.slash));
16
-
17
- assert.deepEqual(
18
- metaNames,
19
- skillDirs,
20
- 'Every skills/<name>/SKILL.md directory must have a skills_meta entry so MCP/help use explicit caller-facing terms',
21
- );
22
-
23
- function skillPath(name) {
24
- const direct = join(root, 'skills', name, 'SKILL.md');
25
- if (existsSync(direct)) return direct;
26
- const prefixed = join(root, 'skills', `rdc-${name}`, 'SKILL.md');
27
- if (existsSync(prefixed)) return prefixed;
28
- return direct;
29
- }
30
-
31
- for (const [name, meta] of Object.entries(manifest.skills_meta)) {
32
- const file = skillPath(name);
33
- assert.equal(existsSync(file), true, `${name} must have a SKILL.md`);
34
- const body = readFileSync(file, 'utf8');
35
-
36
- assert.equal(meta.name, name, `${name} manifest name must match its skills_meta key`);
37
- assert.doesNotMatch(meta.slash, /^rdc:rdc-/, `${name} slash command must not be a synthesized rdc:rdc-* name`);
38
- assert.doesNotMatch(meta.slash, /^rdc:lifeai-/, `${name} slash command must not invent an rdc: prefix for non-rdc skills`);
39
-
40
- const hasOutputContract = body.includes('guides/output-contract.md') || body.includes('OUTPUT CONTRACT');
41
- assert.equal(
42
- Boolean(meta.output_contract),
43
- hasOutputContract,
44
- `${name} manifest output_contract must match SKILL.md banner`,
45
- );
46
-
47
- if (body.includes('RDC_TEST=1')) {
48
- assert.equal(meta.sandbox_aware, true, `${name} mentions RDC_TEST=1 and must be sandbox_aware`);
49
- }
50
-
51
- if (meta.sandbox_aware) {
52
- assert.match(
53
- body,
54
- /Sandbox contract|RDC_TEST=1/,
55
- `${name} is sandbox_aware and must explain the sandbox contract in SKILL.md`,
56
- );
57
- }
58
- }
59
-
60
- const testManifests = readdirSync(join(root, 'skills', 'tests'), { withFileTypes: true })
61
- .filter((entry) => entry.isFile() && entry.name.endsWith('.test.json'));
62
-
63
- for (const entry of testManifests) {
64
- const testPath = join(root, 'skills', 'tests', entry.name);
65
- const test = JSON.parse(readFileSync(testPath, 'utf8'));
66
- assert.equal(
67
- manifestSlashes.has(test.skill),
68
- true,
69
- `${entry.name} skill "${test.skill}" must match a skills_meta[].slash value`,
70
- );
71
- assert.match(
72
- test.fixture?.prompt || '',
73
- new RegExp(`(^|\\s)${test.skill.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(\\s|$)`),
74
- `${entry.name} prompt must invoke its manifest skill name`,
75
- );
76
- }
77
-
78
- console.log('manifest contract field tests — PASS');
1
+ #!/usr/bin/env node
2
+ import assert from 'node:assert/strict';
3
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
4
+ import { dirname, join, resolve } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+ const root = resolve(__dirname, '..');
9
+ const manifest = JSON.parse(readFileSync(join(root, '.claude-plugin', 'plugin.json'), 'utf8'));
10
+ const skillDirs = readdirSync(join(root, 'skills'), { withFileTypes: true })
11
+ .filter((entry) => entry.isDirectory() && entry.name !== 'tests')
12
+ .map((entry) => entry.name)
13
+ .sort();
14
+ const metaNames = Object.keys(manifest.skills_meta).sort();
15
+ const manifestSlashes = new Set(Object.values(manifest.skills_meta).map((meta) => meta.slash));
16
+
17
+ assert.deepEqual(
18
+ metaNames,
19
+ skillDirs,
20
+ 'Every skills/<name>/SKILL.md directory must have a skills_meta entry so MCP/help use explicit caller-facing terms',
21
+ );
22
+
23
+ function skillPath(name) {
24
+ const direct = join(root, 'skills', name, 'SKILL.md');
25
+ if (existsSync(direct)) return direct;
26
+ const prefixed = join(root, 'skills', `rdc-${name}`, 'SKILL.md');
27
+ if (existsSync(prefixed)) return prefixed;
28
+ return direct;
29
+ }
30
+
31
+ for (const [name, meta] of Object.entries(manifest.skills_meta)) {
32
+ const file = skillPath(name);
33
+ assert.equal(existsSync(file), true, `${name} must have a SKILL.md`);
34
+ const body = readFileSync(file, 'utf8');
35
+
36
+ assert.equal(meta.name, name, `${name} manifest name must match its skills_meta key`);
37
+ assert.doesNotMatch(meta.slash, /^rdc:rdc-/, `${name} slash command must not be a synthesized rdc:rdc-* name`);
38
+ assert.doesNotMatch(meta.slash, /^rdc:lifeai-/, `${name} slash command must not invent an rdc: prefix for non-rdc skills`);
39
+
40
+ const hasOutputContract = body.includes('guides/output-contract.md') || body.includes('OUTPUT CONTRACT');
41
+ assert.equal(
42
+ Boolean(meta.output_contract),
43
+ hasOutputContract,
44
+ `${name} manifest output_contract must match SKILL.md banner`,
45
+ );
46
+
47
+ if (body.includes('RDC_TEST=1')) {
48
+ assert.equal(meta.sandbox_aware, true, `${name} mentions RDC_TEST=1 and must be sandbox_aware`);
49
+ }
50
+
51
+ if (meta.sandbox_aware) {
52
+ assert.match(
53
+ body,
54
+ /Sandbox contract|RDC_TEST=1/,
55
+ `${name} is sandbox_aware and must explain the sandbox contract in SKILL.md`,
56
+ );
57
+ }
58
+ }
59
+
60
+ const testManifests = readdirSync(join(root, 'skills', 'tests'), { withFileTypes: true })
61
+ .filter((entry) => entry.isFile() && entry.name.endsWith('.test.json'));
62
+
63
+ for (const entry of testManifests) {
64
+ const testPath = join(root, 'skills', 'tests', entry.name);
65
+ const test = JSON.parse(readFileSync(testPath, 'utf8'));
66
+ assert.equal(
67
+ manifestSlashes.has(test.skill),
68
+ true,
69
+ `${entry.name} skill "${test.skill}" must match a skills_meta[].slash value`,
70
+ );
71
+ assert.match(
72
+ test.fixture?.prompt || '',
73
+ new RegExp(`(^|\\s)${test.skill.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(\\s|$)`),
74
+ `${entry.name} prompt must invoke its manifest skill name`,
75
+ );
76
+ }
77
+
78
+ console.log('manifest contract field tests — PASS');