@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.
- package/.claude/settings.json +15 -15
- package/.claude-plugin/marketplace.json +21 -21
- package/.claude-plugin/plugin.json +1371 -1371
- package/.github/workflows/publish.yml +34 -34
- package/.github/workflows/self-test.yml +58 -58
- package/CHANGELOG.md +310 -310
- package/LICENSE +21 -21
- package/MANIFEST.md +221 -221
- package/README.md +376 -375
- package/README.sandbox.md +3 -3
- package/RELEASE.md +42 -0
- package/assets/watcher/viewer.html +164 -164
- package/bin/rdc-skills-mcp.mjs +316 -316
- package/commands/build.md +183 -183
- package/commands/collab.md +180 -180
- package/commands/deploy.md +152 -152
- package/commands/design.md +31 -31
- package/commands/edit.md +28 -28
- package/commands/fixit.md +124 -124
- package/commands/handoff.md +173 -173
- package/commands/help.md +95 -95
- package/commands/overnight.md +220 -220
- package/commands/plan.md +158 -158
- package/commands/preplan.md +131 -131
- package/commands/prototype.md +145 -145
- package/commands/release.md +49 -49
- package/commands/report.md +99 -99
- package/commands/review.md +120 -120
- package/commands/self-test.md +113 -113
- package/commands/status.md +86 -86
- package/commands/watch.md +98 -98
- package/commands/workitems.md +137 -137
- package/git-sha.json +1 -1
- package/guides/agent-bootstrap.md +295 -295
- package/guides/agents/backend.md +104 -104
- package/guides/agents/content.md +94 -94
- package/guides/agents/cs2.md +56 -56
- package/guides/agents/data.md +87 -87
- package/guides/agents/design.md +77 -77
- package/guides/agents/frontend.md +92 -92
- package/guides/agents/infrastructure.md +81 -81
- package/guides/agents/setup.md +281 -281
- package/guides/agents/verify.md +151 -151
- package/guides/agents/viz.md +106 -106
- package/guides/backend.md +146 -146
- package/guides/content.md +147 -147
- package/guides/cs2.md +190 -190
- package/guides/data.md +123 -123
- package/guides/design.md +116 -116
- package/guides/engineering-behavior.md +43 -43
- package/guides/escalation-protocol.md +125 -125
- package/guides/frontend.md +151 -151
- package/guides/history-md-spec.md +297 -297
- package/guides/infrastructure.md +179 -179
- package/guides/lessons-learned-spec.md +151 -145
- package/guides/output-contract.md +108 -108
- package/guides/publish-md-spec.md +289 -289
- package/guides/rdc-skills-startup.md +30 -30
- package/guides/verify.md +11 -11
- package/hooks/check-cwd.js +31 -31
- package/hooks/check-rdc-environment.js +164 -164
- package/hooks/check-services.js +6 -6
- package/hooks/check-stale-work-items.js +19 -19
- package/hooks/foreground-process-gate.js +128 -128
- package/hooks/gate-watchdog-selfcheck.js +257 -257
- package/hooks/hook-logger.js +25 -25
- package/hooks/lib/run-evidence-gate.mjs +241 -241
- package/hooks/no-stop-open-epics.js +127 -127
- package/hooks/post-tool-batch-gate.js +203 -203
- package/hooks/post-work-check.js +21 -21
- package/hooks/postcompact-log.js +13 -13
- package/hooks/precompact-log.js +13 -13
- package/hooks/rate-limit-retry.js +46 -46
- package/hooks/rdc-invocation-marker.js +157 -157
- package/hooks/rdc-output-contract-gate.js +94 -94
- package/hooks/require-work-item-on-commit.js +294 -294
- package/hooks/restart-brief.js +19 -19
- package/hooks/run-hidden-hook.ps1 +47 -47
- package/hooks/task-completed-gate.js +274 -274
- package/hooks/work-item-exit-gate.js +944 -944
- package/lib/catalog.mjs +236 -236
- package/lib/cloud-rewrite.mjs +155 -155
- package/package.json +57 -56
- package/rules/work-items-rpc.md +520 -520
- package/scaffold/templates/HISTORY.md.template +39 -39
- package/scaffold/templates/PUBLISH.md.template +21 -21
- package/scaffold/templates/brochure-studio-default.html +70 -70
- package/scripts/acceptance.mjs +502 -502
- package/scripts/fixtures/guides/bad-guide.md +15 -15
- package/scripts/fixtures/guides-clean/good-guide.md +16 -16
- package/scripts/install-rdc-skills.js +1289 -1289
- package/scripts/install.ps1 +202 -202
- package/scripts/install.sh +132 -132
- package/scripts/lib/assertions.mjs +287 -287
- package/scripts/lib/manifest-schema.mjs +754 -754
- package/scripts/lib/runner.mjs +465 -465
- package/scripts/lib/sandbox.mjs +435 -435
- package/scripts/prepack.mjs +32 -32
- package/scripts/rdc-brochure.mjs +482 -464
- package/scripts/rdc-design-cli.mjs +134 -134
- package/scripts/rebuild-mcp.mjs +107 -107
- package/scripts/self-test.mjs +1460 -1460
- package/scripts/stamp-git-sha.mjs +29 -29
- package/scripts/test-guide-validator.mjs +196 -196
- package/scripts/test-rdc-hooks.mjs +145 -145
- package/scripts/uninstall.ps1 +77 -77
- package/scripts/uninstall.sh +69 -69
- package/scripts/update.ps1 +43 -43
- package/scripts/update.sh +43 -43
- package/scripts/validate-place-histories.js +461 -461
- package/scripts/validate-publish-manifests.js +424 -424
- package/scripts/watch-init.mjs +100 -100
- package/skills/brochure/SKILL.md +107 -107
- package/skills/build/SKILL.md +563 -563
- package/skills/channel-formatter/SKILL.md +533 -533
- package/skills/co-develop/SKILL.md +196 -196
- package/skills/collab/SKILL.md +239 -239
- package/skills/convert/SKILL.md +140 -140
- package/skills/deploy/SKILL.md +541 -541
- package/skills/design/SKILL.md +211 -211
- package/skills/design/reference/ownership.md +16 -16
- package/skills/design/reference/rampa.md +92 -92
- package/skills/design/reference/studio-model.md +153 -153
- package/skills/edit/SKILL.md +98 -98
- package/skills/fixit/SKILL.md +165 -165
- package/skills/fs-mcp/SKILL.md +148 -148
- package/skills/handoff/SKILL.md +236 -200
- package/skills/help/SKILL.md +143 -143
- package/skills/housekeeping/SKILL.md +219 -160
- package/skills/lifeai-brochure-author/SKILL.md +340 -340
- package/skills/overnight/SKILL.md +251 -251
- package/skills/plan/SKILL.md +345 -345
- package/skills/preplan/SKILL.md +90 -90
- package/skills/prototype/SKILL.md +150 -150
- package/skills/rdc-brochurify/SKILL.md +245 -245
- package/skills/rdc-extract-verifier-rules/SKILL.md +191 -191
- package/skills/release/SKILL.md +140 -140
- package/skills/report/SKILL.md +100 -100
- package/skills/review/SKILL.md +152 -152
- package/skills/rpms-filemap/SKILL.cloud.md +111 -111
- package/skills/rpms-filemap/SKILL.md +111 -111
- package/skills/self-test/SKILL.md +132 -132
- package/skills/status/SKILL.md +99 -99
- package/skills/terminal-config/SKILL.md +62 -62
- package/skills/tests/MATRIX.md +54 -54
- package/skills/tests/README.md +47 -47
- package/skills/tests/rdc-brochure.test.json +34 -34
- package/skills/tests/rdc-build.test.json +36 -36
- package/skills/tests/rdc-channel-formatter.test.json +45 -45
- package/skills/tests/rdc-co-develop.test.json +29 -29
- package/skills/tests/rdc-collab.test.json +29 -29
- package/skills/tests/rdc-convert.test.json +35 -35
- package/skills/tests/rdc-deploy.test.json +30 -30
- package/skills/tests/rdc-design.test.json +27 -27
- package/skills/tests/rdc-edit.test.json +29 -29
- package/skills/tests/rdc-fixit.test.json +36 -36
- package/skills/tests/rdc-fs-mcp.test.json +36 -36
- package/skills/tests/rdc-handoff.test.json +28 -28
- package/skills/tests/rdc-help.test.json +29 -29
- package/skills/tests/rdc-housekeeping.test.json +32 -28
- package/skills/tests/rdc-lifeai-brochure-author.test.json +35 -35
- package/skills/tests/rdc-overnight.test.json +37 -37
- package/skills/tests/rdc-plan.test.json +27 -27
- package/skills/tests/rdc-preplan.test.json +31 -31
- package/skills/tests/rdc-prototype.test.json +28 -28
- package/skills/tests/rdc-rdc-brochurify.test.json +23 -23
- package/skills/tests/rdc-rdc-extract-verifier-rules.test.json +34 -34
- package/skills/tests/rdc-release.test.json +29 -29
- package/skills/tests/rdc-report.test.json +28 -28
- package/skills/tests/rdc-review.test.json +29 -29
- package/skills/tests/rdc-rpms-filemap.test.json +28 -28
- package/skills/tests/rdc-self-test.test.json +24 -24
- package/skills/tests/rdc-status.test.json +29 -29
- package/skills/tests/rdc-terminal-config.test.json +29 -29
- package/skills/tests/rdc-watch.test.json +24 -24
- package/skills/tests/rdc-workitems.test.json +27 -27
- package/skills/watch/SKILL.md +97 -97
- package/skills/workitems/SKILL.md +151 -151
- package/tests/acceptance.test.mjs +59 -59
- package/tests/channel-formatter.contract.test.mjs +251 -251
- package/tests/curl-surface.test.mjs +289 -289
- package/tests/harness-gates.test.mjs +325 -325
- package/tests/help-surface.test.mjs +61 -61
- package/tests/housekeeping-lessons-triage.test.mjs +49 -0
- package/tests/install-rdc-skills.test.mjs +49 -49
- package/tests/lessons-pipeline-contract.test.mjs +26 -0
- package/tests/manifest-contract-fields.test.mjs +78 -78
- package/tests/mcp.test.mjs +271 -271
- package/tests/rdc-brochure.test.mjs +125 -0
- package/tests/release-contract.test.mjs +16 -0
- package/tests/require-work-item-on-commit.test.mjs +162 -162
- package/tests/run-evidence-gate.test.mjs +82 -82
- package/tests/skill-test-matrix.test.mjs +66 -66
- package/tests/validate-skills.js +27 -27
- package/tests/work-item-exit-gate-l2.test.mjs +368 -368
- 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
|
+
}
|