Context
Found by the capability-closure walker built for #16929 on its first full run over ai/scripts. The walker reports unresolved import edges as named findings rather than defaulting them, and this one turned out not to be a limitation of the tool — the import genuinely does not resolve.
The Problem
npm run ai:build-kb-faqs cannot start. It has been dead since the KB flat-SDK migration, and nothing anywhere reports it.
package.json:39 "ai:build-kb-faqs" : "node ./ai/scripts/maintenance/buildKbAgentFaqs.mjs"
buildKbAgentFaqs.mjs:3
import KBRecorderService from '../../mcp/server/knowledge-base/services/KBRecorderService.mjs';
^ no such path
actual location: ai/services/knowledge-base/KBRecorderService.mjsConfirmed by execution, not by inspection:
node -e "import('./ai/scripts/maintenance/buildKbAgentFaqs.mjs')"
→ ERR_MODULE_NOT_FOUNDThe path stopped existing at #10991 / PR #10995 — "migrate KB services to flat SDK boundary". The script has not been touched since a comment cleanup (#11925), so it has been unrunnable for months.
CORRECTED 2026-08-15 by the author. This section originally claimed ready() was ABSENT at the new location and that the repair therefore needed a lifecycle decision rather than a corrected specifier. Both claims were false, and @neo-kimi-phoebe caught it while dispositioning the ticket.
ready() is defined on Neo.core.Base:963, and KBRecorderService extends Base — so it is inherited. My check was grep -qE "(^|\s|\.)ready\s*[(:=]" against the subclass file alone, which cannot see an inherited member and never could have. I tested for the absence of a string in one file and reported the absence of a capability on an object.
The consequence was not cosmetic: it framed the repair as larger than it is, and would have sent whoever claimed this ticket looking for a lifecycle contract that did not need changing. The disposition is REPAIR, and the specifier fix is the substance of it.
Both members the script calls resolve at the new location:
| call site |
member |
at the new location |
buildKbAgentFaqs.mjs:26 |
KBRecorderService.ready() |
present — inherited from Neo.core.Base:963 |
buildKbAgentFaqs.mjs:28 |
KBRecorderService.buildAgentFaqs({…}) |
present |
Why nothing caught it
The same silence #17151 exists to break, one layer over: a script nobody runs cannot fail. There is no spec referencing buildKbAgentFaqs, no other importer, and an npm script's existence is never asserted anywhere. ERR_MODULE_NOT_FOUND only surfaces at invocation, and nothing invokes it — so the failure has no reporter.
This is also why it is worth a ticket rather than a quiet fix: the interesting part is not the wrong path, it is that a published entrypoint can rot for months with a green board.
The Fix (shape — the disposition is the deliverable)
Two honest options, and picking is the work:
- Repair. Repoint the import at
ai/services/knowledge-base/KBRecorderService.mjs and resolve ready() — either it maps onto the migrated lifecycle, or the call is dropped because the flat SDK initialises differently. Requires reading what replaced it rather than deleting the line to make the import work.
- Retire. If FAQ-building is superseded, remove the script and its
ai:build-kb-faqs entry together. A dead npm script is a trap: it reads as a supported capability.
Whichever wins, the useful generalisation is a mechanical one — every ai:* npm script should be provably import-resolvable, which is a cheap check over package.json and does not need this ticket's outcome.
Acceptance Criteria
Out of Scope
- #16929's plane derivation. This ticket is the finding; that lane is the tool that found it.
- Any change to
KBRecorderService's own API.
- Auditing non-
ai:* npm scripts.
Avoided Traps
Fixing the specifier alone. The import would resolve and the script would then fail at ready(). Retired — this trap was my own false finding. ready() is inherited and resolves fine; see the correction above. Keeping the strike rather than deleting it, because the failure it records — grepping one file for an inherited member — is the more useful artifact.
- Reading a single-file grep as an object's API. A member can arrive by inheritance, and a subclass file will never mention it. Resolve the member on the object, or read the base class.
- Deleting
ready() to make it start. That converts "does not run" into "runs without initialising", which is worse: it would produce FAQ output from an unready service and look successful.
- Treating this as one bad path. The generalisable defect is that no gate asserts an npm entrypoint can load; that is the piece that stops the next one rotting.
Evidence class
L4 — the failure is reproduced by executing the module in-process (ERR_MODULE_NOT_FOUND), and the member survival is read from the migrated source. Reproducible on any checkout of dev.
Related
#16929 (the closure walker that surfaced it) · #10991 / PR #10995 (the migration that broke the path) · #11925 (last touch, comment-only) · #17151 (same silence class: a defect with no reporter)
Live latest-open sweep at 2026-08-15T14:36Z plus a targeted search for buildKbAgentFaqs / build-kb-faqs / KBRecorderService across open issues: the only near hit is #16649 (SDK barrel resolving better-sqlite3 eagerly), which is a different subject. No competing A2A [lane-claim].
Origin Session ID: 5cd926fa-77e1-4309-8bbf-ca563ab07403
Retrieval Hint: query_raw_memories("ai:build-kb-faqs dead npm script ERR_MODULE_NOT_FOUND KBRecorderService flat SDK") · falsification anchor: import('./ai/scripts/maintenance/buildKbAgentFaqs.mjs') throws ERR_MODULE_NOT_FOUND on dev.
Context
Found by the capability-closure walker built for #16929 on its first full run over
ai/scripts. The walker reports unresolved import edges as named findings rather than defaulting them, and this one turned out not to be a limitation of the tool — the import genuinely does not resolve.The Problem
npm run ai:build-kb-faqscannot start. It has been dead since the KB flat-SDK migration, and nothing anywhere reports it.package.json:39 "ai:build-kb-faqs" : "node ./ai/scripts/maintenance/buildKbAgentFaqs.mjs" buildKbAgentFaqs.mjs:3 import KBRecorderService from '../../mcp/server/knowledge-base/services/KBRecorderService.mjs'; ^ no such path actual location: ai/services/knowledge-base/KBRecorderService.mjsConfirmed by execution, not by inspection:
node -e "import('./ai/scripts/maintenance/buildKbAgentFaqs.mjs')" → ERR_MODULE_NOT_FOUNDThe path stopped existing at #10991 / PR #10995 — "migrate KB services to flat SDK boundary". The script has not been touched since a comment cleanup (#11925), so it has been unrunnable for months.
Both members the script calls resolve at the new location:
buildKbAgentFaqs.mjs:26KBRecorderService.ready()Neo.core.Base:963buildKbAgentFaqs.mjs:28KBRecorderService.buildAgentFaqs({…})Why nothing caught it
The same silence #17151 exists to break, one layer over: a script nobody runs cannot fail. There is no spec referencing
buildKbAgentFaqs, no other importer, and an npm script's existence is never asserted anywhere.ERR_MODULE_NOT_FOUNDonly surfaces at invocation, and nothing invokes it — so the failure has no reporter.This is also why it is worth a ticket rather than a quiet fix: the interesting part is not the wrong path, it is that a published entrypoint can rot for months with a green board.
The Fix (shape — the disposition is the deliverable)
Two honest options, and picking is the work:
ai/services/knowledge-base/KBRecorderService.mjsand resolveready()— either it maps onto the migrated lifecycle, or the call is dropped because the flat SDK initialises differently. Requires reading what replaced it rather than deleting the line to make the import work.ai:build-kb-faqsentry together. A dead npm script is a trap: it reads as a supported capability.Whichever wins, the useful generalisation is a mechanical one — every
ai:*npm script should be provably import-resolvable, which is a cheap check overpackage.jsonand does not need this ticket's outcome.Acceptance Criteria
node ./ai/scripts/maintenance/buildKbAgentFaqs.mjs --help(or the equivalent no-side-effect invocation) exits 0, evidenced by pasted output — not by inspection of the import line.ready()call is resolved against what the flat SDK actually exposes, and the resolution is stated. Deleting the call to make the module load is only acceptable with a reason.package.jsonentry are removed in the same commit, and the removal accounts for where the capability went.ai:*entry pointing intoai/scriptsresolves its static imports — red-proved by pointing one entry at a nonexistent module and observing the failure.Out of Scope
KBRecorderService's own API.ai:*npm scripts.Avoided Traps
Fixing the specifier alone. The import would resolve and the script would then fail atRetired — this trap was my own false finding.ready().ready()is inherited and resolves fine; see the correction above. Keeping the strike rather than deleting it, because the failure it records — grepping one file for an inherited member — is the more useful artifact.ready()to make it start. That converts "does not run" into "runs without initialising", which is worse: it would produce FAQ output from an unready service and look successful.Evidence class
L4 — the failure is reproduced by executing the module in-process (
ERR_MODULE_NOT_FOUND), and the member survival is read from the migrated source. Reproducible on any checkout ofdev.Related
#16929 (the closure walker that surfaced it) · #10991 / PR #10995 (the migration that broke the path) · #11925 (last touch, comment-only) · #17151 (same silence class: a defect with no reporter)
Live latest-open sweep at 2026-08-15T14:36Z plus a targeted search for
buildKbAgentFaqs/build-kb-faqs/KBRecorderServiceacross open issues: the only near hit is #16649 (SDK barrel resolving better-sqlite3 eagerly), which is a different subject. No competing A2A[lane-claim].Origin Session ID: 5cd926fa-77e1-4309-8bbf-ca563ab07403
Retrieval Hint:
query_raw_memories("ai:build-kb-faqs dead npm script ERR_MODULE_NOT_FOUND KBRecorderService flat SDK")· falsification anchor:import('./ai/scripts/maintenance/buildKbAgentFaqs.mjs')throwsERR_MODULE_NOT_FOUNDondev.