@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,29 +1,29 @@
1
- #!/usr/bin/env node
2
- /**
3
- * stamp-git-sha.mjs — bake the current git HEAD into git-sha.json at pack/publish
4
- * time. The MCP server runs from the npm-installed copy (no .git), so it cannot
5
- * `git rev-parse` at runtime; this build-time stamp is how /health reports a
6
- * PROVABLE commit ("running artifact == this exact published commit"). Run from
7
- * `prepack`, so `npm pack`/`npm publish` (incl. the publish.yml CI job at the
8
- * tagged commit) embeds the right SHA. Never fails the build — writes 'unknown'
9
- * if git is unavailable (e.g. a tarball-only checkout).
10
- */
11
- import { execFileSync } from 'node:child_process';
12
- import { writeFileSync } from 'node:fs';
13
- import path from 'node:path';
14
- import { fileURLToPath } from 'node:url';
15
-
16
- const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
17
- let sha = 'unknown';
18
- try {
19
- sha = execFileSync('git', ['rev-parse', 'HEAD'], {
20
- cwd: root,
21
- encoding: 'utf8',
22
- stdio: ['ignore', 'pipe', 'ignore'],
23
- }).trim();
24
- } catch {
25
- sha = process.env.RDC_SKILLS_GIT_SHA || 'unknown';
26
- }
27
- const out = path.join(root, 'git-sha.json');
28
- writeFileSync(out, JSON.stringify({ sha }, null, 2) + '\n');
29
- console.error(`[stamp-git-sha] wrote ${out} sha=${sha}`);
1
+ #!/usr/bin/env node
2
+ /**
3
+ * stamp-git-sha.mjs — bake the current git HEAD into git-sha.json at pack/publish
4
+ * time. The MCP server runs from the npm-installed copy (no .git), so it cannot
5
+ * `git rev-parse` at runtime; this build-time stamp is how /health reports a
6
+ * PROVABLE commit ("running artifact == this exact published commit"). Run from
7
+ * `prepack`, so `npm pack`/`npm publish` (incl. the publish.yml CI job at the
8
+ * tagged commit) embeds the right SHA. Never fails the build — writes 'unknown'
9
+ * if git is unavailable (e.g. a tarball-only checkout).
10
+ */
11
+ import { execFileSync } from 'node:child_process';
12
+ import { writeFileSync } from 'node:fs';
13
+ import path from 'node:path';
14
+ import { fileURLToPath } from 'node:url';
15
+
16
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
17
+ let sha = 'unknown';
18
+ try {
19
+ sha = execFileSync('git', ['rev-parse', 'HEAD'], {
20
+ cwd: root,
21
+ encoding: 'utf8',
22
+ stdio: ['ignore', 'pipe', 'ignore'],
23
+ }).trim();
24
+ } catch {
25
+ sha = process.env.RDC_SKILLS_GIT_SHA || 'unknown';
26
+ }
27
+ const out = path.join(root, 'git-sha.json');
28
+ writeFileSync(out, JSON.stringify({ sha }, null, 2) + '\n');
29
+ console.error(`[stamp-git-sha] wrote ${out} sha=${sha}`);
@@ -1,196 +1,196 @@
1
- #!/usr/bin/env node
2
- // test-guide-validator.mjs — Unit test for the guide-content validator
3
- //
4
- // Proves:
5
- // 1. The validator flags guides containing banned terms in positive-instruction context
6
- // 2. The validator passes guides that mention banned terms only as warnings/negations
7
- // 3. The validator flags unknown clauth key names
8
- // 4. The validator passes known clauth key names
9
- //
10
- // Usage:
11
- // node scripts/test-guide-validator.mjs
12
- //
13
- // Exit codes:
14
- // 0 = all assertions pass
15
- // 1 = one or more assertions failed
16
-
17
- import { dirname, join, resolve } from "node:path";
18
- import { fileURLToPath } from "node:url";
19
-
20
- const __dirname = dirname(fileURLToPath(import.meta.url));
21
- const REPO_ROOT = resolve(__dirname, "..");
22
-
23
- // Import the validator functions from self-test.mjs by running it in test mode.
24
- // Since self-test.mjs is not a module that exports cleanly, we replicate the
25
- // validator logic here using the same constants and algorithms.
26
- // (The shared implementation lives in runGuideContentValidator / auditGuideFile.)
27
- //
28
- // We import via dynamic import of the self-test module's exported helpers —
29
- // but since self-test.mjs has no exports (it runs immediately), we inline
30
- // the equivalent test using the same fixture files it targets.
31
-
32
- // ─── Inline validator (mirrors self-test.mjs logic) ────────────────────────
33
- import { readFileSync, existsSync, readdirSync, statSync } from "node:fs";
34
-
35
- const GUIDE_BANNED_TERMS = [
36
- "@masonator/coolify-mcp",
37
- "@masonator",
38
- "coolify-mcp",
39
- "@regen/brand-studio",
40
- "brand-studio",
41
- ];
42
-
43
- const GUIDE_NEGATION_PATTERNS = [
44
- /\bdo not\b/i,
45
- /\bnever\b/i,
46
- /\bno such\b/i,
47
- /\bdoes not exist\b/i,
48
- /\bbanned\b/i,
49
- /\bnot reference\b/i,
50
- /\bnot use\b/i,
51
- /\bavoid\b/i,
52
- /\bremoved\b/i,
53
- /\bdeprecated\b/i,
54
- // Markdown table row showing a WRONG→CORRECT mapping (naming-corrections.md pattern)
55
- /^\|[^|]*WRONG[^|]*\|/i,
56
- // A table row where the term is in the WRONG column (first data column after the | WRONG | header)
57
- /^\|\s*(Brand Studio|brand-studio|@regen\/brand-studio|@masonator[^ |]*|coolify-mcp)[^|]*\|\s*\*\*/,
58
- ];
59
-
60
- const KNOWN_CLAUTH_KEYS = new Set([
61
- "coolify-api",
62
- "cloudflare",
63
- "npm",
64
- "supabase",
65
- "supabase-anon",
66
- "supabase-db",
67
- "supabase-service",
68
- "supabase-service-role",
69
- "r2-access-key-id",
70
- "r2-secret-key",
71
- "anthropic",
72
- "openai",
73
- "github",
74
- "github-token",
75
- "vultr-dev-ssh",
76
- "mcp-web-research-secret",
77
- "mcp-regen-media-secret",
78
- "web-research",
79
- "regen-media",
80
- ]);
81
-
82
- function auditGuideFile(filepath, relPath) {
83
- const findings = [];
84
- let text;
85
- try {
86
- text = readFileSync(filepath, "utf8");
87
- } catch (e) {
88
- findings.push({ level: "error", code: "guide-unreadable", file: relPath, line: 0, message: `cannot read: ${e.message}` });
89
- return findings;
90
- }
91
- const lines = text.split(/\r?\n/);
92
- lines.forEach((line, i) => {
93
- const lineNo = i + 1;
94
- for (const term of GUIDE_BANNED_TERMS) {
95
- if (line.includes(term)) {
96
- const isNegated = GUIDE_NEGATION_PATTERNS.some((re) => re.test(line));
97
- if (!isNegated) {
98
- findings.push({ level: "error", code: "guide-banned-term", file: relPath, line: lineNo, message: `banned term "${term}"` });
99
- }
100
- }
101
- }
102
- const clauthKeyRe = /\bhttp:\/\/127\.0\.0\.1:52437\/v\/([\w-]+)/g;
103
- let m;
104
- while ((m = clauthKeyRe.exec(line)) !== null) {
105
- const key = m[1];
106
- if (!KNOWN_CLAUTH_KEYS.has(key)) {
107
- findings.push({ level: "warn", code: "guide-clauth-key-unknown", file: relPath, line: lineNo, message: `unknown clauth key "${key}"` });
108
- }
109
- }
110
- });
111
- return findings;
112
- }
113
-
114
- function scanDir(dir, prefix) {
115
- const findings = [];
116
- if (!existsSync(dir)) return findings;
117
- let entries;
118
- try { entries = readdirSync(dir); } catch { return findings; }
119
- for (const entry of entries) {
120
- const fullPath = join(dir, entry);
121
- let stat;
122
- try { stat = statSync(fullPath); } catch { continue; }
123
- if (stat.isDirectory()) {
124
- findings.push(...scanDir(fullPath, `${prefix}/${entry}`));
125
- } else if (entry.endsWith(".md")) {
126
- findings.push(...auditGuideFile(fullPath, `${prefix}/${entry}`));
127
- }
128
- }
129
- return findings;
130
- }
131
-
132
- // ─── Test runner ─────────────────────────────────────────────────────────────
133
-
134
- let passed = 0;
135
- let failed = 0;
136
-
137
- function assert(description, condition, detail = "") {
138
- if (condition) {
139
- console.log(` ✓ ${description}`);
140
- passed++;
141
- } else {
142
- console.error(` ✗ FAIL: ${description}${detail ? ` — ${detail}` : ""}`);
143
- failed++;
144
- }
145
- }
146
-
147
- console.log("\nrdc:self-test — guide-content validator unit tests\n");
148
-
149
- // ─── Test 1: bad-guide.md should produce errors for banned terms ─────────────
150
- console.log("Test 1: bad-guide.md flags banned terms");
151
- const BAD_FIXTURE = join(REPO_ROOT, "scripts/fixtures/guides/bad-guide.md");
152
- const badFindings = auditGuideFile(BAD_FIXTURE, "fixtures/guides/bad-guide.md");
153
- const badErrors = badFindings.filter((f) => f.level === "error" && f.code === "guide-banned-term");
154
- assert("bad-guide.md produces at least 2 banned-term errors", badErrors.length >= 2, `got ${badErrors.length}`);
155
- assert("flags @masonator/coolify-mcp", badErrors.some((f) => f.message.includes("@masonator/coolify-mcp")));
156
- assert("flags brand-studio or @regen/brand-studio", badErrors.some((f) => f.message.includes("brand-studio")));
157
-
158
- // ─── Test 2: bad-guide.md flags unknown clauth key ───────────────────────────
159
- console.log("\nTest 2: bad-guide.md flags unknown clauth key");
160
- const badKeyWarnings = badFindings.filter((f) => f.code === "guide-clauth-key-unknown");
161
- assert("bad-guide.md warns on unknown clauth key 'nonexistent-key'", badKeyWarnings.length >= 1, `got ${badKeyWarnings.length}`);
162
- assert("warning message references nonexistent-key", badKeyWarnings.some((f) => f.message.includes("nonexistent-key")));
163
-
164
- // ─── Test 3: good-guide.md produces no errors (negated mentions are OK) ──────
165
- console.log("\nTest 3: good-guide.md passes (negated mentions are not flagged)");
166
- const GOOD_FIXTURE = join(REPO_ROOT, "scripts/fixtures/guides-clean/good-guide.md");
167
- const goodFindings = auditGuideFile(GOOD_FIXTURE, "fixtures/guides-clean/good-guide.md");
168
- const goodErrors = goodFindings.filter((f) => f.level === "error");
169
- assert("good-guide.md produces 0 banned-term errors", goodErrors.length === 0, `got ${goodErrors.length}: ${goodErrors.map((f) => f.message).join("; ")}`);
170
-
171
- // good-guide uses coolify-api which is a known key — no key warnings
172
- const goodKeyWarnings = goodFindings.filter((f) => f.code === "guide-clauth-key-unknown");
173
- assert("good-guide.md produces 0 unknown-key warnings", goodKeyWarnings.length === 0, `got ${goodKeyWarnings.length}`);
174
-
175
- // ─── Test 4: scan fixture directory returns combined findings ─────────────────
176
- console.log("\nTest 4: scanDir finds findings across fixture dirs");
177
- const BAD_DIR = join(REPO_ROOT, "scripts/fixtures/guides");
178
- const dirFindings = scanDir(BAD_DIR, "fixtures");
179
- const dirErrors = dirFindings.filter((f) => f.level === "error");
180
- assert("scanDir on bad fixture dir returns errors", dirErrors.length >= 2, `got ${dirErrors.length}`);
181
-
182
- const GOOD_DIR = join(REPO_ROOT, "scripts/fixtures/guides-clean");
183
- const cleanFindings = scanDir(GOOD_DIR, "fixtures");
184
- const cleanErrors = cleanFindings.filter((f) => f.level === "error");
185
- assert("scanDir on clean fixture dir returns 0 errors", cleanErrors.length === 0, `got ${cleanErrors.length}`);
186
-
187
- // ─── Summary ─────────────────────────────────────────────────────────────────
188
- console.log(`\n${"─".repeat(60)}`);
189
- console.log(`guide-validator tests: ${passed} passed, ${failed} failed`);
190
- if (failed > 0) {
191
- console.error(`\n❌ FAIL — ${failed} assertion(s) did not pass`);
192
- process.exit(1);
193
- } else {
194
- console.log(`\n✓ PASS — all guide-content validator assertions passed`);
195
- process.exit(0);
196
- }
1
+ #!/usr/bin/env node
2
+ // test-guide-validator.mjs — Unit test for the guide-content validator
3
+ //
4
+ // Proves:
5
+ // 1. The validator flags guides containing banned terms in positive-instruction context
6
+ // 2. The validator passes guides that mention banned terms only as warnings/negations
7
+ // 3. The validator flags unknown clauth key names
8
+ // 4. The validator passes known clauth key names
9
+ //
10
+ // Usage:
11
+ // node scripts/test-guide-validator.mjs
12
+ //
13
+ // Exit codes:
14
+ // 0 = all assertions pass
15
+ // 1 = one or more assertions failed
16
+
17
+ import { dirname, join, resolve } from "node:path";
18
+ import { fileURLToPath } from "node:url";
19
+
20
+ const __dirname = dirname(fileURLToPath(import.meta.url));
21
+ const REPO_ROOT = resolve(__dirname, "..");
22
+
23
+ // Import the validator functions from self-test.mjs by running it in test mode.
24
+ // Since self-test.mjs is not a module that exports cleanly, we replicate the
25
+ // validator logic here using the same constants and algorithms.
26
+ // (The shared implementation lives in runGuideContentValidator / auditGuideFile.)
27
+ //
28
+ // We import via dynamic import of the self-test module's exported helpers —
29
+ // but since self-test.mjs has no exports (it runs immediately), we inline
30
+ // the equivalent test using the same fixture files it targets.
31
+
32
+ // ─── Inline validator (mirrors self-test.mjs logic) ────────────────────────
33
+ import { readFileSync, existsSync, readdirSync, statSync } from "node:fs";
34
+
35
+ const GUIDE_BANNED_TERMS = [
36
+ "@masonator/coolify-mcp",
37
+ "@masonator",
38
+ "coolify-mcp",
39
+ "@regen/brand-studio",
40
+ "brand-studio",
41
+ ];
42
+
43
+ const GUIDE_NEGATION_PATTERNS = [
44
+ /\bdo not\b/i,
45
+ /\bnever\b/i,
46
+ /\bno such\b/i,
47
+ /\bdoes not exist\b/i,
48
+ /\bbanned\b/i,
49
+ /\bnot reference\b/i,
50
+ /\bnot use\b/i,
51
+ /\bavoid\b/i,
52
+ /\bremoved\b/i,
53
+ /\bdeprecated\b/i,
54
+ // Markdown table row showing a WRONG→CORRECT mapping (naming-corrections.md pattern)
55
+ /^\|[^|]*WRONG[^|]*\|/i,
56
+ // A table row where the term is in the WRONG column (first data column after the | WRONG | header)
57
+ /^\|\s*(Brand Studio|brand-studio|@regen\/brand-studio|@masonator[^ |]*|coolify-mcp)[^|]*\|\s*\*\*/,
58
+ ];
59
+
60
+ const KNOWN_CLAUTH_KEYS = new Set([
61
+ "coolify-api",
62
+ "cloudflare",
63
+ "npm",
64
+ "supabase",
65
+ "supabase-anon",
66
+ "supabase-db",
67
+ "supabase-service",
68
+ "supabase-service-role",
69
+ "r2-access-key-id",
70
+ "r2-secret-key",
71
+ "anthropic",
72
+ "openai",
73
+ "github",
74
+ "github-token",
75
+ "vultr-dev-ssh",
76
+ "mcp-web-research-secret",
77
+ "mcp-regen-media-secret",
78
+ "web-research",
79
+ "regen-media",
80
+ ]);
81
+
82
+ function auditGuideFile(filepath, relPath) {
83
+ const findings = [];
84
+ let text;
85
+ try {
86
+ text = readFileSync(filepath, "utf8");
87
+ } catch (e) {
88
+ findings.push({ level: "error", code: "guide-unreadable", file: relPath, line: 0, message: `cannot read: ${e.message}` });
89
+ return findings;
90
+ }
91
+ const lines = text.split(/\r?\n/);
92
+ lines.forEach((line, i) => {
93
+ const lineNo = i + 1;
94
+ for (const term of GUIDE_BANNED_TERMS) {
95
+ if (line.includes(term)) {
96
+ const isNegated = GUIDE_NEGATION_PATTERNS.some((re) => re.test(line));
97
+ if (!isNegated) {
98
+ findings.push({ level: "error", code: "guide-banned-term", file: relPath, line: lineNo, message: `banned term "${term}"` });
99
+ }
100
+ }
101
+ }
102
+ const clauthKeyRe = /\bhttp:\/\/127\.0\.0\.1:52437\/v\/([\w-]+)/g;
103
+ let m;
104
+ while ((m = clauthKeyRe.exec(line)) !== null) {
105
+ const key = m[1];
106
+ if (!KNOWN_CLAUTH_KEYS.has(key)) {
107
+ findings.push({ level: "warn", code: "guide-clauth-key-unknown", file: relPath, line: lineNo, message: `unknown clauth key "${key}"` });
108
+ }
109
+ }
110
+ });
111
+ return findings;
112
+ }
113
+
114
+ function scanDir(dir, prefix) {
115
+ const findings = [];
116
+ if (!existsSync(dir)) return findings;
117
+ let entries;
118
+ try { entries = readdirSync(dir); } catch { return findings; }
119
+ for (const entry of entries) {
120
+ const fullPath = join(dir, entry);
121
+ let stat;
122
+ try { stat = statSync(fullPath); } catch { continue; }
123
+ if (stat.isDirectory()) {
124
+ findings.push(...scanDir(fullPath, `${prefix}/${entry}`));
125
+ } else if (entry.endsWith(".md")) {
126
+ findings.push(...auditGuideFile(fullPath, `${prefix}/${entry}`));
127
+ }
128
+ }
129
+ return findings;
130
+ }
131
+
132
+ // ─── Test runner ─────────────────────────────────────────────────────────────
133
+
134
+ let passed = 0;
135
+ let failed = 0;
136
+
137
+ function assert(description, condition, detail = "") {
138
+ if (condition) {
139
+ console.log(` ✓ ${description}`);
140
+ passed++;
141
+ } else {
142
+ console.error(` ✗ FAIL: ${description}${detail ? ` — ${detail}` : ""}`);
143
+ failed++;
144
+ }
145
+ }
146
+
147
+ console.log("\nrdc:self-test — guide-content validator unit tests\n");
148
+
149
+ // ─── Test 1: bad-guide.md should produce errors for banned terms ─────────────
150
+ console.log("Test 1: bad-guide.md flags banned terms");
151
+ const BAD_FIXTURE = join(REPO_ROOT, "scripts/fixtures/guides/bad-guide.md");
152
+ const badFindings = auditGuideFile(BAD_FIXTURE, "fixtures/guides/bad-guide.md");
153
+ const badErrors = badFindings.filter((f) => f.level === "error" && f.code === "guide-banned-term");
154
+ assert("bad-guide.md produces at least 2 banned-term errors", badErrors.length >= 2, `got ${badErrors.length}`);
155
+ assert("flags @masonator/coolify-mcp", badErrors.some((f) => f.message.includes("@masonator/coolify-mcp")));
156
+ assert("flags brand-studio or @regen/brand-studio", badErrors.some((f) => f.message.includes("brand-studio")));
157
+
158
+ // ─── Test 2: bad-guide.md flags unknown clauth key ───────────────────────────
159
+ console.log("\nTest 2: bad-guide.md flags unknown clauth key");
160
+ const badKeyWarnings = badFindings.filter((f) => f.code === "guide-clauth-key-unknown");
161
+ assert("bad-guide.md warns on unknown clauth key 'nonexistent-key'", badKeyWarnings.length >= 1, `got ${badKeyWarnings.length}`);
162
+ assert("warning message references nonexistent-key", badKeyWarnings.some((f) => f.message.includes("nonexistent-key")));
163
+
164
+ // ─── Test 3: good-guide.md produces no errors (negated mentions are OK) ──────
165
+ console.log("\nTest 3: good-guide.md passes (negated mentions are not flagged)");
166
+ const GOOD_FIXTURE = join(REPO_ROOT, "scripts/fixtures/guides-clean/good-guide.md");
167
+ const goodFindings = auditGuideFile(GOOD_FIXTURE, "fixtures/guides-clean/good-guide.md");
168
+ const goodErrors = goodFindings.filter((f) => f.level === "error");
169
+ assert("good-guide.md produces 0 banned-term errors", goodErrors.length === 0, `got ${goodErrors.length}: ${goodErrors.map((f) => f.message).join("; ")}`);
170
+
171
+ // good-guide uses coolify-api which is a known key — no key warnings
172
+ const goodKeyWarnings = goodFindings.filter((f) => f.code === "guide-clauth-key-unknown");
173
+ assert("good-guide.md produces 0 unknown-key warnings", goodKeyWarnings.length === 0, `got ${goodKeyWarnings.length}`);
174
+
175
+ // ─── Test 4: scan fixture directory returns combined findings ─────────────────
176
+ console.log("\nTest 4: scanDir finds findings across fixture dirs");
177
+ const BAD_DIR = join(REPO_ROOT, "scripts/fixtures/guides");
178
+ const dirFindings = scanDir(BAD_DIR, "fixtures");
179
+ const dirErrors = dirFindings.filter((f) => f.level === "error");
180
+ assert("scanDir on bad fixture dir returns errors", dirErrors.length >= 2, `got ${dirErrors.length}`);
181
+
182
+ const GOOD_DIR = join(REPO_ROOT, "scripts/fixtures/guides-clean");
183
+ const cleanFindings = scanDir(GOOD_DIR, "fixtures");
184
+ const cleanErrors = cleanFindings.filter((f) => f.level === "error");
185
+ assert("scanDir on clean fixture dir returns 0 errors", cleanErrors.length === 0, `got ${cleanErrors.length}`);
186
+
187
+ // ─── Summary ─────────────────────────────────────────────────────────────────
188
+ console.log(`\n${"─".repeat(60)}`);
189
+ console.log(`guide-validator tests: ${passed} passed, ${failed} failed`);
190
+ if (failed > 0) {
191
+ console.error(`\n❌ FAIL — ${failed} assertion(s) did not pass`);
192
+ process.exit(1);
193
+ } else {
194
+ console.log(`\n✓ PASS — all guide-content validator assertions passed`);
195
+ process.exit(0);
196
+ }