Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .github/workflows/lint.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -688,6 +688,24 @@ jobs:
- name: ROOT_DIR_WATCH_HINTS declarations are literals
run: pnpm check:watch-hint-literal

# The other half of the same failure (#13519). The step above holds that a
# declaration is still READABLE BY A TEXT SCANNER; this one holds that
# what it reads still names something. A family whose declared literals
# all reach zero tracked files prints as an ORDINARY SILENCE — byte for
# byte the output of a gate that declared nothing — so the derivation and
# the gate disagree about whether a population was declared and neither
# says so. Measured on the tree this landed against: two families, one of
# them a gate that sweeps every workflow file and appeared on no card that
# edited one. The rule is per-FAMILY, not per-literal: a gate may spell a
# slug or a sentinel path beside a live declaration, and only a whole
# declaration that reaches nothing is refused. No allowlist and no gate
# names — the stronger rule ("a gate that enumerates a directory must
# declare one") was implemented, measured at 86 findings over 114
# enumerating gate files, and refused as an allowlist with a verdict
# attached. Reads the derivation once over the tracked corpus; ~5s.
- name: A declared gate population reaches the tree
run: pnpm check:declared-population-live

# PM bare-root worklist self-test (#10840). The step above proves the
# dispatch derivation still WORKS; this one proves the recorded triage of
# the gates that derivation structurally cannot see is still true of the
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -70,6 +70,7 @@
"check:pm-label-desc-cap": "node scripts/pm/check-label-desc-cap.mjs --self-test && node scripts/pm/check-label-desc-cap.mjs",
"check:pm-dispatch-gates": "node scripts/pm/check-dispatch-gates.mjs",
"check:watch-hint-literal": "node scripts/check-watch-hint-literal.mjs --self-test && node scripts/check-watch-hint-literal.mjs",
"check:declared-population-live": "node scripts/check-declared-population-live.mjs --self-test && node scripts/check-declared-population-live.mjs",
"check:pm-half-states": "node scripts/pm/check-half-states.mjs --self-test",
"check:pm-clause2-carriers": "node scripts/pm/check-clause2-carriers.mjs --self-test",
"check:pm-governed-merges": "node scripts/pm/check-governed-merges.mjs --self-test",
Expand Down
70 changes: 68 additions & 2 deletions packages/spec/scripts/build-skill-docs.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,9 +26,73 @@ import path from 'path';

const REPO_ROOT = path.resolve(__dirname, '../../..');
const SKILLS_DIR = path.resolve(REPO_ROOT, 'skills');
/** The file a child of the skills root must CARRY to be a skill (`:main` admits on it). */
const ENTRYPOINT_FILE = 'SKILL.md';
const README = path.resolve(SKILLS_DIR, 'README.md');
const GUIDE = path.resolve(REPO_ROOT, 'content/docs/ai/skills-reference.mdx');

/**
* The population this script READS, declared for `scripts/pm/dispatch-gates.mjs`
* — spelled as a LITERAL array because the hint extractor reads source TEXT (a
* declaration computed from `SKILLS_DIR` would build no hint at all;
* `scripts/check-watch-hint-literal.mjs` holds that spelling for every declarer
* in the tree).
*
* ## What was declared before, and why it under-matched
*
* The only path literal this module body spelled was the GUIDE path,
* `content/docs/ai/skills-reference.mdx` — an OUTPUT. So `check:skill-docs`
* declared its generated artifact and not one of its inputs: a card editing a
* skill entrypoint — the file whose frontmatter is the entire catalog — was
* never told that this gate reads it, and `--check` reds in CI on drift the
* derivation could have predicted.
*
* The inputs were not forgotten, they were unspellable AS ONE HINT: `SKILLS_DIR`
* is built with `path.resolve(REPO_ROOT, 'skills')`, and a single-segment
* literal is refused by the extractor as too generic to be a path population.
*
* ## Why NOT the subtree spelling, which is the idiom's usual escape
*
* `scripts/pm/bare-root-worklist.mjs` carries the measured triage for exactly
* this root and refuses the wholesale hint: this script's population is 12 of
* the 50 files tracked under it (24%), so `skills/**` would name this gate for
* 38 files it never opens. That is the REFUSE-WIDE trade, and the worklist
* prices a false wholesale hint as the costlier error.
*
* What the recorded refusal turned on is that no SINGLE spelling reaches both
* halves: `:221` readdirs the root and admits a child only if it carries a
* SKILL.md, while the twelfth file is the root README this script WRITES, which
* sits outside every skill directory. Two literals reach both, and reach
* nothing else — 12 of 12, precise AND complete — which is the declaration
* below and why the row's verdict is withdrawn rather than the declaration.
*/
const DECLARED_WATCH_HINTS = ['skills/*/SKILL.md', 'skills/README.md'];

/**
* The declaration above, held against the constants this script really reads
* from. A hand-written path that agreed with the read only on the day it was
* typed is the drift this idiom replaces, so the check derives what it compares
* against from `SKILLS_DIR` and `README` — move either and this throws here, in
* this file, instead of going quiet in a dispatch brief.
*/
function assertWatchHintsDeclareTheReadSurface(): void {
const rel = (abs: string) => path.relative(REPO_ROOT, abs).split(path.sep).join('/');
const expected = [`${rel(SKILLS_DIR)}/*/${ENTRYPOINT_FILE}`, rel(README)];
if (DECLARED_WATCH_HINTS.join('|') !== expected.join('|')) {
throw new Error(
`build-skill-docs: the declared watch-hint population ${JSON.stringify(DECLARED_WATCH_HINTS)} no longer ` +
`names what this script reads (${JSON.stringify(expected)}) — update the declaration, as a LITERAL array.`,
);
}
// …and it stays NARROW. A hint that reached the bare root would be the
// wholesale claim the worklist refused, arriving through a reword.
if (DECLARED_WATCH_HINTS.some((h) => h.endsWith('/**'))) {
throw new Error(
`build-skill-docs: the declaration must stay narrower than the root: ${JSON.stringify(DECLARED_WATCH_HINTS)}`,
);
}
}

// Marker comments delimit the generated region. MDX does not support HTML
// comments (`<!-- -->`) — it needs `{/* */}` — so the syntax is per file type.
type CommentStyle = 'html' | 'mdx';
Expand DownExpand Up@@ -78,7 +142,7 @@ interface Skill {
}

function parseFrontmatter(name: string, label: string): Skill {
const file = path.resolve(SKILLS_DIR, name, 'SKILL.md');
const file = path.resolve(SKILLS_DIR, name, ENTRYPOINT_FILE);
const raw = fs.readFileSync(file, 'utf-8');
const parts = raw.split(/^---\s*$/m);
if (parts.length < 3) throw new Error(`${name}: no YAML frontmatter`);
Expand DownExpand Up@@ -216,10 +280,12 @@ function spliceBlock(file: string, block: string, style: CommentStyle): string {
function main() {
const check = process.argv.includes('--check');

assertWatchHintsDeclareTheReadSurface();

// Catalog ⇄ DISPLAY must be in lockstep.
const onDisk = fs
.readdirSync(SKILLS_DIR)
.filter((d) => d.startsWith('objectstack-') && fs.existsSync(path.resolve(SKILLS_DIR, d, 'SKILL.md')));
.filter((d) => d.startsWith('objectstack-') && fs.existsSync(path.resolve(SKILLS_DIR, d, ENTRYPOINT_FILE)));
const configured = new Set(DISPLAY.map((d) => d.name));
const missing = onDisk.filter((d) => !configured.has(d));
const extra = DISPLAY.filter((d) => !onDisk.includes(d.name)).map((d) => d.name);
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .github/workflows/lint.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -688,6 +688,24 @@ jobs:
- name: ROOT_DIR_WATCH_HINTS declarations are literals
run: pnpm check:watch-hint-literal

# The other half of the same failure (#13519). The step above holds that a
# declaration is still READABLE BY A TEXT SCANNER; this one holds that
# what it reads still names something. A family whose declared literals
# all reach zero tracked files prints as an ORDINARY SILENCE — byte for
# byte the output of a gate that declared nothing — so the derivation and
# the gate disagree about whether a population was declared and neither
# says so. Measured on the tree this landed against: two families, one of
# them a gate that sweeps every workflow file and appeared on no card that
# edited one. The rule is per-FAMILY, not per-literal: a gate may spell a
# slug or a sentinel path beside a live declaration, and only a whole
# declaration that reaches nothing is refused. No allowlist and no gate
# names — the stronger rule ("a gate that enumerates a directory must
# declare one") was implemented, measured at 86 findings over 114
# enumerating gate files, and refused as an allowlist with a verdict
# attached. Reads the derivation once over the tracked corpus; ~5s.
- name: A declared gate population reaches the tree
run: pnpm check:declared-population-live

# PM bare-root worklist self-test (#10840). The step above proves the
# dispatch derivation still WORKS; this one proves the recorded triage of
# the gates that derivation structurally cannot see is still true of the
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -70,6 +70,7 @@
"check:pm-label-desc-cap": "node scripts/pm/check-label-desc-cap.mjs --self-test && node scripts/pm/check-label-desc-cap.mjs",
"check:pm-dispatch-gates": "node scripts/pm/check-dispatch-gates.mjs",
"check:watch-hint-literal": "node scripts/check-watch-hint-literal.mjs --self-test && node scripts/check-watch-hint-literal.mjs",
"check:declared-population-live": "node scripts/check-declared-population-live.mjs --self-test && node scripts/check-declared-population-live.mjs",
"check:pm-half-states": "node scripts/pm/check-half-states.mjs --self-test",
"check:pm-clause2-carriers": "node scripts/pm/check-clause2-carriers.mjs --self-test",
"check:pm-governed-merges": "node scripts/pm/check-governed-merges.mjs --self-test",
Expand Down
70 changes: 68 additions & 2 deletions packages/spec/scripts/build-skill-docs.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,9 +26,73 @@ import path from 'path';

const REPO_ROOT = path.resolve(__dirname, '../../..');
const SKILLS_DIR = path.resolve(REPO_ROOT, 'skills');
/** The file a child of the skills root must CARRY to be a skill (`:main` admits on it). */
const ENTRYPOINT_FILE = 'SKILL.md';
const README = path.resolve(SKILLS_DIR, 'README.md');
const GUIDE = path.resolve(REPO_ROOT, 'content/docs/ai/skills-reference.mdx');

/**
* The population this script READS, declared for `scripts/pm/dispatch-gates.mjs`
* — spelled as a LITERAL array because the hint extractor reads source TEXT (a
* declaration computed from `SKILLS_DIR` would build no hint at all;
* `scripts/check-watch-hint-literal.mjs` holds that spelling for every declarer
* in the tree).
*
* ## What was declared before, and why it under-matched
*
* The only path literal this module body spelled was the GUIDE path,
* `content/docs/ai/skills-reference.mdx` — an OUTPUT. So `check:skill-docs`
* declared its generated artifact and not one of its inputs: a card editing a
* skill entrypoint — the file whose frontmatter is the entire catalog — was
* never told that this gate reads it, and `--check` reds in CI on drift the
* derivation could have predicted.
*
* The inputs were not forgotten, they were unspellable AS ONE HINT: `SKILLS_DIR`
* is built with `path.resolve(REPO_ROOT, 'skills')`, and a single-segment
* literal is refused by the extractor as too generic to be a path population.
*
* ## Why NOT the subtree spelling, which is the idiom's usual escape
*
* `scripts/pm/bare-root-worklist.mjs` carries the measured triage for exactly
* this root and refuses the wholesale hint: this script's population is 12 of
* the 50 files tracked under it (24%), so `skills/**` would name this gate for
* 38 files it never opens. That is the REFUSE-WIDE trade, and the worklist
* prices a false wholesale hint as the costlier error.
*
* What the recorded refusal turned on is that no SINGLE spelling reaches both
* halves: `:221` readdirs the root and admits a child only if it carries a
* SKILL.md, while the twelfth file is the root README this script WRITES, which
* sits outside every skill directory. Two literals reach both, and reach
* nothing else — 12 of 12, precise AND complete — which is the declaration
* below and why the row's verdict is withdrawn rather than the declaration.
*/
const DECLARED_WATCH_HINTS = ['skills/*/SKILL.md', 'skills/README.md'];

/**
* The declaration above, held against the constants this script really reads
* from. A hand-written path that agreed with the read only on the day it was
* typed is the drift this idiom replaces, so the check derives what it compares
* against from `SKILLS_DIR` and `README` — move either and this throws here, in
* this file, instead of going quiet in a dispatch brief.
*/
function assertWatchHintsDeclareTheReadSurface(): void {
const rel = (abs: string) => path.relative(REPO_ROOT, abs).split(path.sep).join('/');
const expected = [`${rel(SKILLS_DIR)}/*/${ENTRYPOINT_FILE}`, rel(README)];
if (DECLARED_WATCH_HINTS.join('|') !== expected.join('|')) {
throw new Error(
`build-skill-docs: the declared watch-hint population ${JSON.stringify(DECLARED_WATCH_HINTS)} no longer ` +
`names what this script reads (${JSON.stringify(expected)}) — update the declaration, as a LITERAL array.`,
);
}
// …and it stays NARROW. A hint that reached the bare root would be the
// wholesale claim the worklist refused, arriving through a reword.
if (DECLARED_WATCH_HINTS.some((h) => h.endsWith('/**'))) {
throw new Error(
`build-skill-docs: the declaration must stay narrower than the root: ${JSON.stringify(DECLARED_WATCH_HINTS)}`,
);
}
}

// Marker comments delimit the generated region. MDX does not support HTML
// comments (`<!-- -->`) — it needs `{/* */}` — so the syntax is per file type.
type CommentStyle = 'html' | 'mdx';
Expand DownExpand Up@@ -78,7 +142,7 @@ interface Skill {
}

function parseFrontmatter(name: string, label: string): Skill {
const file = path.resolve(SKILLS_DIR, name, 'SKILL.md');
const file = path.resolve(SKILLS_DIR, name, ENTRYPOINT_FILE);
const raw = fs.readFileSync(file, 'utf-8');
const parts = raw.split(/^---\s*$/m);
if (parts.length < 3) throw new Error(`${name}: no YAML frontmatter`);
Expand DownExpand Up@@ -216,10 +280,12 @@ function spliceBlock(file: string, block: string, style: CommentStyle): string {
function main() {
const check = process.argv.includes('--check');

assertWatchHintsDeclareTheReadSurface();

// Catalog ⇄ DISPLAY must be in lockstep.
const onDisk = fs
.readdirSync(SKILLS_DIR)
.filter((d) => d.startsWith('objectstack-') && fs.existsSync(path.resolve(SKILLS_DIR, d, 'SKILL.md')));
.filter((d) => d.startsWith('objectstack-') && fs.existsSync(path.resolve(SKILLS_DIR, d, ENTRYPOINT_FILE)));
const configured = new Set(DISPLAY.map((d) => d.name));
const missing = onDisk.filter((d) => !configured.has(d));
const extra = DISPLAY.filter((d) => !onDisk.includes(d.name)).map((d) => d.name);
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .github/workflows/lint.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -688,6 +688,24 @@ jobs:
- name: ROOT_DIR_WATCH_HINTS declarations are literals
run: pnpm check:watch-hint-literal

# The other half of the same failure (#13519). The step above holds that a
# declaration is still READABLE BY A TEXT SCANNER; this one holds that
# what it reads still names something. A family whose declared literals
# all reach zero tracked files prints as an ORDINARY SILENCE — byte for
# byte the output of a gate that declared nothing — so the derivation and
# the gate disagree about whether a population was declared and neither
# says so. Measured on the tree this landed against: two families, one of
# them a gate that sweeps every workflow file and appeared on no card that
# edited one. The rule is per-FAMILY, not per-literal: a gate may spell a
# slug or a sentinel path beside a live declaration, and only a whole
# declaration that reaches nothing is refused. No allowlist and no gate
# names — the stronger rule ("a gate that enumerates a directory must
# declare one") was implemented, measured at 86 findings over 114
# enumerating gate files, and refused as an allowlist with a verdict
# attached. Reads the derivation once over the tracked corpus; ~5s.
- name: A declared gate population reaches the tree
run: pnpm check:declared-population-live

# PM bare-root worklist self-test (#10840). The step above proves the
# dispatch derivation still WORKS; this one proves the recorded triage of
# the gates that derivation structurally cannot see is still true of the
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -70,6 +70,7 @@
"check:pm-label-desc-cap": "node scripts/pm/check-label-desc-cap.mjs --self-test && node scripts/pm/check-label-desc-cap.mjs",
"check:pm-dispatch-gates": "node scripts/pm/check-dispatch-gates.mjs",
"check:watch-hint-literal": "node scripts/check-watch-hint-literal.mjs --self-test && node scripts/check-watch-hint-literal.mjs",
"check:declared-population-live": "node scripts/check-declared-population-live.mjs --self-test && node scripts/check-declared-population-live.mjs",
"check:pm-half-states": "node scripts/pm/check-half-states.mjs --self-test",
"check:pm-clause2-carriers": "node scripts/pm/check-clause2-carriers.mjs --self-test",
"check:pm-governed-merges": "node scripts/pm/check-governed-merges.mjs --self-test",
Expand Down
70 changes: 68 additions & 2 deletions packages/spec/scripts/build-skill-docs.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,9 +26,73 @@ import path from 'path';

const REPO_ROOT = path.resolve(__dirname, '../../..');
const SKILLS_DIR = path.resolve(REPO_ROOT, 'skills');
/** The file a child of the skills root must CARRY to be a skill (`:main` admits on it). */
const ENTRYPOINT_FILE = 'SKILL.md';
const README = path.resolve(SKILLS_DIR, 'README.md');
const GUIDE = path.resolve(REPO_ROOT, 'content/docs/ai/skills-reference.mdx');

/**
* The population this script READS, declared for `scripts/pm/dispatch-gates.mjs`
* — spelled as a LITERAL array because the hint extractor reads source TEXT (a
* declaration computed from `SKILLS_DIR` would build no hint at all;
* `scripts/check-watch-hint-literal.mjs` holds that spelling for every declarer
* in the tree).
*
* ## What was declared before, and why it under-matched
*
* The only path literal this module body spelled was the GUIDE path,
* `content/docs/ai/skills-reference.mdx` — an OUTPUT. So `check:skill-docs`
* declared its generated artifact and not one of its inputs: a card editing a
* skill entrypoint — the file whose frontmatter is the entire catalog — was
* never told that this gate reads it, and `--check` reds in CI on drift the
* derivation could have predicted.
*
* The inputs were not forgotten, they were unspellable AS ONE HINT: `SKILLS_DIR`
* is built with `path.resolve(REPO_ROOT, 'skills')`, and a single-segment
* literal is refused by the extractor as too generic to be a path population.
*
* ## Why NOT the subtree spelling, which is the idiom's usual escape
*
* `scripts/pm/bare-root-worklist.mjs` carries the measured triage for exactly
* this root and refuses the wholesale hint: this script's population is 12 of
* the 50 files tracked under it (24%), so `skills/**` would name this gate for
* 38 files it never opens. That is the REFUSE-WIDE trade, and the worklist
* prices a false wholesale hint as the costlier error.
*
* What the recorded refusal turned on is that no SINGLE spelling reaches both
* halves: `:221` readdirs the root and admits a child only if it carries a
* SKILL.md, while the twelfth file is the root README this script WRITES, which
* sits outside every skill directory. Two literals reach both, and reach
* nothing else — 12 of 12, precise AND complete — which is the declaration
* below and why the row's verdict is withdrawn rather than the declaration.
*/
const DECLARED_WATCH_HINTS = ['skills/*/SKILL.md', 'skills/README.md'];

/**
* The declaration above, held against the constants this script really reads
* from. A hand-written path that agreed with the read only on the day it was
* typed is the drift this idiom replaces, so the check derives what it compares
* against from `SKILLS_DIR` and `README` — move either and this throws here, in
* this file, instead of going quiet in a dispatch brief.
*/
function assertWatchHintsDeclareTheReadSurface(): void {
const rel = (abs: string) => path.relative(REPO_ROOT, abs).split(path.sep).join('/');
const expected = [`${rel(SKILLS_DIR)}/*/${ENTRYPOINT_FILE}`, rel(README)];
if (DECLARED_WATCH_HINTS.join('|') !== expected.join('|')) {
throw new Error(
`build-skill-docs: the declared watch-hint population ${JSON.stringify(DECLARED_WATCH_HINTS)} no longer ` +
`names what this script reads (${JSON.stringify(expected)}) — update the declaration, as a LITERAL array.`,
);
}
// …and it stays NARROW. A hint that reached the bare root would be the
// wholesale claim the worklist refused, arriving through a reword.
if (DECLARED_WATCH_HINTS.some((h) => h.endsWith('/**'))) {
throw new Error(
`build-skill-docs: the declaration must stay narrower than the root: ${JSON.stringify(DECLARED_WATCH_HINTS)}`,
);
}
}

// Marker comments delimit the generated region. MDX does not support HTML
// comments (`<!-- -->`) — it needs `{/* */}` — so the syntax is per file type.
type CommentStyle = 'html' | 'mdx';
Expand DownExpand Up@@ -78,7 +142,7 @@ interface Skill {
}

function parseFrontmatter(name: string, label: string): Skill {
const file = path.resolve(SKILLS_DIR, name, 'SKILL.md');
const file = path.resolve(SKILLS_DIR, name, ENTRYPOINT_FILE);
const raw = fs.readFileSync(file, 'utf-8');
const parts = raw.split(/^---\s*$/m);
if (parts.length < 3) throw new Error(`${name}: no YAML frontmatter`);
Expand DownExpand Up@@ -216,10 +280,12 @@ function spliceBlock(file: string, block: string, style: CommentStyle): string {
function main() {
const check = process.argv.includes('--check');

assertWatchHintsDeclareTheReadSurface();

// Catalog ⇄ DISPLAY must be in lockstep.
const onDisk = fs
.readdirSync(SKILLS_DIR)
.filter((d) => d.startsWith('objectstack-') && fs.existsSync(path.resolve(SKILLS_DIR, d, 'SKILL.md')));
.filter((d) => d.startsWith('objectstack-') && fs.existsSync(path.resolve(SKILLS_DIR, d, ENTRYPOINT_FILE)));
const configured = new Set(DISPLAY.map((d) => d.name));
const missing = onDisk.filter((d) => !configured.has(d));
const extra = DISPLAY.filter((d) => !onDisk.includes(d.name)).map((d) => d.name);
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .github/workflows/lint.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -688,6 +688,24 @@ jobs:
- name: ROOT_DIR_WATCH_HINTS declarations are literals
run: pnpm check:watch-hint-literal

# The other half of the same failure (#13519). The step above holds that a
# declaration is still READABLE BY A TEXT SCANNER; this one holds that
# what it reads still names something. A family whose declared literals
# all reach zero tracked files prints as an ORDINARY SILENCE — byte for
# byte the output of a gate that declared nothing — so the derivation and
# the gate disagree about whether a population was declared and neither
# says so. Measured on the tree this landed against: two families, one of
# them a gate that sweeps every workflow file and appeared on no card that
# edited one. The rule is per-FAMILY, not per-literal: a gate may spell a
# slug or a sentinel path beside a live declaration, and only a whole
# declaration that reaches nothing is refused. No allowlist and no gate
# names — the stronger rule ("a gate that enumerates a directory must
# declare one") was implemented, measured at 86 findings over 114
# enumerating gate files, and refused as an allowlist with a verdict
# attached. Reads the derivation once over the tracked corpus; ~5s.
- name: A declared gate population reaches the tree
run: pnpm check:declared-population-live

# PM bare-root worklist self-test (#10840). The step above proves the
# dispatch derivation still WORKS; this one proves the recorded triage of
# the gates that derivation structurally cannot see is still true of the
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -70,6 +70,7 @@
"check:pm-label-desc-cap": "node scripts/pm/check-label-desc-cap.mjs --self-test && node scripts/pm/check-label-desc-cap.mjs",
"check:pm-dispatch-gates": "node scripts/pm/check-dispatch-gates.mjs",
"check:watch-hint-literal": "node scripts/check-watch-hint-literal.mjs --self-test && node scripts/check-watch-hint-literal.mjs",
"check:declared-population-live": "node scripts/check-declared-population-live.mjs --self-test && node scripts/check-declared-population-live.mjs",
"check:pm-half-states": "node scripts/pm/check-half-states.mjs --self-test",
"check:pm-clause2-carriers": "node scripts/pm/check-clause2-carriers.mjs --self-test",
"check:pm-governed-merges": "node scripts/pm/check-governed-merges.mjs --self-test",
Expand Down
70 changes: 68 additions & 2 deletions packages/spec/scripts/build-skill-docs.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,9 +26,73 @@ import path from 'path';

const REPO_ROOT = path.resolve(__dirname, '../../..');
const SKILLS_DIR = path.resolve(REPO_ROOT, 'skills');
/** The file a child of the skills root must CARRY to be a skill (`:main` admits on it). */
const ENTRYPOINT_FILE = 'SKILL.md';
const README = path.resolve(SKILLS_DIR, 'README.md');
const GUIDE = path.resolve(REPO_ROOT, 'content/docs/ai/skills-reference.mdx');

/**
* The population this script READS, declared for `scripts/pm/dispatch-gates.mjs`
* — spelled as a LITERAL array because the hint extractor reads source TEXT (a
* declaration computed from `SKILLS_DIR` would build no hint at all;
* `scripts/check-watch-hint-literal.mjs` holds that spelling for every declarer
* in the tree).
*
* ## What was declared before, and why it under-matched
*
* The only path literal this module body spelled was the GUIDE path,
* `content/docs/ai/skills-reference.mdx` — an OUTPUT. So `check:skill-docs`
* declared its generated artifact and not one of its inputs: a card editing a
* skill entrypoint — the file whose frontmatter is the entire catalog — was
* never told that this gate reads it, and `--check` reds in CI on drift the
* derivation could have predicted.
*
* The inputs were not forgotten, they were unspellable AS ONE HINT: `SKILLS_DIR`
* is built with `path.resolve(REPO_ROOT, 'skills')`, and a single-segment
* literal is refused by the extractor as too generic to be a path population.
*
* ## Why NOT the subtree spelling, which is the idiom's usual escape
*
* `scripts/pm/bare-root-worklist.mjs` carries the measured triage for exactly
* this root and refuses the wholesale hint: this script's population is 12 of
* the 50 files tracked under it (24%), so `skills/**` would name this gate for
* 38 files it never opens. That is the REFUSE-WIDE trade, and the worklist
* prices a false wholesale hint as the costlier error.
*
* What the recorded refusal turned on is that no SINGLE spelling reaches both
* halves: `:221` readdirs the root and admits a child only if it carries a
* SKILL.md, while the twelfth file is the root README this script WRITES, which
* sits outside every skill directory. Two literals reach both, and reach
* nothing else — 12 of 12, precise AND complete — which is the declaration
* below and why the row's verdict is withdrawn rather than the declaration.
*/
const DECLARED_WATCH_HINTS = ['skills/*/SKILL.md', 'skills/README.md'];

/**
* The declaration above, held against the constants this script really reads
* from. A hand-written path that agreed with the read only on the day it was
* typed is the drift this idiom replaces, so the check derives what it compares
* against from `SKILLS_DIR` and `README` — move either and this throws here, in
* this file, instead of going quiet in a dispatch brief.
*/
function assertWatchHintsDeclareTheReadSurface(): void {
const rel = (abs: string) => path.relative(REPO_ROOT, abs).split(path.sep).join('/');
const expected = [`${rel(SKILLS_DIR)}/*/${ENTRYPOINT_FILE}`, rel(README)];
if (DECLARED_WATCH_HINTS.join('|') !== expected.join('|')) {
throw new Error(
`build-skill-docs: the declared watch-hint population ${JSON.stringify(DECLARED_WATCH_HINTS)} no longer ` +
`names what this script reads (${JSON.stringify(expected)}) — update the declaration, as a LITERAL array.`,
);
}
// …and it stays NARROW. A hint that reached the bare root would be the
// wholesale claim the worklist refused, arriving through a reword.
if (DECLARED_WATCH_HINTS.some((h) => h.endsWith('/**'))) {
throw new Error(
`build-skill-docs: the declaration must stay narrower than the root: ${JSON.stringify(DECLARED_WATCH_HINTS)}`,
);
}
}

// Marker comments delimit the generated region. MDX does not support HTML
// comments (`<!-- -->`) — it needs `{/* */}` — so the syntax is per file type.
type CommentStyle = 'html' | 'mdx';
Expand DownExpand Up@@ -78,7 +142,7 @@ interface Skill {
}

function parseFrontmatter(name: string, label: string): Skill {
const file = path.resolve(SKILLS_DIR, name, 'SKILL.md');
const file = path.resolve(SKILLS_DIR, name, ENTRYPOINT_FILE);
const raw = fs.readFileSync(file, 'utf-8');
const parts = raw.split(/^---\s*$/m);
if (parts.length < 3) throw new Error(`${name}: no YAML frontmatter`);
Expand DownExpand Up@@ -216,10 +280,12 @@ function spliceBlock(file: string, block: string, style: CommentStyle): string {
function main() {
const check = process.argv.includes('--check');

assertWatchHintsDeclareTheReadSurface();

// Catalog ⇄ DISPLAY must be in lockstep.
const onDisk = fs
.readdirSync(SKILLS_DIR)
.filter((d) => d.startsWith('objectstack-') && fs.existsSync(path.resolve(SKILLS_DIR, d, 'SKILL.md')));
.filter((d) => d.startsWith('objectstack-') && fs.existsSync(path.resolve(SKILLS_DIR, d, ENTRYPOINT_FILE)));
const configured = new Set(DISPLAY.map((d) => d.name));
const missing = onDisk.filter((d) => !configured.has(d));
const extra = DISPLAY.filter((d) => !onDisk.includes(d.name)).map((d) => d.name);
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .github/workflows/lint.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -688,6 +688,24 @@ jobs:
- name: ROOT_DIR_WATCH_HINTS declarations are literals
run: pnpm check:watch-hint-literal

# The other half of the same failure (#13519). The step above holds that a
# declaration is still READABLE BY A TEXT SCANNER; this one holds that
# what it reads still names something. A family whose declared literals
# all reach zero tracked files prints as an ORDINARY SILENCE — byte for
# byte the output of a gate that declared nothing — so the derivation and
# the gate disagree about whether a population was declared and neither
# says so. Measured on the tree this landed against: two families, one of
# them a gate that sweeps every workflow file and appeared on no card that
# edited one. The rule is per-FAMILY, not per-literal: a gate may spell a
# slug or a sentinel path beside a live declaration, and only a whole
# declaration that reaches nothing is refused. No allowlist and no gate
# names — the stronger rule ("a gate that enumerates a directory must
# declare one") was implemented, measured at 86 findings over 114
# enumerating gate files, and refused as an allowlist with a verdict
# attached. Reads the derivation once over the tracked corpus; ~5s.
- name: A declared gate population reaches the tree
run: pnpm check:declared-population-live

# PM bare-root worklist self-test (#10840). The step above proves the
# dispatch derivation still WORKS; this one proves the recorded triage of
# the gates that derivation structurally cannot see is still true of the
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -70,6 +70,7 @@
"check:pm-label-desc-cap": "node scripts/pm/check-label-desc-cap.mjs --self-test && node scripts/pm/check-label-desc-cap.mjs",
"check:pm-dispatch-gates": "node scripts/pm/check-dispatch-gates.mjs",
"check:watch-hint-literal": "node scripts/check-watch-hint-literal.mjs --self-test && node scripts/check-watch-hint-literal.mjs",
"check:declared-population-live": "node scripts/check-declared-population-live.mjs --self-test && node scripts/check-declared-population-live.mjs",
"check:pm-half-states": "node scripts/pm/check-half-states.mjs --self-test",
"check:pm-clause2-carriers": "node scripts/pm/check-clause2-carriers.mjs --self-test",
"check:pm-governed-merges": "node scripts/pm/check-governed-merges.mjs --self-test",
Expand Down
70 changes: 68 additions & 2 deletions packages/spec/scripts/build-skill-docs.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,9 +26,73 @@ import path from 'path';

const REPO_ROOT = path.resolve(__dirname, '../../..');
const SKILLS_DIR = path.resolve(REPO_ROOT, 'skills');
/** The file a child of the skills root must CARRY to be a skill (`:main` admits on it). */
const ENTRYPOINT_FILE = 'SKILL.md';
const README = path.resolve(SKILLS_DIR, 'README.md');
const GUIDE = path.resolve(REPO_ROOT, 'content/docs/ai/skills-reference.mdx');

/**
* The population this script READS, declared for `scripts/pm/dispatch-gates.mjs`
* — spelled as a LITERAL array because the hint extractor reads source TEXT (a
* declaration computed from `SKILLS_DIR` would build no hint at all;
* `scripts/check-watch-hint-literal.mjs` holds that spelling for every declarer
* in the tree).
*
* ## What was declared before, and why it under-matched
*
* The only path literal this module body spelled was the GUIDE path,
* `content/docs/ai/skills-reference.mdx` — an OUTPUT. So `check:skill-docs`
* declared its generated artifact and not one of its inputs: a card editing a
* skill entrypoint — the file whose frontmatter is the entire catalog — was
* never told that this gate reads it, and `--check` reds in CI on drift the
* derivation could have predicted.
*
* The inputs were not forgotten, they were unspellable AS ONE HINT: `SKILLS_DIR`
* is built with `path.resolve(REPO_ROOT, 'skills')`, and a single-segment
* literal is refused by the extractor as too generic to be a path population.
*
* ## Why NOT the subtree spelling, which is the idiom's usual escape
*
* `scripts/pm/bare-root-worklist.mjs` carries the measured triage for exactly
* this root and refuses the wholesale hint: this script's population is 12 of
* the 50 files tracked under it (24%), so `skills/**` would name this gate for
* 38 files it never opens. That is the REFUSE-WIDE trade, and the worklist
* prices a false wholesale hint as the costlier error.
*
* What the recorded refusal turned on is that no SINGLE spelling reaches both
* halves: `:221` readdirs the root and admits a child only if it carries a
* SKILL.md, while the twelfth file is the root README this script WRITES, which
* sits outside every skill directory. Two literals reach both, and reach
* nothing else — 12 of 12, precise AND complete — which is the declaration
* below and why the row's verdict is withdrawn rather than the declaration.
*/
const DECLARED_WATCH_HINTS = ['skills/*/SKILL.md', 'skills/README.md'];

/**
* The declaration above, held against the constants this script really reads
* from. A hand-written path that agreed with the read only on the day it was
* typed is the drift this idiom replaces, so the check derives what it compares
* against from `SKILLS_DIR` and `README` — move either and this throws here, in
* this file, instead of going quiet in a dispatch brief.
*/
function assertWatchHintsDeclareTheReadSurface(): void {
const rel = (abs: string) => path.relative(REPO_ROOT, abs).split(path.sep).join('/');
const expected = [`${rel(SKILLS_DIR)}/*/${ENTRYPOINT_FILE}`, rel(README)];
if (DECLARED_WATCH_HINTS.join('|') !== expected.join('|')) {
throw new Error(
`build-skill-docs: the declared watch-hint population ${JSON.stringify(DECLARED_WATCH_HINTS)} no longer ` +
`names what this script reads (${JSON.stringify(expected)}) — update the declaration, as a LITERAL array.`,
);
}
// …and it stays NARROW. A hint that reached the bare root would be the
// wholesale claim the worklist refused, arriving through a reword.
if (DECLARED_WATCH_HINTS.some((h) => h.endsWith('/**'))) {
throw new Error(
`build-skill-docs: the declaration must stay narrower than the root: ${JSON.stringify(DECLARED_WATCH_HINTS)}`,
);
}
}

// Marker comments delimit the generated region. MDX does not support HTML
// comments (`<!-- -->`) — it needs `{/* */}` — so the syntax is per file type.
type CommentStyle = 'html' | 'mdx';
Expand DownExpand Up@@ -78,7 +142,7 @@ interface Skill {
}

function parseFrontmatter(name: string, label: string): Skill {
const file = path.resolve(SKILLS_DIR, name, 'SKILL.md');
const file = path.resolve(SKILLS_DIR, name, ENTRYPOINT_FILE);
const raw = fs.readFileSync(file, 'utf-8');
const parts = raw.split(/^---\s*$/m);
if (parts.length < 3) throw new Error(`${name}: no YAML frontmatter`);
Expand DownExpand Up@@ -216,10 +280,12 @@ function spliceBlock(file: string, block: string, style: CommentStyle): string {
function main() {
const check = process.argv.includes('--check');

assertWatchHintsDeclareTheReadSurface();

// Catalog ⇄ DISPLAY must be in lockstep.
const onDisk = fs
.readdirSync(SKILLS_DIR)
.filter((d) => d.startsWith('objectstack-') && fs.existsSync(path.resolve(SKILLS_DIR, d, 'SKILL.md')));
.filter((d) => d.startsWith('objectstack-') && fs.existsSync(path.resolve(SKILLS_DIR, d, ENTRYPOINT_FILE)));
const configured = new Set(DISPLAY.map((d) => d.name));
const missing = onDisk.filter((d) => !configured.has(d));
const extra = DISPLAY.filter((d) => !onDisk.includes(d.name)).map((d) => d.name);
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .github/workflows/lint.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -688,6 +688,24 @@ jobs:
- name: ROOT_DIR_WATCH_HINTS declarations are literals
run: pnpm check:watch-hint-literal

# The other half of the same failure (#13519). The step above holds that a
# declaration is still READABLE BY A TEXT SCANNER; this one holds that
# what it reads still names something. A family whose declared literals
# all reach zero tracked files prints as an ORDINARY SILENCE — byte for
# byte the output of a gate that declared nothing — so the derivation and
# the gate disagree about whether a population was declared and neither
# says so. Measured on the tree this landed against: two families, one of
# them a gate that sweeps every workflow file and appeared on no card that
# edited one. The rule is per-FAMILY, not per-literal: a gate may spell a
# slug or a sentinel path beside a live declaration, and only a whole
# declaration that reaches nothing is refused. No allowlist and no gate
# names — the stronger rule ("a gate that enumerates a directory must
# declare one") was implemented, measured at 86 findings over 114
# enumerating gate files, and refused as an allowlist with a verdict
# attached. Reads the derivation once over the tracked corpus; ~5s.
- name: A declared gate population reaches the tree
run: pnpm check:declared-population-live

# PM bare-root worklist self-test (#10840). The step above proves the
# dispatch derivation still WORKS; this one proves the recorded triage of
# the gates that derivation structurally cannot see is still true of the
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -70,6 +70,7 @@
"check:pm-label-desc-cap": "node scripts/pm/check-label-desc-cap.mjs --self-test && node scripts/pm/check-label-desc-cap.mjs",
"check:pm-dispatch-gates": "node scripts/pm/check-dispatch-gates.mjs",
"check:watch-hint-literal": "node scripts/check-watch-hint-literal.mjs --self-test && node scripts/check-watch-hint-literal.mjs",
"check:declared-population-live": "node scripts/check-declared-population-live.mjs --self-test && node scripts/check-declared-population-live.mjs",
"check:pm-half-states": "node scripts/pm/check-half-states.mjs --self-test",
"check:pm-clause2-carriers": "node scripts/pm/check-clause2-carriers.mjs --self-test",
"check:pm-governed-merges": "node scripts/pm/check-governed-merges.mjs --self-test",
Expand Down
70 changes: 68 additions & 2 deletions packages/spec/scripts/build-skill-docs.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,9 +26,73 @@ import path from 'path';

const REPO_ROOT = path.resolve(__dirname, '../../..');
const SKILLS_DIR = path.resolve(REPO_ROOT, 'skills');
/** The file a child of the skills root must CARRY to be a skill (`:main` admits on it). */
const ENTRYPOINT_FILE = 'SKILL.md';
const README = path.resolve(SKILLS_DIR, 'README.md');
const GUIDE = path.resolve(REPO_ROOT, 'content/docs/ai/skills-reference.mdx');

/**
* The population this script READS, declared for `scripts/pm/dispatch-gates.mjs`
* — spelled as a LITERAL array because the hint extractor reads source TEXT (a
* declaration computed from `SKILLS_DIR` would build no hint at all;
* `scripts/check-watch-hint-literal.mjs` holds that spelling for every declarer
* in the tree).
*
* ## What was declared before, and why it under-matched
*
* The only path literal this module body spelled was the GUIDE path,
* `content/docs/ai/skills-reference.mdx` — an OUTPUT. So `check:skill-docs`
* declared its generated artifact and not one of its inputs: a card editing a
* skill entrypoint — the file whose frontmatter is the entire catalog — was
* never told that this gate reads it, and `--check` reds in CI on drift the
* derivation could have predicted.
*
* The inputs were not forgotten, they were unspellable AS ONE HINT: `SKILLS_DIR`
* is built with `path.resolve(REPO_ROOT, 'skills')`, and a single-segment
* literal is refused by the extractor as too generic to be a path population.
*
* ## Why NOT the subtree spelling, which is the idiom's usual escape
*
* `scripts/pm/bare-root-worklist.mjs` carries the measured triage for exactly
* this root and refuses the wholesale hint: this script's population is 12 of
* the 50 files tracked under it (24%), so `skills/**` would name this gate for
* 38 files it never opens. That is the REFUSE-WIDE trade, and the worklist
* prices a false wholesale hint as the costlier error.
*
* What the recorded refusal turned on is that no SINGLE spelling reaches both
* halves: `:221` readdirs the root and admits a child only if it carries a
* SKILL.md, while the twelfth file is the root README this script WRITES, which
* sits outside every skill directory. Two literals reach both, and reach
* nothing else — 12 of 12, precise AND complete — which is the declaration
* below and why the row's verdict is withdrawn rather than the declaration.
*/
const DECLARED_WATCH_HINTS = ['skills/*/SKILL.md', 'skills/README.md'];

/**
* The declaration above, held against the constants this script really reads
* from. A hand-written path that agreed with the read only on the day it was
* typed is the drift this idiom replaces, so the check derives what it compares
* against from `SKILLS_DIR` and `README` — move either and this throws here, in
* this file, instead of going quiet in a dispatch brief.
*/
function assertWatchHintsDeclareTheReadSurface(): void {
const rel = (abs: string) => path.relative(REPO_ROOT, abs).split(path.sep).join('/');
const expected = [`${rel(SKILLS_DIR)}/*/${ENTRYPOINT_FILE}`, rel(README)];
if (DECLARED_WATCH_HINTS.join('|') !== expected.join('|')) {
throw new Error(
`build-skill-docs: the declared watch-hint population ${JSON.stringify(DECLARED_WATCH_HINTS)} no longer ` +
`names what this script reads (${JSON.stringify(expected)}) — update the declaration, as a LITERAL array.`,
);
}
// …and it stays NARROW. A hint that reached the bare root would be the
// wholesale claim the worklist refused, arriving through a reword.
if (DECLARED_WATCH_HINTS.some((h) => h.endsWith('/**'))) {
throw new Error(
`build-skill-docs: the declaration must stay narrower than the root: ${JSON.stringify(DECLARED_WATCH_HINTS)}`,
);
}
}

// Marker comments delimit the generated region. MDX does not support HTML
// comments (`<!-- -->`) — it needs `{/* */}` — so the syntax is per file type.
type CommentStyle = 'html' | 'mdx';
Expand DownExpand Up@@ -78,7 +142,7 @@ interface Skill {
}

function parseFrontmatter(name: string, label: string): Skill {
const file = path.resolve(SKILLS_DIR, name, 'SKILL.md');
const file = path.resolve(SKILLS_DIR, name, ENTRYPOINT_FILE);
const raw = fs.readFileSync(file, 'utf-8');
const parts = raw.split(/^---\s*$/m);
if (parts.length < 3) throw new Error(`${name}: no YAML frontmatter`);
Expand DownExpand Up@@ -216,10 +280,12 @@ function spliceBlock(file: string, block: string, style: CommentStyle): string {
function main() {
const check = process.argv.includes('--check');

assertWatchHintsDeclareTheReadSurface();

// Catalog ⇄ DISPLAY must be in lockstep.
const onDisk = fs
.readdirSync(SKILLS_DIR)
.filter((d) => d.startsWith('objectstack-') && fs.existsSync(path.resolve(SKILLS_DIR, d, 'SKILL.md')));
.filter((d) => d.startsWith('objectstack-') && fs.existsSync(path.resolve(SKILLS_DIR, d, ENTRYPOINT_FILE)));
const configured = new Set(DISPLAY.map((d) => d.name));
const missing = onDisk.filter((d) => !configured.has(d));
const extra = DISPLAY.filter((d) => !onDisk.includes(d.name)).map((d) => d.name);
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .github/workflows/lint.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -688,6 +688,24 @@ jobs:
- name: ROOT_DIR_WATCH_HINTS declarations are literals
run: pnpm check:watch-hint-literal

# The other half of the same failure (#13519). The step above holds that a
# declaration is still READABLE BY A TEXT SCANNER; this one holds that
# what it reads still names something. A family whose declared literals
# all reach zero tracked files prints as an ORDINARY SILENCE — byte for
# byte the output of a gate that declared nothing — so the derivation and
# the gate disagree about whether a population was declared and neither
# says so. Measured on the tree this landed against: two families, one of
# them a gate that sweeps every workflow file and appeared on no card that
# edited one. The rule is per-FAMILY, not per-literal: a gate may spell a
# slug or a sentinel path beside a live declaration, and only a whole
# declaration that reaches nothing is refused. No allowlist and no gate
# names — the stronger rule ("a gate that enumerates a directory must
# declare one") was implemented, measured at 86 findings over 114
# enumerating gate files, and refused as an allowlist with a verdict
# attached. Reads the derivation once over the tracked corpus; ~5s.
- name: A declared gate population reaches the tree
run: pnpm check:declared-population-live

# PM bare-root worklist self-test (#10840). The step above proves the
# dispatch derivation still WORKS; this one proves the recorded triage of
# the gates that derivation structurally cannot see is still true of the
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -70,6 +70,7 @@
"check:pm-label-desc-cap": "node scripts/pm/check-label-desc-cap.mjs --self-test && node scripts/pm/check-label-desc-cap.mjs",
"check:pm-dispatch-gates": "node scripts/pm/check-dispatch-gates.mjs",
"check:watch-hint-literal": "node scripts/check-watch-hint-literal.mjs --self-test && node scripts/check-watch-hint-literal.mjs",
"check:declared-population-live": "node scripts/check-declared-population-live.mjs --self-test && node scripts/check-declared-population-live.mjs",
"check:pm-half-states": "node scripts/pm/check-half-states.mjs --self-test",
"check:pm-clause2-carriers": "node scripts/pm/check-clause2-carriers.mjs --self-test",
"check:pm-governed-merges": "node scripts/pm/check-governed-merges.mjs --self-test",
Expand Down
70 changes: 68 additions & 2 deletions packages/spec/scripts/build-skill-docs.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,9 +26,73 @@ import path from 'path';

const REPO_ROOT = path.resolve(__dirname, '../../..');
const SKILLS_DIR = path.resolve(REPO_ROOT, 'skills');
/** The file a child of the skills root must CARRY to be a skill (`:main` admits on it). */
const ENTRYPOINT_FILE = 'SKILL.md';
const README = path.resolve(SKILLS_DIR, 'README.md');
const GUIDE = path.resolve(REPO_ROOT, 'content/docs/ai/skills-reference.mdx');

/**
* The population this script READS, declared for `scripts/pm/dispatch-gates.mjs`
* — spelled as a LITERAL array because the hint extractor reads source TEXT (a
* declaration computed from `SKILLS_DIR` would build no hint at all;
* `scripts/check-watch-hint-literal.mjs` holds that spelling for every declarer
* in the tree).
*
* ## What was declared before, and why it under-matched
*
* The only path literal this module body spelled was the GUIDE path,
* `content/docs/ai/skills-reference.mdx` — an OUTPUT. So `check:skill-docs`
* declared its generated artifact and not one of its inputs: a card editing a
* skill entrypoint — the file whose frontmatter is the entire catalog — was
* never told that this gate reads it, and `--check` reds in CI on drift the
* derivation could have predicted.
*
* The inputs were not forgotten, they were unspellable AS ONE HINT: `SKILLS_DIR`
* is built with `path.resolve(REPO_ROOT, 'skills')`, and a single-segment
* literal is refused by the extractor as too generic to be a path population.
*
* ## Why NOT the subtree spelling, which is the idiom's usual escape
*
* `scripts/pm/bare-root-worklist.mjs` carries the measured triage for exactly
* this root and refuses the wholesale hint: this script's population is 12 of
* the 50 files tracked under it (24%), so `skills/**` would name this gate for
* 38 files it never opens. That is the REFUSE-WIDE trade, and the worklist
* prices a false wholesale hint as the costlier error.
*
* What the recorded refusal turned on is that no SINGLE spelling reaches both
* halves: `:221` readdirs the root and admits a child only if it carries a
* SKILL.md, while the twelfth file is the root README this script WRITES, which
* sits outside every skill directory. Two literals reach both, and reach
* nothing else — 12 of 12, precise AND complete — which is the declaration
* below and why the row's verdict is withdrawn rather than the declaration.
*/
const DECLARED_WATCH_HINTS = ['skills/*/SKILL.md', 'skills/README.md'];

/**
* The declaration above, held against the constants this script really reads
* from. A hand-written path that agreed with the read only on the day it was
* typed is the drift this idiom replaces, so the check derives what it compares
* against from `SKILLS_DIR` and `README` — move either and this throws here, in
* this file, instead of going quiet in a dispatch brief.
*/
function assertWatchHintsDeclareTheReadSurface(): void {
const rel = (abs: string) => path.relative(REPO_ROOT, abs).split(path.sep).join('/');
const expected = [`${rel(SKILLS_DIR)}/*/${ENTRYPOINT_FILE}`, rel(README)];
if (DECLARED_WATCH_HINTS.join('|') !== expected.join('|')) {
throw new Error(
`build-skill-docs: the declared watch-hint population ${JSON.stringify(DECLARED_WATCH_HINTS)} no longer ` +
`names what this script reads (${JSON.stringify(expected)}) — update the declaration, as a LITERAL array.`,
);
}
// …and it stays NARROW. A hint that reached the bare root would be the
// wholesale claim the worklist refused, arriving through a reword.
if (DECLARED_WATCH_HINTS.some((h) => h.endsWith('/**'))) {
throw new Error(
`build-skill-docs: the declaration must stay narrower than the root: ${JSON.stringify(DECLARED_WATCH_HINTS)}`,
);
}
}

// Marker comments delimit the generated region. MDX does not support HTML
// comments (`<!-- -->`) — it needs `{/* */}` — so the syntax is per file type.
type CommentStyle = 'html' | 'mdx';
Expand DownExpand Up@@ -78,7 +142,7 @@ interface Skill {
}

function parseFrontmatter(name: string, label: string): Skill {
const file = path.resolve(SKILLS_DIR, name, 'SKILL.md');
const file = path.resolve(SKILLS_DIR, name, ENTRYPOINT_FILE);
const raw = fs.readFileSync(file, 'utf-8');
const parts = raw.split(/^---\s*$/m);
if (parts.length < 3) throw new Error(`${name}: no YAML frontmatter`);
Expand DownExpand Up@@ -216,10 +280,12 @@ function spliceBlock(file: string, block: string, style: CommentStyle): string {
function main() {
const check = process.argv.includes('--check');

assertWatchHintsDeclareTheReadSurface();

// Catalog ⇄ DISPLAY must be in lockstep.
const onDisk = fs
.readdirSync(SKILLS_DIR)
.filter((d) => d.startsWith('objectstack-') && fs.existsSync(path.resolve(SKILLS_DIR, d, 'SKILL.md')));
.filter((d) => d.startsWith('objectstack-') && fs.existsSync(path.resolve(SKILLS_DIR, d, ENTRYPOINT_FILE)));
const configured = new Set(DISPLAY.map((d) => d.name));
const missing = onDisk.filter((d) => !configured.has(d));
const extra = DISPLAY.filter((d) => !onDisk.includes(d.name)).map((d) => d.name);
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .github/workflows/lint.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -688,6 +688,24 @@ jobs:
- name: ROOT_DIR_WATCH_HINTS declarations are literals
run: pnpm check:watch-hint-literal

# The other half of the same failure (#13519). The step above holds that a
# declaration is still READABLE BY A TEXT SCANNER; this one holds that
# what it reads still names something. A family whose declared literals
# all reach zero tracked files prints as an ORDINARY SILENCE — byte for
# byte the output of a gate that declared nothing — so the derivation and
# the gate disagree about whether a population was declared and neither
# says so. Measured on the tree this landed against: two families, one of
# them a gate that sweeps every workflow file and appeared on no card that
# edited one. The rule is per-FAMILY, not per-literal: a gate may spell a
# slug or a sentinel path beside a live declaration, and only a whole
# declaration that reaches nothing is refused. No allowlist and no gate
# names — the stronger rule ("a gate that enumerates a directory must
# declare one") was implemented, measured at 86 findings over 114
# enumerating gate files, and refused as an allowlist with a verdict
# attached. Reads the derivation once over the tracked corpus; ~5s.
- name: A declared gate population reaches the tree
run: pnpm check:declared-population-live

# PM bare-root worklist self-test (#10840). The step above proves the
# dispatch derivation still WORKS; this one proves the recorded triage of
# the gates that derivation structurally cannot see is still true of the
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -70,6 +70,7 @@
"check:pm-label-desc-cap": "node scripts/pm/check-label-desc-cap.mjs --self-test && node scripts/pm/check-label-desc-cap.mjs",
"check:pm-dispatch-gates": "node scripts/pm/check-dispatch-gates.mjs",
"check:watch-hint-literal": "node scripts/check-watch-hint-literal.mjs --self-test && node scripts/check-watch-hint-literal.mjs",
"check:declared-population-live": "node scripts/check-declared-population-live.mjs --self-test && node scripts/check-declared-population-live.mjs",
"check:pm-half-states": "node scripts/pm/check-half-states.mjs --self-test",
"check:pm-clause2-carriers": "node scripts/pm/check-clause2-carriers.mjs --self-test",
"check:pm-governed-merges": "node scripts/pm/check-governed-merges.mjs --self-test",
Expand Down
70 changes: 68 additions & 2 deletions packages/spec/scripts/build-skill-docs.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,9 +26,73 @@ import path from 'path';

const REPO_ROOT = path.resolve(__dirname, '../../..');
const SKILLS_DIR = path.resolve(REPO_ROOT, 'skills');
/** The file a child of the skills root must CARRY to be a skill (`:main` admits on it). */
const ENTRYPOINT_FILE = 'SKILL.md';
const README = path.resolve(SKILLS_DIR, 'README.md');
const GUIDE = path.resolve(REPO_ROOT, 'content/docs/ai/skills-reference.mdx');

/**
* The population this script READS, declared for `scripts/pm/dispatch-gates.mjs`
* — spelled as a LITERAL array because the hint extractor reads source TEXT (a
* declaration computed from `SKILLS_DIR` would build no hint at all;
* `scripts/check-watch-hint-literal.mjs` holds that spelling for every declarer
* in the tree).
*
* ## What was declared before, and why it under-matched
*
* The only path literal this module body spelled was the GUIDE path,
* `content/docs/ai/skills-reference.mdx` — an OUTPUT. So `check:skill-docs`
* declared its generated artifact and not one of its inputs: a card editing a
* skill entrypoint — the file whose frontmatter is the entire catalog — was
* never told that this gate reads it, and `--check` reds in CI on drift the
* derivation could have predicted.
*
* The inputs were not forgotten, they were unspellable AS ONE HINT: `SKILLS_DIR`
* is built with `path.resolve(REPO_ROOT, 'skills')`, and a single-segment
* literal is refused by the extractor as too generic to be a path population.
*
* ## Why NOT the subtree spelling, which is the idiom's usual escape
*
* `scripts/pm/bare-root-worklist.mjs` carries the measured triage for exactly
* this root and refuses the wholesale hint: this script's population is 12 of
* the 50 files tracked under it (24%), so `skills/**` would name this gate for
* 38 files it never opens. That is the REFUSE-WIDE trade, and the worklist
* prices a false wholesale hint as the costlier error.
*
* What the recorded refusal turned on is that no SINGLE spelling reaches both
* halves: `:221` readdirs the root and admits a child only if it carries a
* SKILL.md, while the twelfth file is the root README this script WRITES, which
* sits outside every skill directory. Two literals reach both, and reach
* nothing else — 12 of 12, precise AND complete — which is the declaration
* below and why the row's verdict is withdrawn rather than the declaration.
*/
const DECLARED_WATCH_HINTS = ['skills/*/SKILL.md', 'skills/README.md'];

/**
* The declaration above, held against the constants this script really reads
* from. A hand-written path that agreed with the read only on the day it was
* typed is the drift this idiom replaces, so the check derives what it compares
* against from `SKILLS_DIR` and `README` — move either and this throws here, in
* this file, instead of going quiet in a dispatch brief.
*/
function assertWatchHintsDeclareTheReadSurface(): void {
const rel = (abs: string) => path.relative(REPO_ROOT, abs).split(path.sep).join('/');
const expected = [`${rel(SKILLS_DIR)}/*/${ENTRYPOINT_FILE}`, rel(README)];
if (DECLARED_WATCH_HINTS.join('|') !== expected.join('|')) {
throw new Error(
`build-skill-docs: the declared watch-hint population ${JSON.stringify(DECLARED_WATCH_HINTS)} no longer ` +
`names what this script reads (${JSON.stringify(expected)}) — update the declaration, as a LITERAL array.`,
);
}
// …and it stays NARROW. A hint that reached the bare root would be the
// wholesale claim the worklist refused, arriving through a reword.
if (DECLARED_WATCH_HINTS.some((h) => h.endsWith('/**'))) {
throw new Error(
`build-skill-docs: the declaration must stay narrower than the root: ${JSON.stringify(DECLARED_WATCH_HINTS)}`,
);
}
}

// Marker comments delimit the generated region. MDX does not support HTML
// comments (`<!-- -->`) — it needs `{/* */}` — so the syntax is per file type.
type CommentStyle = 'html' | 'mdx';
Expand DownExpand Up@@ -78,7 +142,7 @@ interface Skill {
}

function parseFrontmatter(name: string, label: string): Skill {
const file = path.resolve(SKILLS_DIR, name, 'SKILL.md');
const file = path.resolve(SKILLS_DIR, name, ENTRYPOINT_FILE);
const raw = fs.readFileSync(file, 'utf-8');
const parts = raw.split(/^---\s*$/m);
if (parts.length < 3) throw new Error(`${name}: no YAML frontmatter`);
Expand DownExpand Up@@ -216,10 +280,12 @@ function spliceBlock(file: string, block: string, style: CommentStyle): string {
function main() {
const check = process.argv.includes('--check');

assertWatchHintsDeclareTheReadSurface();

// Catalog ⇄ DISPLAY must be in lockstep.
const onDisk = fs
.readdirSync(SKILLS_DIR)
.filter((d) => d.startsWith('objectstack-') && fs.existsSync(path.resolve(SKILLS_DIR, d, 'SKILL.md')));
.filter((d) => d.startsWith('objectstack-') && fs.existsSync(path.resolve(SKILLS_DIR, d, ENTRYPOINT_FILE)));
const configured = new Set(DISPLAY.map((d) => d.name));
const missing = onDisk.filter((d) => !configured.has(d));
const extra = DISPLAY.filter((d) => !onDisk.includes(d.name)).map((d) => d.name);
Expand Down
Loading
Loading