Files
fusion/scripts/__tests__/plugin-authoring-docs.test.mjs
gsxdsm f411d55591 test(docs): the PLUGIN_AUTHORING TOC guard rejected legal nested entries, and has been red on main (#3036)
## A legal Markdown sub-entry turned this guard red

```
AssertionError: Invalid TOC line: - [Theming & Overlay Layering for Dashboard Views](#theming--overlay-layering-for-dashboard-views)
```

That line is an ordinary nested TOC entry, indented under item 8 of
`docs/PLUGIN_AUTHORING.md`. The parser did `.map(line => line.trim())`
**first** and then required every line to match the top-level `N.
[title](#anchor)` shape — so indentation, the one thing distinguishing a
sub-entry from a malformed top-level one, was destroyed before it could
be used.

**The doc was never wrong.** Only the parser was, and it has been red on
`main` since the entry was added.

Indentation is now read before trimming. Sub-entries are still required
to be well-formed links; they just do not participate in the numbering
or the count.

## Both guard directions verified by breaking them

A looser parser that skipped anything unrecognised would have made the
failure go away while quietly ending the guard's usefulness — so I
checked it still fails in both directions:

| mutation | result |
| --- | --- |
| top-level `9.` rewritten as a bullet | still fails (`Invalid TOC
line`) |
| nested entry replaced with un-linked prose | still fails (`Invalid
nested TOC line`) |

## The wider finding, which matters more than this fix

I found it by sweeping `scripts/__tests__` against clean `main`: **688
passing, 7 failing test files.**

| suite | failing assertion |
| --- | --- |
| `ci-test-shard-timings` | committed timing snapshot references live
test files |
| `dependency-security-floor` | pnpm overrides pin transitive protobufjs
to a safe floor |
| `engine-vitest-gate-policy` | pg gate canaries remain a subset of the
enabled suite |
| `plugin-authoring-docs` | **this PR** |
| `release-prompt-gate` | release dry-run exits before proceed
confirmation |
| `verify-fast` | defaults to every canonical pretest validator |
| `workflow-reliability-release-check` | manifest references existing
seam files |

All sit **outside the merge gate**. That is now the third instance of
this pattern I have hit — #2969's 15 red agent-action tests and #3033's
stale ratchet list were the others — and it is clearly systemic rather
than incidental.

I fixed only the one in plugin territory. The rest span CI sharding,
**dependency security** (that protobufjs floor is a security assertion
currently not holding), release gating and workflow manifests. Each
needs its owner's judgement about whether the assertion or the world is
wrong, and a drive-by "make it green" is exactly how a real signal gets
erased — `dependency-security-floor` especially.

## Verification (measured)

- this suite — **4 passed / 0 failed** (was 1 failed)
- `eslint` — clean

Test-only; the doc is untouched. No changeset.
2026-07-31 01:52:13 -07:00

122 lines
4.8 KiB
JavaScript

import test from "node:test";
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const docPath = path.resolve(__dirname, "../../docs/PLUGIN_AUTHORING.md");
const doc = readFileSync(docPath, "utf8");
const expectedSections = [
"Getting Started",
"Plugin Manifest Reference",
"Plugin Settings Schema",
"Available Hooks and Signatures",
"Registering Tools",
"Registering Routes",
"Registering UI Slots",
"Registering Top-Level Dashboard Views",
"Registering Agent Runtimes",
"Plugin Context API Reference",
"Plugin Lifecycle States",
"Testing Plugins",
"Publishing Plugins",
"Example Plugins",
"Registering Skills",
"Registering Workflow Steps",
"Contributing Prompt Modifications",
"Plugin Binary Setup Hooks",
];
function slugifyHeading(text) {
return text
.trim()
.toLowerCase()
.replace(/[^a-z0-9\s-]/g, "")
.replace(/\s+/g, "-");
}
test("PLUGIN_AUTHORING headings are sequentially numbered 1..18 with expected titles", () => {
const headingMatches = [...doc.matchAll(/^##\s+(\d+)\.\s+(.+)$/gm)];
const numbered = headingMatches.map((m) => ({ number: Number(m[1]), title: m[2].trim() }));
assert.equal(numbered.length, expectedSections.length);
for (let i = 0; i < expectedSections.length; i += 1) {
assert.equal(numbered[i].number, i + 1);
assert.equal(numbered[i].title, expectedSections[i]);
}
});
test("PLUGIN_AUTHORING TOC includes top-level dashboard views and anchors align to headings", () => {
const tocMatch = doc.match(/## Table of Contents\n\n([\s\S]*?)\n---/);
assert.ok(tocMatch, "Table of Contents block should exist");
/*
FNXC:PluginAuthoringDocs 2026-07-31-18:20:
NESTED TOC entries are legal Markdown, and this parser rejected them by flattening indentation away.
`- [Theming & Overlay Layering for Dashboard Views](...)` sits indented under item 8 — an ordinary
sub-entry. The old code trimmed every line first and then required ALL of them to match the
top-level `N. [title](#anchor)` shape, so adding a perfectly valid sub-entry turned this assertion
red on `main`, and it has been red since.
Indentation is the discriminator, so it is read BEFORE trimming. Sub-entries are still required to
be well-formed links — they are simply not top-level sections and do not participate in the
numbering or the count. A malformed TOP-LEVEL line still fails exactly as before, which is the guard
this test exists to be.
*/
const rawTocLines = tocMatch[1].split("\n").filter((line) => line.trim());
const subEntries = rawTocLines.filter((line) => /^\s+/.test(line));
for (const line of subEntries) {
assert.ok(/^\s+-\s+\[.+\]\(#.+\)$/.test(line.replace(/\s+$/, "")), `Invalid nested TOC line: ${line}`);
}
const tocLines = rawTocLines.filter((line) => !/^\s/.test(line)).map((line) => line.trim());
const tocEntries = tocLines.map((line) => {
const m = line.match(/^(\d+)\.\s+\[(.+)\]\(#(.+)\)$/);
assert.ok(m, `Invalid TOC line: ${line}`);
return {
number: Number(m[1]),
title: m[2],
anchor: m[3],
};
});
assert.equal(tocEntries.length, expectedSections.length);
for (let i = 0; i < expectedSections.length; i += 1) {
const sectionNumber = i + 1;
const expectedTitle = expectedSections[i];
const expectedAnchor = slugifyHeading(`${sectionNumber}. ${expectedTitle}`);
assert.equal(tocEntries[i].number, sectionNumber);
assert.equal(tocEntries[i].title, expectedTitle);
assert.equal(tocEntries[i].anchor, expectedAnchor);
}
const topLevelEntry = tocEntries.find((entry) => entry.number === 8);
assert.ok(topLevelEntry, "TOC should include section 8");
assert.equal(topLevelEntry.title, "Registering Top-Level Dashboard Views");
});
test("PLUGIN_AUTHORING documents executorRuntimeEnv hook signature in hook reference", () => {
assert.match(
doc,
/\| `executorRuntimeEnv` \| `\(taskCtx: ExecutorRuntimeTaskContext, ctx: PluginContext\) => Promise<ExecutorRuntimeEnvContribution> \\| ExecutorRuntimeEnvContribution` \|/,
);
});
test("PLUGIN_AUTHORING documents executorRuntimeEnv runtime env contract and PATH injection example", () => {
assert.match(doc, /### `executorRuntimeEnv`: task-scoped executor subprocess environment/);
assert.match(doc, /does \*\*not\*\* apply to internal git plumbing subprocesses/);
assert.match(doc, /pathPrepend` must be an array of absolute path strings/);
assert.match(doc, /must not include `PATH`; use `pathPrepend` instead/);
assert.match(doc, /later plugins override earlier values and the engine logs a warning/);
assert.match(doc, /later plugins are placed earlier in the final prepend list/);
assert.match(doc, /pathPrepend: \[toolDir\]/);
});