## 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.
122 lines
4.8 KiB
JavaScript
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\]/);
|
|
});
|