Found while fixing generated-skill drift in #3138 — out of scope there (it is pre-existing and identical before/after regeneration, so it does not affect that diff), filed per Prime Directive #10.
Problem
packages/spec/scripts/build-skill-references.ts resolves the transitive schema closure with a single-line regex:
const re = /^import\s+.*\s+from\s+['"](\.[^'"]+)['"]/gm;
. does not match newlines, so a multi-line import is never seen:
import {
FooSchema,
} from './foo.zod'; // ← invisible to the resolver
The dep is silently omitted from the skill's "Transitive dependencies" section. No warning — the file simply never enters the queue.
Impact
18 non-test multi-line local imports in packages/spec/src are currently invisible, e.g.:
api/feed-api.zod.ts -> ../data/feed.zod
api/feed-api.zod.ts -> ../data/subscription.zod
api/protocol.zod.ts -> ./analytics.zod
api/protocol.zod.ts -> ../kernel/package-registry.zod
automation/bpmn-mapping.ts -> ./control-flow.zod
integration/connector/*.zod.ts -> ../connector.zod (×4)
(217 including .test.ts, which are irrelevant here.)
The effect is under-reporting, not a broken pointer: skills/*/references/_index.md ships to third parties via npx skills add, and an agent following the index gets an incomplete picture of the schema graph. Whether any of the 18 is reachable from a SKILL_MAP entry's closure is worth checking — that determines real-world impact.
Second, related blind spot
extractLocalImports appends .ts only when the specifier does not already end in .ts:
if (!resolved.endsWith('.ts')) resolved += '.ts';
So a .js-suffixed ESM specifier resolves to foo.js.ts, never exists, and is dropped — e.g. contracts/data-engine.ts -> ../data/index.js and conversions/apply.ts -> ./types.js.
Suggested fix
Replace the regex with a real import scan (TS AST, or at minimum a multi-line-tolerant pattern plus .js → .ts specifier normalization). Note this will legitimately grow the "Transitive dependencies" section of several indexes — a gate now guards those files (#3138), so the regen must land with the fix.
Why this is safe to do now
#3138 added check:skill-refs, proven to fail on stale/missing/leftover output, so any change to the resolver will surface as drift rather than rot silently.
Found while fixing generated-skill drift in #3138 — out of scope there (it is pre-existing and identical before/after regeneration, so it does not affect that diff), filed per Prime Directive #10.
Problem
packages/spec/scripts/build-skill-references.tsresolves the transitive schema closure with a single-line regex:.does not match newlines, so a multi-line import is never seen:The dep is silently omitted from the skill's "Transitive dependencies" section. No warning — the file simply never enters the queue.
Impact
18 non-test multi-line local imports in
packages/spec/srcare currently invisible, e.g.:(217 including
.test.ts, which are irrelevant here.)The effect is under-reporting, not a broken pointer:
skills/*/references/_index.mdships to third parties vianpx skills add, and an agent following the index gets an incomplete picture of the schema graph. Whether any of the 18 is reachable from aSKILL_MAPentry's closure is worth checking — that determines real-world impact.Second, related blind spot
extractLocalImportsappends.tsonly when the specifier does not already end in.ts:So a
.js-suffixed ESM specifier resolves tofoo.js.ts, never exists, and is dropped — e.g.contracts/data-engine.ts -> ../data/index.jsandconversions/apply.ts -> ./types.js.Suggested fix
Replace the regex with a real import scan (TS AST, or at minimum a multi-line-tolerant pattern plus
.js→.tsspecifier normalization). Note this will legitimately grow the "Transitive dependencies" section of several indexes — a gate now guards those files (#3138), so the regen must land with the fix.Why this is safe to do now
#3138 added
check:skill-refs, proven to fail on stale/missing/leftover output, so any change to the resolver will surface as drift rather than rot silently.