@bongos/core 1.19.460 → 1.19.462
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/.bongos-core.json +145 -20
- package/.claude/skills/design/SKILL.md +11 -3
- package/docs/adr/0220-an-in-house-rebuilt-skill-is-a-first-party-skill.md +46 -0
- package/docs/adr/0221-the-impeccable-method-is-one-skill-with-sub-command-playbooks.md +36 -0
- package/docs/adr/README.md +2 -0
- package/docs/file-map.md +1 -1
- package/docs/module-api-changelog.md +4 -0
- package/modules/ui-design/module.json +1 -1
- package/modules/ui-design/skills/PREAMBLE.md +3 -3
- package/modules/ui-design/skills/README.md +15 -1
- package/modules/ui-design/skills/design-taste-frontend/SKILL.md +136 -0
- package/modules/ui-design/skills/design-taste-frontend-v1/SKILL.md +125 -0
- package/modules/ui-design/skills/high-end-visual-design/SKILL.md +128 -0
- package/modules/ui-design/skills/impeccable/SKILL.md +143 -0
- package/modules/ui-design/skills/impeccable/reference/adapt.md +25 -0
- package/modules/ui-design/skills/impeccable/reference/animate.md +27 -0
- package/modules/ui-design/skills/impeccable/reference/audit.md +19 -0
- package/modules/ui-design/skills/impeccable/reference/bolder.md +17 -0
- package/modules/ui-design/skills/impeccable/reference/clarify.md +25 -0
- package/modules/ui-design/skills/impeccable/reference/colorize.md +22 -0
- package/modules/ui-design/skills/impeccable/reference/critique.md +15 -0
- package/modules/ui-design/skills/impeccable/reference/distill.md +26 -0
- package/modules/ui-design/skills/impeccable/reference/document.md +25 -0
- package/modules/ui-design/skills/impeccable/reference/extract.md +20 -0
- package/modules/ui-design/skills/impeccable/reference/harden.md +21 -0
- package/modules/ui-design/skills/impeccable/reference/layout.md +20 -0
- package/modules/ui-design/skills/impeccable/reference/onboard.md +22 -0
- package/modules/ui-design/skills/impeccable/reference/optimize.md +22 -0
- package/modules/ui-design/skills/impeccable/reference/polish.md +22 -0
- package/modules/ui-design/skills/impeccable/reference/quieter.md +17 -0
- package/modules/ui-design/skills/impeccable/reference/shape.md +23 -0
- package/modules/ui-design/skills/impeccable/reference/typeset.md +24 -0
- package/modules/ui-design/skills/redesign-existing-projects/SKILL.md +120 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/docs-entropy.js +5 -1
- package/src/module-api.js +1 -1
- package/tests/docs_entropy.mjs +29 -1
- package/tests/ui_design_skills.mjs +140 -9
|
@@ -259,8 +259,12 @@ function scanDuplicateProse(relFiles) {
|
|
|
259
259
|
for (const { text, startLine } of paragraphs) {
|
|
260
260
|
const trimmed = text.trim();
|
|
261
261
|
if (trimmed.length < MIN_DUPE_CHARS) continue;
|
|
262
|
-
// Skip content that is intentionally generated/duplicated across files
|
|
262
|
+
// Skip content that is intentionally generated/duplicated across files:
|
|
263
|
+
// generated blocks, and the ui-design module's PLATFORM PREAMBLE, which
|
|
264
|
+
// its policy requires byte-identical in every skill it ships (ADR 0198 §4;
|
|
265
|
+
// tests/ui_design_skills.mjs pins the copies against PREAMBLE.md).
|
|
263
266
|
if (trimmed.startsWith('<!-- BEGIN GENERATED')) continue;
|
|
267
|
+
if (trimmed.startsWith('<!-- BEGIN PLATFORM PREAMBLE')) continue;
|
|
264
268
|
// Normalize internal whitespace for comparison
|
|
265
269
|
const key = trimmed.replace(/\s+/g, ' ');
|
|
266
270
|
if (!byText.has(key)) byText.set(key, []);
|
package/src/module-api.js
CHANGED
|
@@ -33,7 +33,7 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
|
|
|
33
33
|
// there. scripts/gds/bump-version.js still rewrites the literal below; it appends
|
|
34
34
|
// the entry to that file. Look for a version's history there, not here.
|
|
35
35
|
// ---------------------------------------------------------------------------
|
|
36
|
-
const CORE_VERSION = '1.19.
|
|
36
|
+
const CORE_VERSION = '1.19.462'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
37
37
|
|
|
38
38
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
39
39
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|
package/tests/docs_entropy.mjs
CHANGED
|
@@ -19,7 +19,7 @@ import path from 'node:path';
|
|
|
19
19
|
const require = createRequire(import.meta.url);
|
|
20
20
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
21
21
|
const SCANNER = path.join(ROOT, 'scripts', 'gds', 'docs-entropy.js');
|
|
22
|
-
const { collectMarkdownFiles, parseFile } = require(SCANNER); // require.main-guarded → no scan on import
|
|
22
|
+
const { collectMarkdownFiles, parseFile, scanDuplicateProse } = require(SCANNER); // require.main-guarded → no scan on import
|
|
23
23
|
|
|
24
24
|
test('collectMarkdownFiles passes the glob as an argv array, never a shell string', () => {
|
|
25
25
|
let seen = null;
|
|
@@ -138,3 +138,31 @@ test('the real scan on THIS checkout matches the CI baselines (the platform-pari
|
|
|
138
138
|
+ 'on a CRLF checkout this read 21, because every heading in every CRLF file went unparsed',
|
|
139
139
|
);
|
|
140
140
|
});
|
|
141
|
+
|
|
142
|
+
// The duplicate-prose scan skips content that is DUPLICATED BY POLICY: generated blocks,
|
|
143
|
+
// and the ui-design module's PLATFORM PREAMBLE, which ADR 0198 §4 requires byte-identical
|
|
144
|
+
// in every skill the module ships (task 1003325 / ADR 0220 — four in-house copies would
|
|
145
|
+
// have tripped the doc_duplicate_prose ratchet on the policy working as designed). The
|
|
146
|
+
// skip is a marker prefix, so a plain paragraph beside it in the same two files is still
|
|
147
|
+
// flagged: the calibration that proves the skip is narrow.
|
|
148
|
+
test('scanDuplicateProse skips a paragraph opening with the PLATFORM PREAMBLE marker, and still flags a plain duplicate beside it', () => {
|
|
149
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'docs-entropy-preamble-'));
|
|
150
|
+
const plain = 'A plain paragraph long enough to clear the scanner minimum of one hundred and twenty characters, written into two files so the scan has a real duplicate to report.';
|
|
151
|
+
const preamble = [
|
|
152
|
+
'<!-- BEGIN PLATFORM PREAMBLE · a fixture block, long enough to clear the minimum on its own, duplicated on purpose -->',
|
|
153
|
+
'> **Platform preamble.** Read the world first; this block is byte-identical in every skill the module ships.',
|
|
154
|
+
'<!-- END PLATFORM PREAMBLE -->',
|
|
155
|
+
].join('\n');
|
|
156
|
+
const generated = '<!-- BEGIN GENERATED fixture --> a generated block long enough to clear the minimum, also duplicated on purpose, also skipped <!-- END GENERATED -->';
|
|
157
|
+
for (const name of ['a.md', 'b.md']) {
|
|
158
|
+
fs.writeFileSync(path.join(tmp, name), `# ${name}\n\n${plain}\n\n${preamble}\n\n${generated}\n`);
|
|
159
|
+
}
|
|
160
|
+
// scanDuplicateProse resolves each rel against the repo root, so a temp file is
|
|
161
|
+
// addressed by its relative path from there (path.join normalises the `..` hops).
|
|
162
|
+
const rel = (f) => path.relative(ROOT, path.join(tmp, f)).split(path.sep).join('/');
|
|
163
|
+
const dupes = scanDuplicateProse([rel('a.md'), rel('b.md')]);
|
|
164
|
+
assert.equal(dupes.length, 1, `exactly the plain paragraph is flagged, got ${JSON.stringify(dupes.map((d) => d.snippet.slice(0, 40)))}`);
|
|
165
|
+
assert.ok(dupes[0].snippet.startsWith('A plain paragraph'), 'the flagged block is the plain paragraph');
|
|
166
|
+
assert.equal(dupes[0].locations.length, 2, 'seen in both files');
|
|
167
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
168
|
+
});
|
|
@@ -4,7 +4,11 @@
|
|
|
4
4
|
// opens with the PLATFORM PREAMBLE byte-identical to PREAMBLE.md; every name the manifest
|
|
5
5
|
// contributes resolves to a real skill dir; a module-owned skill reaches a builder's
|
|
6
6
|
// materialised .claude/skills with its sidecar (and not when the module is off); /design
|
|
7
|
-
// lists every vendored skill by name; the policy ADR is indexed.
|
|
7
|
+
// lists every vendored skill by name; the policy ADR is indexed. Since task 1003325 (ADR
|
|
8
|
+
// 0220) the same file pins the IN-HOUSE rebuilds under skills/<name>/: the same preamble
|
|
9
|
+
// first and once, a `## Rebuilt from` section instead of a sidecar (the spec task, ADR
|
|
10
|
+
// 0198, "no upstream bytes"), the style library named as the palette source, no image
|
|
11
|
+
// mandate, and the preamble copies proven not to be doc-entropy debt. DB-free.
|
|
8
12
|
import assert from 'node:assert/strict';
|
|
9
13
|
import { test } from 'node:test';
|
|
10
14
|
import { createRequire } from 'node:module';
|
|
@@ -93,6 +97,126 @@ test('every vendored SKILL.md opens with the PLATFORM PREAMBLE, byte-identical t
|
|
|
93
97
|
}
|
|
94
98
|
});
|
|
95
99
|
|
|
100
|
+
// ── every in-house rebuild: the preamble, the Rebuilt-from section, no sidecar (ADR 0220) ──
|
|
101
|
+
const TASTE_CORE = ['design-taste-frontend', 'design-taste-frontend-v1', 'high-end-visual-design', 'redesign-existing-projects'];
|
|
102
|
+
|
|
103
|
+
test('the taste core of task 1003325 exists as four in-house skills and every one is declared', () => {
|
|
104
|
+
for (const name of TASTE_CORE) {
|
|
105
|
+
assert.ok(ownDirs.includes(name), `${name}: skills/${name}/SKILL.md exists`);
|
|
106
|
+
assert.ok(manifest.contributes.skills.includes(name), `${name}: declared in contributes.skills`);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// ── the impeccable method (task 1003326, ADR 0221): one skill, one playbook per sub-command ──
|
|
111
|
+
const IMPECCABLE = path.join(SKILLS, 'impeccable');
|
|
112
|
+
const IMPECCABLE_SUBCOMMANDS = ['shape', 'audit', 'critique', 'polish', 'harden', 'animate', 'typeset', 'colorize', 'layout', 'adapt', 'distill', 'clarify', 'onboard', 'optimize', 'document', 'extract'];
|
|
113
|
+
|
|
114
|
+
test('impeccable is one in-house skill whose sixteen sub-commands each own exactly one playbook under reference/', () => {
|
|
115
|
+
assert.ok(ownDirs.includes('impeccable'), 'skills/impeccable/SKILL.md exists');
|
|
116
|
+
assert.ok(manifest.contributes.skills.includes('impeccable'), 'declared in contributes.skills');
|
|
117
|
+
const skill = read(path.join(IMPECCABLE, 'SKILL.md'));
|
|
118
|
+
const refDir = path.join(IMPECCABLE, 'reference');
|
|
119
|
+
const onDisk = fs.readdirSync(refDir).filter((f) => f.endsWith('.md')).map((f) => f.replace(/\.md$/, '')).sort();
|
|
120
|
+
for (const sub of IMPECCABLE_SUBCOMMANDS) {
|
|
121
|
+
assert.ok(onDisk.includes(sub), `reference/${sub}.md exists (the task body names it)`);
|
|
122
|
+
assert.ok(skill.includes(`reference/${sub}.md`), `SKILL.md routes \`${sub}\` to its playbook`);
|
|
123
|
+
}
|
|
124
|
+
for (const ref of onDisk) assert.ok(skill.includes(`reference/${ref}.md`), `reference/${ref}.md is routed from SKILL.md (no orphan playbook)`);
|
|
125
|
+
for (const ref of onDisk) {
|
|
126
|
+
const text = read(path.join(refDir, `${ref}.md`));
|
|
127
|
+
assert.ok(!text.includes(policy.preamble.begin), `reference/${ref}.md carries no preamble copy (the block is pinned once, in SKILL.md, and governs every playbook)`);
|
|
128
|
+
assert.ok(text.trim().length > 400, `reference/${ref}.md is a playbook, not a stub`);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test('impeccable carries none of the behaviour that made its origin dangerous: no hooks, no bypass agent, no telemetry, no remote bundle, no live-server token', () => {
|
|
133
|
+
const files = [path.join(IMPECCABLE, 'SKILL.md'), ...fs.readdirSync(path.join(IMPECCABLE, 'reference')).map((f) => path.join(IMPECCABLE, 'reference', f))];
|
|
134
|
+
for (const f of files) {
|
|
135
|
+
const text = read(f);
|
|
136
|
+
const rel = path.relative(ROOT, f).split(path.sep).join('/');
|
|
137
|
+
assert.doesNotMatch(text, /bypassPermissions|--dangerously-skip-permissions/, `${rel}: no permission bypass`);
|
|
138
|
+
assert.doesNotMatch(text, /\b(write|add|install|register)\b[^.\n]{0,60}\b(PostToolUse|PreToolUse|Stop)\b[^.\n]{0,40}\bhooks?\b/i, `${rel}: no step writes a hook into a harness config`);
|
|
139
|
+
assert.doesNotMatch(text, /\b(write|edit|patch)\b[^.\n]{0,40}\bsettings(\.local)?\.json\b/i, `${rel}: no step edits settings.json`);
|
|
140
|
+
assert.doesNotMatch(text, /\b(enable|send|collect|report|emit)s?\b[^.\n]{0,40}\btelemetry\b|\btelemetry\b[^.\n]{0,40}\b(is|are|stays|defaults?)\s+(on|enabled|sent)\b/i, `${rel}: no step turns on or sends telemetry (naming the ban is fine; directing it is not)`);
|
|
141
|
+
assert.doesNotMatch(text, /\b(curl|wget|fetch|download)\b[^.\n]{0,60}\bhttps?:\/\//i, `${rel}: no remote bundle download`);
|
|
142
|
+
assert.doesNotMatch(text, /\b(auth|access|bearer|helper)\s+token\b/i, `${rel}: no live-server token`);
|
|
143
|
+
assert.doesNotMatch(text, /(disregard|ignore)\b[^.\n]{0,40}\b(system prompt|harness)/i, `${rel}: no instruction-override directive`);
|
|
144
|
+
}
|
|
145
|
+
const skill = read(path.join(IMPECCABLE, 'SKILL.md'));
|
|
146
|
+
assert.match(skill, /playwright/i, 'the live loop is the playwright plugin, named as a prerequisite');
|
|
147
|
+
assert.match(skill, /kit\/render\.js/, 'the headless loop is the kit');
|
|
148
|
+
assert.match(skill, /1003329/, 'the detector is named as its own task, not shipped here');
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test('every in-house SKILL.md opens with the PLATFORM PREAMBLE, byte-identical to PREAMBLE.md, exactly once, right after its frontmatter', () => {
|
|
152
|
+
const pre = read(path.join(SKILLS, 'PREAMBLE.md')).trimEnd();
|
|
153
|
+
for (const name of ownDirs) {
|
|
154
|
+
const text = read(path.join(SKILLS, name, 'SKILL.md'));
|
|
155
|
+
const fm = text.match(/^---\n[\s\S]*?\n---\n/);
|
|
156
|
+
assert.ok(fm, `${name}: SKILL.md keeps its frontmatter first`);
|
|
157
|
+
const body = text.slice(fm[0].length).replace(/^\n+/, '');
|
|
158
|
+
assert.ok(body.startsWith(pre), `${name}: the preamble block is the first thing after the frontmatter and matches PREAMBLE.md byte for byte`);
|
|
159
|
+
assert.equal(text.split(policy.preamble.begin).length, 2, `${name}: the preamble appears exactly once`);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test('every in-house skill names its dir, carries a description, and says what it was rebuilt from instead of carrying a sidecar', () => {
|
|
164
|
+
const pre = read(path.join(SKILLS, 'PREAMBLE.md')).trimEnd();
|
|
165
|
+
for (const name of ownDirs) {
|
|
166
|
+
const dir = path.join(SKILLS, name);
|
|
167
|
+
const text = read(path.join(dir, 'SKILL.md'));
|
|
168
|
+
const fm = frontmatter(text);
|
|
169
|
+
assert.equal(fm.name, name, `${name}: frontmatter names its own dir`);
|
|
170
|
+
assert.ok(fm.description && fm.description.length > 80, `${name}: a description the harness can route on`);
|
|
171
|
+
assert.ok(!fs.existsSync(path.join(dir, policy.sidecar)), `${name}: an in-house skill claims no upstream, so no ${policy.sidecar}`);
|
|
172
|
+
assert.ok(!fs.existsSync(path.join(dir, 'LICENSE')), `${name}: no upstream licence text travels with a rebuild`);
|
|
173
|
+
const afterPreamble = text.slice(text.indexOf(pre) + pre.length);
|
|
174
|
+
const section = afterPreamble.match(/^## Rebuilt from\n([\s\S]*?)(?=\n## )/m);
|
|
175
|
+
assert.ok(section, `${name}: a "## Rebuilt from" section is the first thing after the preamble`);
|
|
176
|
+
assert.match(afterPreamble.trimStart(), /^## Rebuilt from\n/, `${name}: nothing sits between the preamble and the Rebuilt-from section`);
|
|
177
|
+
assert.match(section[1], /builders#\/task\/\d+/, `${name}: the section links the spec task`);
|
|
178
|
+
assert.match(section[1], /ADR 0198/, `${name}: the section names the vendoring policy that made it a rebuild`);
|
|
179
|
+
assert.match(section[1], /REBUILD-SPEC/, `${name}: the section names the spec`);
|
|
180
|
+
assert.match(section[1], /no upstream bytes/i, `${name}: the section states no upstream bytes were consulted or copied`);
|
|
181
|
+
assert.match(section[1], /what changed in translation/i, `${name}: the section says where the rebuild departs from the documented behaviour`);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test('every in-house skill works inside the world: the style library is the palette source, images are hero plates only, and no step mandates generating one', () => {
|
|
186
|
+
const pre = read(path.join(SKILLS, 'PREAMBLE.md')).trimEnd();
|
|
187
|
+
for (const name of ownDirs) {
|
|
188
|
+
const text = read(path.join(SKILLS, name, 'SKILL.md'));
|
|
189
|
+
const own = text.slice(text.indexOf(pre) + pre.length); // the skill's own rules, not the shared block
|
|
190
|
+
assert.ok(own.includes('modules/ui-design/styles/'), `${name}: names the style library as where a variant comes from`);
|
|
191
|
+
assert.match(own, /hero plates/i, `${name}: restates the owner's imagery rule in its own rules`);
|
|
192
|
+
assert.doesNotMatch(own, /\b(must|always|first|then)\s+generate\s+(an?\s+|the\s+)?(image|asset|photo|render)/i, `${name}: no step mandates image generation (ADR 0198's panel finding; the owner's rule)`);
|
|
193
|
+
assert.doesNotMatch(own, /\b(use|add|insert|prefer|fall back to|source)\s+(seeded\s+)?placeholder (photo|imag)/i, `${name}: no step reaches for seeded placeholder photography (naming the ban is fine; directing it is not)`);
|
|
194
|
+
assert.match(own, /Kill Switch|prefers-reduced-motion/, `${name}: its motion rules name the switch they live inside`);
|
|
195
|
+
assert.match(own, /kit\/render\.js/, `${name}: the kit's render is its pre-flight`);
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test('the preamble copies are policy, not doc-entropy debt: the scanner skips the block and still flags a plain duplicate', () => {
|
|
200
|
+
const de = require(path.join(ROOT, 'scripts', 'gds', 'docs-entropy.js'));
|
|
201
|
+
const carriers = ['modules/ui-design/skills/PREAMBLE.md', ...ownDirs.map((n) => `modules/ui-design/skills/${n}/SKILL.md`)];
|
|
202
|
+
assert.ok(carriers.length >= 2, 'at least one own skill carries the block beside PREAMBLE.md');
|
|
203
|
+
for (const rel of carriers) assert.ok(read(path.join(ROOT, rel)).includes(policy.preamble.begin), `${rel} carries the block`);
|
|
204
|
+
const dupes = de.scanDuplicateProse(carriers);
|
|
205
|
+
assert.ok(!dupes.some((d) => d.snippet.startsWith(policy.preamble.begin)), 'the byte-identical preamble is not reported as duplicate prose');
|
|
206
|
+
// calibration: the same scanner over the same files still sees a plain paragraph twice
|
|
207
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'ui-skills-dupe-'));
|
|
208
|
+
const para = 'A plain paragraph long enough to clear the scanner\'s 120-character floor, written twice so the duplicate-prose scan has something real to report.';
|
|
209
|
+
const a = path.join(tmp, 'a.md');
|
|
210
|
+
const b = path.join(tmp, 'b.md');
|
|
211
|
+
fs.writeFileSync(a, `# a\n\n${para}\n\n${read(path.join(SKILLS, 'PREAMBLE.md'))}`);
|
|
212
|
+
fs.writeFileSync(b, `# b\n\n${para}\n\n${read(path.join(SKILLS, 'PREAMBLE.md'))}`);
|
|
213
|
+
const rel = (p) => path.relative(ROOT, p).split(path.sep).join('/');
|
|
214
|
+
const control = de.scanDuplicateProse([rel(a), rel(b)]);
|
|
215
|
+
assert.equal(control.length, 1, 'the plain paragraph is flagged once');
|
|
216
|
+
assert.ok(control[0].snippet.startsWith('A plain paragraph'), 'and it is the plain paragraph, not the preamble');
|
|
217
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
218
|
+
});
|
|
219
|
+
|
|
96
220
|
// ── the manifest declares what is on disk, and nothing else ─────────────────
|
|
97
221
|
test('every skill the manifest contributes resolves to a skill dir (core .claude/skills or the module), and every module skill dir is declared', () => {
|
|
98
222
|
const declared = manifest.contributes.skills;
|
|
@@ -145,24 +269,31 @@ test('a module-owned skill lands in a materialised .claude/skills with its sidec
|
|
|
145
269
|
});
|
|
146
270
|
|
|
147
271
|
// ── the doors: /design lists them, the README rosters them, the ADR is indexed ──
|
|
148
|
-
test('/design lists every
|
|
272
|
+
test('/design lists every module skill (vendored or in-house) by name, one line each, and names the playwright plugin as a prerequisite', () => {
|
|
149
273
|
const design = read(path.join(ROOT, '.claude', 'skills', 'design', 'SKILL.md'));
|
|
150
|
-
for (const name of vendoredDirs) {
|
|
274
|
+
for (const name of [...vendoredDirs, ...ownDirs]) {
|
|
151
275
|
const line = design.split('\n').find((l) => l.includes(`\`/${name}\``) || l.includes(`**${name}**`));
|
|
152
276
|
assert.ok(line, `/design names ${name} on a line of its own`);
|
|
153
277
|
}
|
|
154
278
|
assert.match(design, /playwright/i, '/design names the playwright plugin');
|
|
155
279
|
});
|
|
156
280
|
|
|
157
|
-
test('the skills README
|
|
281
|
+
test('the skills README and the module CLAUDE.md roster every module skill by name', () => {
|
|
158
282
|
const readme = read(path.join(SKILLS, 'README.md'));
|
|
159
|
-
|
|
283
|
+
const claude = read(path.join(MODULE, 'CLAUDE.md'));
|
|
284
|
+
for (const name of [...vendoredDirs, ...ownDirs]) {
|
|
285
|
+
assert.ok(readme.includes(`\`${name}\``), `README rosters ${name}`);
|
|
286
|
+
assert.ok(claude.includes(`\`${name}\``), `modules/ui-design/CLAUDE.md names ${name}`);
|
|
287
|
+
}
|
|
160
288
|
assert.ok(readme.includes('policy.json') && readme.includes('PREAMBLE.md') && readme.includes('PROVENANCE.md'));
|
|
289
|
+
assert.ok(readme.includes('Rebuilt from'), 'the README says what a rebuild carries instead of a sidecar');
|
|
161
290
|
});
|
|
162
291
|
|
|
163
|
-
test('the vendoring
|
|
164
|
-
const adr = fs.readdirSync(path.join(ROOT, 'docs', 'adr')).find((f) => /^0198-/.test(f));
|
|
165
|
-
assert.ok(adr, 'docs/adr/0198-*.md exists');
|
|
292
|
+
test('the vendoring-policy ADR and the in-house-rebuild ADR exist and are indexed', () => {
|
|
166
293
|
const index = read(path.join(ROOT, 'docs', 'adr', 'README.md'));
|
|
167
|
-
|
|
294
|
+
for (const n of ['0198', '0220']) {
|
|
295
|
+
const adr = fs.readdirSync(path.join(ROOT, 'docs', 'adr')).find((f) => f.startsWith(`${n}-`));
|
|
296
|
+
assert.ok(adr, `docs/adr/${n}-*.md exists`);
|
|
297
|
+
assert.ok(index.includes(`](${adr})`), `docs/adr/README.md links ADR ${n}`);
|
|
298
|
+
}
|
|
168
299
|
});
|