chore: clear dead test and allowlist leftovers of dropped runtimes
Some checks failed
Tests / PR mergeability (push) Successful in 18s
Tests / Base branch health (push) Successful in 9s
Tests / Detect test scope (push) Successful in 16s
Tests / lint-tests (push) Failing after 1m43s
Tests / plugin-validate (push) Successful in 58s
Tests / test (ubuntu-latest, 24, shard 1/3) (push) Failing after 19s
Tests / test (ubuntu-latest, 24, shard 2/3) (push) Failing after 20s
Tests / test (ubuntu-latest, 24, shard 3/3) (push) Failing after 20s
Tests / test (ubuntu-latest, 24) (push) Failing after 18s
Tests / test (inert CI) (push) Has been skipped
Tests / QA loop walk (smell ratchet) (push) Failing after 19s
Tests / Coverage gate (merged shards) (push) Has been skipped
Tests / Publish emitted-baseline artifact (push) Has been skipped
Duplicate auto-close sweep / sweep (push) Successful in 19s
CI timeout budget report / report (push) Failing after 14s
Close Draft PRs (sweep) / Sweep open draft PRs (push) Successful in 9s
Dismiss Unauthorized PR Approvals / dismiss-unauthorized-approval (push) Successful in 9s
Tests / conformance test (macos-latest, 24) (push) Has been cancelled
Tests / conformance test (windows-latest, 24, shard 1/3) (push) Has been cancelled
Tests / conformance test (windows-latest, 24, shard 2/3) (push) Has been cancelled
Tests / conformance test (windows-latest, 24, shard 3/3) (push) Has been cancelled
Tests / Required tests (push) Has been cancelled
Some checks failed
Tests / PR mergeability (push) Successful in 18s
Tests / Base branch health (push) Successful in 9s
Tests / Detect test scope (push) Successful in 16s
Tests / lint-tests (push) Failing after 1m43s
Tests / plugin-validate (push) Successful in 58s
Tests / test (ubuntu-latest, 24, shard 1/3) (push) Failing after 19s
Tests / test (ubuntu-latest, 24, shard 2/3) (push) Failing after 20s
Tests / test (ubuntu-latest, 24, shard 3/3) (push) Failing after 20s
Tests / test (ubuntu-latest, 24) (push) Failing after 18s
Tests / test (inert CI) (push) Has been skipped
Tests / QA loop walk (smell ratchet) (push) Failing after 19s
Tests / Coverage gate (merged shards) (push) Has been skipped
Tests / Publish emitted-baseline artifact (push) Has been skipped
Duplicate auto-close sweep / sweep (push) Successful in 19s
CI timeout budget report / report (push) Failing after 14s
Close Draft PRs (sweep) / Sweep open draft PRs (push) Successful in 9s
Dismiss Unauthorized PR Approvals / dismiss-unauthorized-approval (push) Successful in 9s
Tests / conformance test (macos-latest, 24) (push) Has been cancelled
Tests / conformance test (windows-latest, 24, shard 1/3) (push) Has been cancelled
Tests / conformance test (windows-latest, 24, shard 2/3) (push) Has been cancelled
Tests / conformance test (windows-latest, 24, shard 3/3) (push) Has been cancelled
Tests / Required tests (push) Has been cancelled
This commit is contained in:
@@ -1,462 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* emitted-caps.cjs — the per-runtime emitted-byte cap decision (issue #2931,
|
||||
* epic #1671, Phase 4). Sibling law to `emitted-diff.cjs`'s conservation law:
|
||||
* same pure/IO split, same error-accumulation shape, same reserved-key guard.
|
||||
*
|
||||
* ── Why this module is pure ──────────────────────────────────────────────────
|
||||
* No fs, no git, no clock, no process. The expensive part — spawning an
|
||||
* install and measuring real emitted bytes — lives elsewhere; this module only
|
||||
* decides, given a `{ [runtime]: { [rel]: bytes } }` map and a cap table,
|
||||
* which artifacts violate, which are unmeasured, and which comply. Keeping the
|
||||
* decision pure makes every boundary (`cap-1`/`cap`/`cap+1`) a millisecond
|
||||
* table test and keeps the Stryker gate able to bite.
|
||||
*
|
||||
* ── Why the cap table is HARD-CODED here, not read from capability.json ─────
|
||||
* ADR-2719's guiding principle (already load-bearing in `emitted-diff.cjs`:
|
||||
* it never re-derives a byte, because asserting `emitted == transform(source)`
|
||||
* is the tautology ADR-2264's Amendment rejected) applies here too, one level
|
||||
* up: a cap that is DERIVED from the same descriptor it is meant to guard
|
||||
* (`capabilities/<rt>/capability.json`) would silently follow any edit to that
|
||||
* descriptor. Bump the descriptor, the guard bumps with it, and a real
|
||||
* regression sails through unnoticed. `EMITTED_CAPS` is a second, independent
|
||||
* source of truth, edited deliberately by a human who has to look Windsurf's
|
||||
* actual 12,000-byte platform limit in the eye — exactly the friction a guard
|
||||
* exists to provide.
|
||||
*/
|
||||
|
||||
// ─── REASON enum ───────────────────────────────────────────────────────────
|
||||
|
||||
const REASON = Object.freeze({
|
||||
CAP_EXCEEDED: 'cap_exceeded',
|
||||
DEAD_RULE: 'dead_rule',
|
||||
UNKNOWN_RUNTIME: 'unknown_runtime',
|
||||
NO_ARTIFACTS: 'no_artifacts',
|
||||
INVALID_SIZES: 'invalid_sizes',
|
||||
INVALID_CAP_TABLE: 'invalid_cap_table',
|
||||
INVALID_CAP_VALUE: 'invalid_cap_value',
|
||||
RESERVED_KEY: 'reserved_key',
|
||||
UNSAFE_PATTERN: 'unsafe_pattern',
|
||||
});
|
||||
|
||||
/**
|
||||
* Keys that can never legitimately name a runtime or an emitted path, and
|
||||
* that also happen to be the JS-object footguns. Mirrors
|
||||
* `emitted-diff.cjs`'s `RESERVED_ACK_KEYS`: rejected LOUDLY (REASON.RESERVED_KEY),
|
||||
* never silently dropped.
|
||||
*/
|
||||
const RESERVED_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
|
||||
|
||||
/**
|
||||
* The declared per-runtime emitted-byte cap table. Exactly one real entry
|
||||
* today: Windsurf hard-caps a workspace workflow file at 12,000 bytes.
|
||||
*
|
||||
* Shape: `{ [runtime]: Array<{ pattern, maxBytes, note }> }`. `pattern` is a
|
||||
* simple glob, evaluated against the emitted-relative path: `*` matches
|
||||
* within one path segment (never crosses `/`), `**` matches across segments.
|
||||
* Frozen two levels deep so a test cannot mutate the shipped table out from
|
||||
* under a later assertion in the same run.
|
||||
*/
|
||||
const EMITTED_CAPS = Object.freeze({
|
||||
windsurf: Object.freeze([
|
||||
Object.freeze({
|
||||
pattern: 'workflows/*.md',
|
||||
maxBytes: 12000,
|
||||
note: 'Windsurf hard-caps workspace workflow files at 12,000 bytes.',
|
||||
}),
|
||||
]),
|
||||
});
|
||||
|
||||
// ─── Small predicates ────────────────────────────────────────────────────────
|
||||
|
||||
function isPlainObject(value) {
|
||||
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function typeNameOf(value) {
|
||||
if (value === null) return 'null';
|
||||
if (Array.isArray(value)) return 'array';
|
||||
return typeof value;
|
||||
}
|
||||
|
||||
/** A legal byte count or cap value: a `number`, never a coerced string, never
|
||||
* negative, NaN, Infinity, or fractional. `Number.isSafeInteger` alone
|
||||
* already excludes NaN/Infinity/non-integers; `>= 0` excludes negatives. */
|
||||
function isNonNegativeSafeInteger(value) {
|
||||
return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;
|
||||
}
|
||||
|
||||
/** `..` traversal or a leading `/` in a cap-table pattern is never legitimate
|
||||
* — every emitted-relative path in this repo is already relative and
|
||||
* segment-clean, so either shape can only be an authoring mistake or a
|
||||
* hostile table entry. */
|
||||
function isUnsafePattern(pattern) {
|
||||
return pattern.includes('..') || pattern.startsWith('/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate a `pattern` (already validated safe) into an anchored RegExp.
|
||||
* `*` -> one path segment (`[^/]*`); `**` -> across segments (`.*`). Every
|
||||
* other character is escaped, so a pattern is never accidentally read as a
|
||||
* richer regex than the two glob tokens it declares.
|
||||
*/
|
||||
function compileGlobPattern(pattern) {
|
||||
let out = '';
|
||||
for (let i = 0; i < pattern.length; i += 1) {
|
||||
const ch = pattern[i];
|
||||
if (ch === '*' && pattern[i + 1] === '*') {
|
||||
out += '.*';
|
||||
i += 1;
|
||||
} else if (ch === '*') {
|
||||
out += '[^/]*';
|
||||
} else if (/[.+^${}()|[\]\\]/.test(ch)) {
|
||||
out += `\\${ch}`;
|
||||
} else {
|
||||
out += ch;
|
||||
}
|
||||
}
|
||||
return new RegExp(`^${out}$`);
|
||||
}
|
||||
|
||||
/** Stable comparator: runtime, then rel/pattern. Plain `<`/`>` on strings,
|
||||
* not `localeCompare`, so ordering is locale-independent and reproducible. */
|
||||
function byRuntimeThen(field) {
|
||||
return (a, b) => {
|
||||
if (a.runtime !== b.runtime) return a.runtime < b.runtime ? -1 : 1;
|
||||
if (a[field] === b[field]) return 0;
|
||||
return a[field] < b[field] ? -1 : 1;
|
||||
};
|
||||
}
|
||||
|
||||
// ─── The decision ────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Evaluate every measured emitted artifact against the declared cap table.
|
||||
*
|
||||
* ── The conservation law (by construction) ───────────────────────────────
|
||||
* Every `(runtime, rel)` key in `sizes` whose byte value is a legal
|
||||
* non-negative safe integer, and whose `runtime`/`rel` are not reserved
|
||||
* keys, is placed into EXACTLY ONE of `violations`, `unmeasured`, or
|
||||
* `compliant` — the single walk below assigns each such key to precisely one
|
||||
* push. A key excluded by a RESERVED_KEY or INVALID_SIZES error is not a
|
||||
* legitimate size measurement at all (its own error already names it), so it
|
||||
* is not counted as a fourth bucket; the law is over the WELL-FORMED subset,
|
||||
* exactly as `diffEmitted`'s conservation law is over the entries `parseAck`
|
||||
* accepted.
|
||||
*
|
||||
* Internal bookkeeping (which rule matched which path, for dead-rule
|
||||
* detection) is kept as index-based tracking, never as an object keyed by an
|
||||
* external string — so a hostile `rel`/`runtime` value cannot pollute
|
||||
* anything even transiently.
|
||||
*
|
||||
* @param {object} opts
|
||||
* @param {object} opts.sizes { [runtime]: { [rel]: bytes } }
|
||||
* @param {object} [opts.capTable] defaults to the shipped `EMITTED_CAPS`
|
||||
* @returns {{
|
||||
* violations: Array, unmeasured: Array, compliant: Array,
|
||||
* deadRules: Array, errors: Array, ok: boolean
|
||||
* }}
|
||||
*/
|
||||
function evaluateEmittedCaps({ sizes, capTable = EMITTED_CAPS } = {}) {
|
||||
const errors = [];
|
||||
|
||||
if (sizes === null || sizes === undefined) {
|
||||
errors.push({
|
||||
reason: REASON.INVALID_SIZES,
|
||||
receivedType: sizes === null ? 'null' : 'undefined',
|
||||
message: `sizes is required, got ${sizes === null ? 'null' : 'undefined'}`,
|
||||
});
|
||||
} else if (!isPlainObject(sizes)) {
|
||||
errors.push({
|
||||
reason: REASON.INVALID_SIZES,
|
||||
receivedType: typeNameOf(sizes),
|
||||
message: `sizes must be a plain object keyed by runtime, got ${typeNameOf(sizes)}`,
|
||||
});
|
||||
}
|
||||
|
||||
if (capTable === null || capTable === undefined || !isPlainObject(capTable)) {
|
||||
errors.push({
|
||||
reason: REASON.INVALID_CAP_TABLE,
|
||||
receivedType: typeNameOf(capTable),
|
||||
message: `capTable must be a plain object keyed by runtime, got ${typeNameOf(capTable)}`,
|
||||
});
|
||||
}
|
||||
|
||||
if (errors.length) {
|
||||
return { violations: [], unmeasured: [], compliant: [], deadRules: [], errors, ok: false };
|
||||
}
|
||||
|
||||
const violations = [];
|
||||
const unmeasured = [];
|
||||
const compliant = [];
|
||||
const deadRules = [];
|
||||
|
||||
// ── capTable validation, once, up front — never at compare time ──────────
|
||||
// Maps `runtime -> Array<{ regex, maxBytes, note, pattern, matches: number }>`
|
||||
// for runtimes that ARE known (present in `sizes`) and whose rules parsed
|
||||
// cleanly. Keyed on `runtime`, but only ever assigned via `Map.set`, never
|
||||
// bracket-property assignment — a hostile `__proto__` runtime name cannot
|
||||
// pollute anything here either.
|
||||
const compiledRules = new Map();
|
||||
const knownRuntimeNoArtifacts = new Set();
|
||||
|
||||
for (const runtime of Object.keys(capTable)) {
|
||||
if (RESERVED_KEYS.has(runtime)) {
|
||||
errors.push({
|
||||
reason: REASON.RESERVED_KEY,
|
||||
scope: 'capTable-runtime',
|
||||
key: runtime,
|
||||
message: `capTable key "${runtime}" is reserved and can never be a real runtime name`,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!Object.prototype.hasOwnProperty.call(sizes, runtime)) {
|
||||
// A cap declared for a runtime that never even reported sizes. Not
|
||||
// installable/measured in this run at all — a hard error, distinct
|
||||
// from "measured but produced zero files" (REASON.NO_ARTIFACTS below).
|
||||
errors.push({
|
||||
reason: REASON.UNKNOWN_RUNTIME,
|
||||
runtime,
|
||||
message: `capTable declares caps for "${runtime}", but sizes has no entry for it`,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
const rules = capTable[runtime];
|
||||
if (!Array.isArray(rules)) {
|
||||
errors.push({
|
||||
reason: REASON.INVALID_CAP_TABLE,
|
||||
runtime,
|
||||
message: `capTable.${runtime} must be an array of rules, got ${typeNameOf(rules)}`,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
const parsed = [];
|
||||
rules.forEach((rule, index) => {
|
||||
if (!isPlainObject(rule)) {
|
||||
errors.push({
|
||||
reason: REASON.INVALID_CAP_TABLE,
|
||||
runtime,
|
||||
index,
|
||||
message: `capTable.${runtime}[${index}] must be an object with pattern/maxBytes`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const { pattern, maxBytes, note } = rule;
|
||||
|
||||
let patternOk = true;
|
||||
if (typeof pattern !== 'string' || pattern.length === 0) {
|
||||
errors.push({
|
||||
reason: REASON.INVALID_CAP_TABLE,
|
||||
runtime,
|
||||
index,
|
||||
message: `capTable.${runtime}[${index}].pattern must be a non-empty string`,
|
||||
});
|
||||
patternOk = false;
|
||||
} else if (isUnsafePattern(pattern)) {
|
||||
errors.push({
|
||||
reason: REASON.UNSAFE_PATTERN,
|
||||
runtime,
|
||||
pattern,
|
||||
message: `capTable.${runtime}[${index}].pattern "${pattern}" contains a ".." traversal or a leading "/"`,
|
||||
});
|
||||
patternOk = false;
|
||||
}
|
||||
|
||||
let capOk = true;
|
||||
if (!isNonNegativeSafeInteger(maxBytes)) {
|
||||
errors.push({
|
||||
reason: REASON.INVALID_CAP_VALUE,
|
||||
runtime,
|
||||
pattern: typeof pattern === 'string' ? pattern : null,
|
||||
value: maxBytes,
|
||||
message:
|
||||
`capTable.${runtime}[${index}].maxBytes must be a non-negative safe integer, `
|
||||
+ `got ${JSON.stringify(maxBytes)} (${typeNameOf(maxBytes)})`,
|
||||
});
|
||||
capOk = false;
|
||||
}
|
||||
|
||||
if (patternOk && capOk) {
|
||||
parsed.push({
|
||||
pattern,
|
||||
regex: compileGlobPattern(pattern),
|
||||
maxBytes,
|
||||
note: typeof note === 'string' ? note : undefined,
|
||||
matches: 0,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
compiledRules.set(runtime, parsed);
|
||||
}
|
||||
|
||||
// ── Walk sizes, sorted, and assign every well-formed key exactly once ────
|
||||
for (const runtime of Object.keys(sizes).sort()) {
|
||||
if (RESERVED_KEYS.has(runtime)) {
|
||||
errors.push({
|
||||
reason: REASON.RESERVED_KEY,
|
||||
scope: 'sizes-runtime',
|
||||
key: runtime,
|
||||
message: `sizes key "${runtime}" is reserved and can never be a real runtime name`,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
const artifacts = sizes[runtime];
|
||||
if (!isPlainObject(artifacts)) {
|
||||
errors.push({
|
||||
reason: REASON.INVALID_SIZES,
|
||||
runtime,
|
||||
message: `sizes.${runtime} must be a plain object of { rel: bytes }, got ${typeNameOf(artifacts)}`,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
const rels = Object.keys(artifacts);
|
||||
if (rels.length === 0) {
|
||||
errors.push({
|
||||
reason: REASON.NO_ARTIFACTS,
|
||||
runtime,
|
||||
message: `sizes.${runtime} produced no artifacts — never read "nothing to check" as "pass"`,
|
||||
});
|
||||
knownRuntimeNoArtifacts.add(runtime);
|
||||
continue;
|
||||
}
|
||||
|
||||
const rules = compiledRules.get(runtime) || [];
|
||||
|
||||
for (const rel of rels.sort()) {
|
||||
if (RESERVED_KEYS.has(rel)) {
|
||||
errors.push({
|
||||
reason: REASON.RESERVED_KEY,
|
||||
scope: 'sizes-rel',
|
||||
runtime,
|
||||
key: rel,
|
||||
message: `sizes.${runtime} key "${rel}" is reserved and can never be a real emitted path`,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
const bytes = artifacts[rel];
|
||||
if (!isNonNegativeSafeInteger(bytes)) {
|
||||
errors.push({
|
||||
reason: REASON.INVALID_SIZES,
|
||||
runtime,
|
||||
rel,
|
||||
message:
|
||||
`sizes.${runtime}["${rel}"] must be a non-negative safe integer, `
|
||||
+ `got ${JSON.stringify(bytes)} (${typeNameOf(bytes)})`,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
const rule = rules.find((r) => r.regex.test(rel));
|
||||
if (!rule) {
|
||||
unmeasured.push({ runtime, rel, bytes });
|
||||
continue;
|
||||
}
|
||||
|
||||
rule.matches += 1;
|
||||
if (bytes <= rule.maxBytes) {
|
||||
compliant.push({ runtime, rel, bytes, cap: rule.maxBytes });
|
||||
} else {
|
||||
violations.push({
|
||||
runtime,
|
||||
rel,
|
||||
bytes,
|
||||
cap: rule.maxBytes,
|
||||
delta: bytes - rule.maxBytes,
|
||||
reason: REASON.CAP_EXCEEDED,
|
||||
note: rule.note,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Dead rules: matched nothing across the WHOLE run ──────────────────────
|
||||
// Skipped for runtimes already flagged UNKNOWN_RUNTIME or NO_ARTIFACTS —
|
||||
// those are more specific, more actionable errors, and a rule for a
|
||||
// runtime with zero measured artifacts trivially matches nothing for a
|
||||
// reason this walk already named.
|
||||
for (const [runtime, rules] of compiledRules) {
|
||||
if (knownRuntimeNoArtifacts.has(runtime)) continue;
|
||||
for (const rule of rules) {
|
||||
if (rule.matches === 0) {
|
||||
deadRules.push({
|
||||
runtime,
|
||||
pattern: rule.pattern,
|
||||
cap: rule.maxBytes,
|
||||
reason: REASON.DEAD_RULE,
|
||||
message: `capTable.${runtime} rule "${rule.pattern}" matched zero emitted paths — a cap guarding nothing is rot`,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
violations.sort(byRuntimeThen('rel'));
|
||||
unmeasured.sort(byRuntimeThen('rel'));
|
||||
compliant.sort(byRuntimeThen('rel'));
|
||||
deadRules.sort(byRuntimeThen('pattern'));
|
||||
|
||||
const ok = errors.length === 0 && violations.length === 0 && deadRules.length === 0;
|
||||
|
||||
return { violations, unmeasured, compliant, deadRules, errors, ok };
|
||||
}
|
||||
|
||||
// ─── Rendering ────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Pure renderer. Tests assert on `evaluateEmittedCaps`'s structured result,
|
||||
* never on this string (CONTRIBUTING.md, "Prohibited: Raw Text Matching on
|
||||
* Test Outputs").
|
||||
*
|
||||
* The CAP_EXCEEDED message deliberately warns against the Goodhart's-Law
|
||||
* escape hatch: moving bytes behind an EAGERLY `@`-imported reference file
|
||||
* changes where the bytes are typed, not how many bytes load. An eager
|
||||
* `@`-import is inlined at load time, so the cap would read green while the
|
||||
* runtime still pays every byte — gaming the metric, not complying with it
|
||||
* (this exact failure mode is recorded in CONTEXT.md
|
||||
* `RULESET.WORKFLOW_SIZE_BUDGET`).
|
||||
*/
|
||||
function formatCapReport(result) {
|
||||
const parts = [];
|
||||
|
||||
if (result.errors.length) {
|
||||
parts.push(
|
||||
`${result.errors.length} error(s):\n ${result.errors.map((e) => e.message).join('\n ')}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (result.violations.length) {
|
||||
const list = result.violations.map(
|
||||
(v) => ` ${v.runtime}: ${v.rel} is ${v.bytes} bytes — exceeds the ${v.cap}-byte cap by ${v.delta}`
|
||||
+ (v.note ? ` (${v.note})` : ''),
|
||||
);
|
||||
parts.push(
|
||||
`${result.violations.length} emitted artifact(s) exceed their declared cap:\n${list.join('\n')}\n\n`
|
||||
+ 'Moving these bytes behind an EAGERLY `@`-imported reference file is gaming this '
|
||||
+ 'metric, not complying with it: an eager @-import is inlined before the cap is ever '
|
||||
+ 'measured, so the runtime still pays every byte at load time (CONTEXT.md '
|
||||
+ 'RULESET.WORKFLOW_SIZE_BUDGET). The fix is to reduce what actually loads.',
|
||||
);
|
||||
}
|
||||
|
||||
if (result.deadRules.length) {
|
||||
const list = result.deadRules.map((r) => ` ${r.runtime}: "${r.pattern}" (cap ${r.cap}) matched nothing`);
|
||||
parts.push(`${result.deadRules.length} cap rule(s) matched zero emitted paths — a cap guarding nothing is rot:\n${list.join('\n')}`);
|
||||
}
|
||||
|
||||
return parts.join('\n\n');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
REASON,
|
||||
EMITTED_CAPS,
|
||||
evaluateEmittedCaps,
|
||||
formatCapReport,
|
||||
};
|
||||
@@ -59,8 +59,7 @@ const { MANIFEST_FAMILIES, runMinimalInstall, buildParityManifest } = require('.
|
||||
const EXPECTED_MANIFEST_COUNT = MANIFEST_FAMILIES.length;
|
||||
|
||||
// ─── Emitted roots ────────────────────────────────────────────────────────────
|
||||
// Longest-first: `skills/msd` (hermes category dir) must win over `skills` for
|
||||
// `skills/msd/...`, and `.agents/skills` must win over `.agents`.
|
||||
// Longest-first: `.agents/skills` must win over `.agents`.
|
||||
|
||||
const SKILLS_ROOTS = ['.agents/skills', 'skills'];
|
||||
const HOOKS_ROOTS = ['hooks'];
|
||||
@@ -68,16 +67,10 @@ const HOOKS_ROOTS = ['hooks'];
|
||||
/** Source-of-truth command dir every skill/command surface converts from. */
|
||||
const COMMANDS_SRC = 'commands/msd';
|
||||
|
||||
/** Installer source file that emits the Cline/AGENTS.md instruction bodies as
|
||||
* code literals (buildClineRulesBody / buildClineAgentsMdBody /
|
||||
* buildClinePreToolUseHook). */
|
||||
const CLINE_BODY_SRC = 'src/runtime-hooks-surface.cts';
|
||||
|
||||
/**
|
||||
* Installer source file that GENERATES the Windows-only `hooks/<name>.cmd` shim
|
||||
* wrapping a Codex hook's `.js` script (#3426). Same physical file as
|
||||
* CLINE_BODY_SRC — kept as its own named constant because the two constants
|
||||
* attribute unrelated transform code that happens to live in one file:
|
||||
* Transform code:
|
||||
* buildCodexHookWindowsShimIR / ensureCodexHooksJsonSessionStart /
|
||||
* ensureCodexHooksJsonEvent (verified via Memtrace: these are the ONLY writers
|
||||
* of a `.cmd` file anywhere in the installer — no other runtime's hook surface
|
||||
@@ -85,8 +78,7 @@ const CLINE_BODY_SRC = 'src/runtime-hooks-surface.cts';
|
||||
*/
|
||||
const HOOKS_WINDOWS_SHIM_SRC = 'src/runtime-hooks-surface.cts';
|
||||
|
||||
/** Installer source file that emits the Hermes skill-category DESCRIPTION.md
|
||||
* (writeHermesCategoryDescription) as a code literal. */
|
||||
/** Installer source file that emits shared code-literal artifacts. */
|
||||
const INSTALLER_SRC = 'bin/install.js';
|
||||
|
||||
/** Module owning the #2544 `{"type":"commonjs"}` marker literal and the
|
||||
@@ -97,9 +89,6 @@ const COMMONJS_MARKER_SRC = 'src/commonjs-marker.cts';
|
||||
* beside it (_installNativePluginIfDeclared). */
|
||||
const INSTALL_ENGINE_SRC = 'src/install-engine.cts';
|
||||
|
||||
/** Source file holding the Kimi root-agent literal (runtime-artifact-layout.cts:303). */
|
||||
const KIMI_ROOT_AGENT_SRC = 'src/runtime-artifact-layout.cts';
|
||||
|
||||
/**
|
||||
* Transform sources for the per-runtime agent-content pipeline (#2757).
|
||||
*
|
||||
@@ -187,7 +176,7 @@ const ZCODE_BODY_TRANSFORM_SRCS = [
|
||||
'bin/install.js',
|
||||
];
|
||||
|
||||
// #4482: every non-Copilot runtime filters audience-specific notes through the
|
||||
// #4482: every runtime filters audience-specific notes through the
|
||||
// conversion module and the published installer copy path.
|
||||
const RUNTIME_NOTE_FILTER_TRANSFORM_SRCS = [
|
||||
'src/runtime-artifact-conversion.cts',
|
||||
@@ -282,7 +271,7 @@ const PROVENANCE_RULES = [
|
||||
{
|
||||
id: 'msd-core-verbatim',
|
||||
// Markdown payloads pass through copyWithPathReplacement's audience
|
||||
// filter for non-Copilot runtimes. Non-Markdown payloads remain byte-for-
|
||||
// filter for every runtime. Non-Markdown payloads remain byte-for-
|
||||
// byte copies, but one rule has one kind; the match-specific transform
|
||||
// list below keeps the causal attribution precise.
|
||||
kind: 'derived',
|
||||
@@ -343,10 +332,7 @@ const PROVENANCE_RULES = [
|
||||
// `agents/msd.md`, which does not exist — a false attribution that still passed
|
||||
// totality, i.e. the exact residual ADR-2719 records. Every repo agent is
|
||||
// `msd-<name>.md`, so excluding the bare `msd.md` is precise.
|
||||
// `.agent.md` is likewise excluded — Copilot emits a RENAMED copy
|
||||
// (`<name>.agent.md`) whose source is `agents/<name>.md`; matching it here
|
||||
// resolved to a file that does not exist. Same false-attribution class.
|
||||
pattern: /^(?!msd\.md$)(?!.*\.agent\.md$)[^/]+\.md$/,
|
||||
pattern: /^(?!msd\.md$)[^/]+\.md$/,
|
||||
sources: (m) => [`agents/${m[0]}`],
|
||||
transforms: AGENT_TRANSFORM_SRCS,
|
||||
},
|
||||
@@ -381,11 +367,8 @@ const PROVENANCE_RULES = [
|
||||
roots: HOOKS_ROOTS,
|
||||
// Emitted from hooks/dist/, which scripts/build-hooks.js builds from hooks/.
|
||||
// Attribute to the REPO source a PR actually edits, not the build artifact.
|
||||
// Excludes Copilot's hook-registration JSON (next rule) — that is a code
|
||||
// literal, not a built script, and attributing it here resolved to a
|
||||
// nonexistent `hooks/msd-session.json`. `package.json` is excluded for the
|
||||
// same reason (the #2544 `commonjs-marker` rule below): there is no
|
||||
// `hooks/package.json` in the repo to attribute to.
|
||||
// `package.json` is excluded (the #2544 `commonjs-marker` rule below):
|
||||
// there is no `hooks/package.json` in the repo to attribute to.
|
||||
//
|
||||
// `.cmd` shims are a SEPARATE, Windows-only emission path folded into this
|
||||
// SAME rule rather than a dedicated one (see the `transforms` doc above for
|
||||
@@ -400,11 +383,11 @@ const PROVENANCE_RULES = [
|
||||
// same source file. The wrapped `.js` file's NAME flows into the `.cmd`
|
||||
// bytes; its CONTENT never does — see the `sources` comment below for why
|
||||
// that rules out attributing to `hooks/<name>.js`.
|
||||
pattern: /^(?!msd-session\.json$|package\.json$).+$/,
|
||||
pattern: /^(?!package\.json$).+$/,
|
||||
// `package.json` (the CommonJS marker) is excluded here and owned by the
|
||||
// dedicated `commonjs-marker` rule below. #2717 attributed it inside THIS
|
||||
// rule, routing it to HOOKS_WINDOWS_SHIM_SRC because at that point
|
||||
// src/runtime-hooks-surface.cts was its only emitter (cursor/windsurf, plus
|
||||
// src/runtime-hooks-surface.cts was its only emitter (cursor, plus
|
||||
// the codex copy block calling the same exported helper). #2544 adds a
|
||||
// second emitter — src/commonjs-marker.cts, via installSharedHooksBundle and
|
||||
// _installNativePluginIfDeclared — and two roots this rule does not cover
|
||||
@@ -418,8 +401,7 @@ const PROVENANCE_RULES = [
|
||||
// source file), but its CONTENT never does, so attributing `sources` to
|
||||
// `hooks/<name>.js` would be a false byte-provenance claim. Point `sources`
|
||||
// at the same file as `transforms`, matching the `code-derived` convention
|
||||
// used elsewhere in this table (copilot-hook-registration, cline-rules-
|
||||
// code-derived, hermes-category-description). The redundancy between
|
||||
// used elsewhere in this table. The redundancy between
|
||||
// `sources` and `transforms` here is harmless — the mis-attribution was not.
|
||||
// No `package.json` arm here: the pattern above excludes it, so the branch
|
||||
// #2717 added for it is unreachable from this rule.
|
||||
@@ -434,8 +416,8 @@ const PROVENANCE_RULES = [
|
||||
roots: [...HOOKS_ROOTS, 'plugins', 'extensions'],
|
||||
// #2544: a `{"type":"commonjs"}` module-type marker, written as a code
|
||||
// literal so Node's ancestor walk resolves MSD's staged `.js` files as
|
||||
// CommonJS under an ambient `"type": "module"`. Like the Copilot
|
||||
// registration JSON above it is emitted, never built — there is no
|
||||
// CommonJS under an ambient `"type": "module"`. It is emitted,
|
||||
// never built — there is no
|
||||
// `hooks/package.json` or `plugins/package.json` in the repo, so the
|
||||
// built-script rule would attribute it to a path that does not exist.
|
||||
// Sources are scoped PER ROOT, not declared as one flat union. Each root has
|
||||
@@ -465,13 +447,8 @@ const PROVENANCE_RULES = [
|
||||
if (root === 'plugins' || root === 'extensions') {
|
||||
return [COMMONJS_MARKER_SRC, INSTALL_ENGINE_SRC];
|
||||
}
|
||||
// Some products install the shared bundle into their own native hook
|
||||
// root rather than under the generic Agent-Skills configDir (#2755).
|
||||
if (root === '.kimi/hooks' || root === '.kimi-code/hooks') {
|
||||
return [COMMONJS_MARKER_SRC, INSTALLER_SRC];
|
||||
}
|
||||
// 'hooks' — written by the shared bundle for most runtimes and by the
|
||||
// #2717 dedicated paths for cursor/windsurf/codex.
|
||||
// #2717 dedicated paths for cursor/codex.
|
||||
return [COMMONJS_MARKER_SRC, INSTALLER_SRC, HOOKS_WINDOWS_SHIM_SRC];
|
||||
},
|
||||
},
|
||||
@@ -533,8 +510,7 @@ const PROVENANCE_RULES = [
|
||||
kind: 'descriptor',
|
||||
roots: ['plugins', 'extensions'],
|
||||
pattern: /^[^/]+\.(js|cjs|mjs)$/,
|
||||
// Source is per-runtime (opencode -> .opencode/…, kilo -> .kilo/…,
|
||||
// pi -> pi/msd.cjs), so attribution is a function of (rel, runtime).
|
||||
// Source is per-runtime (opencode -> .opencode/…), so attribution is a function of (rel, runtime).
|
||||
sources: (m, ctx) => {
|
||||
const np = nativePluginDescriptor(ctx.runtime);
|
||||
if (!np || !np.source) {
|
||||
@@ -554,8 +530,7 @@ const PROVENANCE_RULES = [
|
||||
// ^AGENTS\.md$) and `synthesized-msd-defaults` (^\.msd/defaults\.json$):
|
||||
// both paths only appeared in a manifest while the harness collapsed
|
||||
// configDir onto the sandbox HOME, walking HOME-level siblings
|
||||
// (cline's ~/.agents/AGENTS.md, every non-Claude runtime's
|
||||
// ~/.msd/defaults.json). With the harness installing into each runtime's
|
||||
// (every non-Claude runtime's ~/.msd/defaults.json). With the harness installing into each runtime's
|
||||
// real global subdirectory those files sit outside the walked configDir,
|
||||
// the rules matched nothing, and the totality guard's dead-rule arm fired
|
||||
// — exactly as designed. The files are still written, still covered by the
|
||||
@@ -863,13 +838,11 @@ module.exports = {
|
||||
EXPECTED_MANIFEST_COUNT,
|
||||
PROVENANCE_RULES,
|
||||
SKILLS_ROOTS,
|
||||
KIMI_ROOT_AGENT_SRC,
|
||||
AGENT_TRANSFORM_SRCS,
|
||||
RUNTIME_NOTE_FILTER_TRANSFORM_SRCS,
|
||||
SOURCE_PREFIX_SUFFIX,
|
||||
HOOKS_ROOTS,
|
||||
COMMANDS_SRC,
|
||||
CLINE_BODY_SRC,
|
||||
HOOKS_WINDOWS_SHIM_SRC,
|
||||
INSTALLER_SRC,
|
||||
stripSkillPrefix,
|
||||
|
||||
@@ -163,7 +163,7 @@ const VOLATILE_FILES = new Set([
|
||||
// varies by platform and cannot be normalized to a single sentinel reliably.
|
||||
// Their content is asserted directly by the dedicated hook tests
|
||||
// (install-minimal-hooks, sh-hook-paths, codex-config, etc.). Matched by basename.
|
||||
// settings.json = Claude/Antigravity/Augment/etc. hook surface; hooks.json =
|
||||
// settings.json = Claude/Antigravity hook surface; hooks.json =
|
||||
// Codex/Cursor hook surface — both embed the platform-varying node-runner command.
|
||||
// settings.local.json = Claude LOCAL hook surface (#338): same platform-varying
|
||||
// node-runner command as settings.json, so excluded for the same reason (#2086).
|
||||
@@ -235,8 +235,7 @@ const SEMVER_ISH_RE = /^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$/;
|
||||
* - `"version": "<ver>"` — JSON manifests (plugin/extension/capability-style)
|
||||
* embedding the package version as a string field.
|
||||
* - `version: "<ver>"` / `version: <ver>` — YAML frontmatter version fields (e.g.
|
||||
* skill frontmatter's `yamlQuote(pkg.version)`, Hermes' category
|
||||
* `DESCRIPTION.md`).
|
||||
* skill frontmatter's `yamlQuote(pkg.version)`).
|
||||
* - `@golem15/msd-core@<ver>` — pinned package-spec references.
|
||||
* - a file whose ENTIRE trimmed content IS the version (`msd-core/VERSION`).
|
||||
* Each pattern only matches when the version in the content EQUALS the supplied
|
||||
@@ -605,7 +604,7 @@ function runMinimalInstall({ runtime, scope, extraArgs = [], installScript = INS
|
||||
// includes rewritten on live installs, zero manifest/fixture diffs). The
|
||||
// explicit flag stays: hermeticity-by-override is immune to ambient
|
||||
// redirect envs (CI runners export XDG_CONFIG_HOME, which redefines the
|
||||
// opencode/kilo XDG descriptors' resolution when no explicit dir wins).
|
||||
// opencode XDG descriptor's resolution when no explicit dir wins).
|
||||
const globalMeta = RUNTIME_META[runtime];
|
||||
if (!globalMeta || !globalMeta.globalSuffix) {
|
||||
// #3023 lesson: a silent `path.join(root, undefined)` here throws a
|
||||
@@ -623,21 +622,18 @@ function runMinimalInstall({ runtime, scope, extraArgs = [], installScript = INS
|
||||
cwd = root;
|
||||
// #3031: local scope reads RUNTIME_META.localDir — the SAME table the
|
||||
// global branch above reads — instead of a second hand-maintained map.
|
||||
// That duplicate map was missing several runtimes (e.g. hermes,
|
||||
// zcode), so `scope: 'local'` for any of them resolved
|
||||
// `path.join(root, undefined)` and threw a bare TypeError naming neither
|
||||
// the runtime nor the map at fault. #3023 fixed exactly this for `pi` by
|
||||
// adding one more entry, which left the divergence itself in place; the
|
||||
// table is now single-source so a new runtime cannot reintroduce it.
|
||||
// `cline` keeps its ternary: its local artifacts land at the project root
|
||||
// itself, which is a genuine exception rather than a directory name.
|
||||
// That duplicate map was missing several runtimes (e.g. zcode), so
|
||||
// `scope: 'local'` for any of them resolved `path.join(root, undefined)`
|
||||
// and threw a bare TypeError naming neither the runtime nor the map at
|
||||
// fault; the table is now single-source so a new runtime cannot
|
||||
// reintroduce it.
|
||||
const localMeta = RUNTIME_META[runtime];
|
||||
if (runtime !== 'cline' && (!localMeta || !localMeta.localDir)) {
|
||||
if (!localMeta || !localMeta.localDir) {
|
||||
throw new Error(
|
||||
`runMinimalInstall: no RUNTIME_META.localDir for runtime "${runtime}" — refusing to guess a local config dir (#3031)`,
|
||||
);
|
||||
}
|
||||
configDir = runtime === 'cline' ? root : path.join(root, localMeta.localDir);
|
||||
configDir = path.join(root, localMeta.localDir);
|
||||
}
|
||||
args.push(...extraArgs);
|
||||
const result = runNode(args, {
|
||||
@@ -680,7 +676,7 @@ function manifestSkillSet(manifest) {
|
||||
const seg = key.split('/')[1].replace(/^msd-/, '').replace(/\.md$/, '');
|
||||
out.add(seg);
|
||||
} else if (key.startsWith('command/')) {
|
||||
// OpenCode/Kilo: command/msd-<cmd>.md
|
||||
// OpenCode: command/msd-<cmd>.md
|
||||
const file = key.split('/')[1];
|
||||
out.add(file.replace(/^msd-/, '').replace(/\.md$/, ''));
|
||||
} else if (key.startsWith('commands/msd/')) {
|
||||
|
||||
@@ -47,8 +47,8 @@ for (const routerStem of ROUTER_STEMS) {
|
||||
/**
|
||||
* Nested SKILL.md path for a concrete skill stem:
|
||||
* <skillsRoot>/<prefix><router>/skills/<stem>/SKILL.md
|
||||
* prefix is '' for runtimes that nest under a skills/msd parent dir (hermes),
|
||||
* or 'msd-' for flat-prefixed runtimes (claude, cline, qwen, …).
|
||||
* prefix is '' for runtimes that nest under a bare parent dir, or 'msd-' for
|
||||
* flat-prefixed runtimes (claude, …).
|
||||
*/
|
||||
function nestedSkillPath(skillsRoot, prefix, stem) {
|
||||
const router = CHILD_ROUTER[stem];
|
||||
|
||||
Reference in New Issue
Block a user