spectra-cli 4.2.0 → 4.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (365) hide show
  1. package/README.md +12 -5
  2. package/dist/.spectra-build-meta.json +9 -0
  3. package/dist/adapters/language-adapter.d.ts +7 -12
  4. package/dist/adapters/language-adapter.d.ts.map +1 -1
  5. package/dist/adapters/python-adapter.d.ts.map +1 -1
  6. package/dist/adapters/python-adapter.js +11 -2
  7. package/dist/adapters/python-adapter.js.map +1 -1
  8. package/dist/batch/batch-orchestrator.d.ts +44 -1
  9. package/dist/batch/batch-orchestrator.d.ts.map +1 -1
  10. package/dist/batch/batch-orchestrator.js +312 -76
  11. package/dist/batch/batch-orchestrator.js.map +1 -1
  12. package/dist/batch/delta-regenerator.d.ts.map +1 -1
  13. package/dist/batch/delta-regenerator.js +21 -28
  14. package/dist/batch/delta-regenerator.js.map +1 -1
  15. package/dist/batch/module-grouper.d.ts +7 -0
  16. package/dist/batch/module-grouper.d.ts.map +1 -1
  17. package/dist/batch/module-grouper.js +2 -0
  18. package/dist/batch/module-grouper.js.map +1 -1
  19. package/dist/batch/regen-plan.d.ts +78 -0
  20. package/dist/batch/regen-plan.d.ts.map +1 -0
  21. package/dist/batch/regen-plan.js +93 -0
  22. package/dist/batch/regen-plan.js.map +1 -0
  23. package/dist/cli/commands/batch.d.ts.map +1 -1
  24. package/dist/cli/commands/batch.js +36 -8
  25. package/dist/cli/commands/batch.js.map +1 -1
  26. package/dist/cli/commands/scaffold-kb.d.ts +9 -0
  27. package/dist/cli/commands/scaffold-kb.d.ts.map +1 -0
  28. package/dist/cli/commands/scaffold-kb.js +179 -0
  29. package/dist/cli/commands/scaffold-kb.js.map +1 -0
  30. package/dist/cli/index.js +24 -5
  31. package/dist/cli/index.js.map +1 -1
  32. package/dist/cli/utils/error-handler.d.ts +0 -6
  33. package/dist/cli/utils/error-handler.d.ts.map +1 -1
  34. package/dist/cli/utils/error-handler.js +0 -13
  35. package/dist/cli/utils/error-handler.js.map +1 -1
  36. package/dist/cli/utils/parse-args.d.ts +43 -3
  37. package/dist/cli/utils/parse-args.d.ts.map +1 -1
  38. package/dist/cli/utils/parse-args.js +98 -12
  39. package/dist/cli/utils/parse-args.js.map +1 -1
  40. package/dist/cli/version-meta.d.ts +16 -0
  41. package/dist/cli/version-meta.d.ts.map +1 -0
  42. package/dist/cli/version-meta.js +28 -0
  43. package/dist/cli/version-meta.js.map +1 -0
  44. package/dist/core/ast-analyzer.d.ts.map +1 -1
  45. package/dist/core/ast-analyzer.js +7 -11
  46. package/dist/core/ast-analyzer.js.map +1 -1
  47. package/dist/core/import-resolver.d.ts +70 -67
  48. package/dist/core/import-resolver.d.ts.map +1 -1
  49. package/dist/core/import-resolver.js +341 -200
  50. package/dist/core/import-resolver.js.map +1 -1
  51. package/dist/core/single-spec-orchestrator.d.ts +21 -0
  52. package/dist/core/single-spec-orchestrator.d.ts.map +1 -1
  53. package/dist/core/single-spec-orchestrator.js +41 -14
  54. package/dist/core/single-spec-orchestrator.js.map +1 -1
  55. package/dist/core/skeleton-hash.d.ts +33 -0
  56. package/dist/core/skeleton-hash.d.ts.map +1 -0
  57. package/dist/core/skeleton-hash.js +76 -0
  58. package/dist/core/skeleton-hash.js.map +1 -0
  59. package/dist/core/tree-sitter-analyzer.d.ts +3 -2
  60. package/dist/core/tree-sitter-analyzer.d.ts.map +1 -1
  61. package/dist/core/tree-sitter-analyzer.js +5 -6
  62. package/dist/core/tree-sitter-analyzer.js.map +1 -1
  63. package/dist/core/tree-sitter-fallback.d.ts +7 -2
  64. package/dist/core/tree-sitter-fallback.d.ts.map +1 -1
  65. package/dist/core/tree-sitter-fallback.js +18 -15
  66. package/dist/core/tree-sitter-fallback.js.map +1 -1
  67. package/dist/extraction/image-extractor.d.ts.map +1 -1
  68. package/dist/extraction/image-extractor.js +3 -1
  69. package/dist/extraction/image-extractor.js.map +1 -1
  70. package/dist/extraction/markdown-extractor.d.ts.map +1 -1
  71. package/dist/extraction/markdown-extractor.js +3 -1
  72. package/dist/extraction/markdown-extractor.js.map +1 -1
  73. package/dist/generator/frontmatter.d.ts +5 -0
  74. package/dist/generator/frontmatter.d.ts.map +1 -1
  75. package/dist/generator/frontmatter.js +4 -0
  76. package/dist/generator/frontmatter.js.map +1 -1
  77. package/dist/kb-mcp/index.d.ts +14 -0
  78. package/dist/kb-mcp/index.d.ts.map +1 -0
  79. package/dist/kb-mcp/index.js +24 -0
  80. package/dist/kb-mcp/index.js.map +1 -0
  81. package/dist/kb-mcp/lib/kb-error.d.ts +21 -0
  82. package/dist/kb-mcp/lib/kb-error.d.ts.map +1 -0
  83. package/dist/kb-mcp/lib/kb-error.js +28 -0
  84. package/dist/kb-mcp/lib/kb-error.js.map +1 -0
  85. package/dist/kb-mcp/lib/kb-locator.d.ts +58 -0
  86. package/dist/kb-mcp/lib/kb-locator.d.ts.map +1 -0
  87. package/dist/kb-mcp/lib/kb-locator.js +85 -0
  88. package/dist/kb-mcp/lib/kb-locator.js.map +1 -0
  89. package/dist/kb-mcp/lib/result-merger.d.ts +35 -0
  90. package/dist/kb-mcp/lib/result-merger.d.ts.map +1 -0
  91. package/dist/kb-mcp/lib/result-merger.js +64 -0
  92. package/dist/kb-mcp/lib/result-merger.js.map +1 -0
  93. package/dist/kb-mcp/server.d.ts +12 -0
  94. package/dist/kb-mcp/server.d.ts.map +1 -0
  95. package/dist/kb-mcp/server.js +29 -0
  96. package/dist/kb-mcp/server.js.map +1 -0
  97. package/dist/kb-mcp/tools/kb-api-lookup.d.ts +28 -0
  98. package/dist/kb-mcp/tools/kb-api-lookup.d.ts.map +1 -0
  99. package/dist/kb-mcp/tools/kb-api-lookup.js +237 -0
  100. package/dist/kb-mcp/tools/kb-api-lookup.js.map +1 -0
  101. package/dist/kb-mcp/tools/kb-doc-lookup.d.ts +20 -0
  102. package/dist/kb-mcp/tools/kb-doc-lookup.d.ts.map +1 -0
  103. package/dist/kb-mcp/tools/kb-doc-lookup.js +92 -0
  104. package/dist/kb-mcp/tools/kb-doc-lookup.js.map +1 -0
  105. package/dist/kb-mcp/tools/kb-search.d.ts +23 -0
  106. package/dist/kb-mcp/tools/kb-search.d.ts.map +1 -0
  107. package/dist/kb-mcp/tools/kb-search.js +126 -0
  108. package/dist/kb-mcp/tools/kb-search.js.map +1 -0
  109. package/dist/knowledge-graph/import-resolver.d.ts +11 -86
  110. package/dist/knowledge-graph/import-resolver.d.ts.map +1 -1
  111. package/dist/knowledge-graph/import-resolver.js +10 -333
  112. package/dist/knowledge-graph/import-resolver.js.map +1 -1
  113. package/dist/knowledge-graph/incremental.d.ts +25 -4
  114. package/dist/knowledge-graph/incremental.d.ts.map +1 -1
  115. package/dist/knowledge-graph/incremental.js +37 -15
  116. package/dist/knowledge-graph/incremental.js.map +1 -1
  117. package/dist/knowledge-graph/index.d.ts +11 -0
  118. package/dist/knowledge-graph/index.d.ts.map +1 -1
  119. package/dist/knowledge-graph/index.js +43 -1
  120. package/dist/knowledge-graph/index.js.map +1 -1
  121. package/dist/knowledge-graph/module-derivation.d.ts +14 -0
  122. package/dist/knowledge-graph/module-derivation.d.ts.map +1 -1
  123. package/dist/knowledge-graph/module-derivation.js +44 -44
  124. package/dist/knowledge-graph/module-derivation.js.map +1 -1
  125. package/dist/knowledge-graph/persistence.d.ts +56 -16
  126. package/dist/knowledge-graph/persistence.d.ts.map +1 -1
  127. package/dist/knowledge-graph/persistence.js +86 -32
  128. package/dist/knowledge-graph/persistence.js.map +1 -1
  129. package/dist/knowledge-graph/query-helpers.d.ts +39 -12
  130. package/dist/knowledge-graph/query-helpers.d.ts.map +1 -1
  131. package/dist/knowledge-graph/query-helpers.js +199 -34
  132. package/dist/knowledge-graph/query-helpers.js.map +1 -1
  133. package/dist/knowledge-graph/relativize.d.ts +51 -0
  134. package/dist/knowledge-graph/relativize.d.ts.map +1 -0
  135. package/dist/knowledge-graph/relativize.js +104 -0
  136. package/dist/knowledge-graph/relativize.js.map +1 -0
  137. package/dist/mcp/agent-context-tools.d.ts +3 -54
  138. package/dist/mcp/agent-context-tools.d.ts.map +1 -1
  139. package/dist/mcp/agent-context-tools.js +141 -189
  140. package/dist/mcp/agent-context-tools.js.map +1 -1
  141. package/dist/mcp/file-nav-tools.d.ts +42 -0
  142. package/dist/mcp/file-nav-tools.d.ts.map +1 -0
  143. package/dist/mcp/file-nav-tools.js +318 -0
  144. package/dist/mcp/file-nav-tools.js.map +1 -0
  145. package/dist/mcp/graph-tools.d.ts +2 -0
  146. package/dist/mcp/graph-tools.d.ts.map +1 -1
  147. package/dist/mcp/graph-tools.js +134 -123
  148. package/dist/mcp/graph-tools.js.map +1 -1
  149. package/dist/mcp/lib/file-nav-helpers.d.ts +128 -0
  150. package/dist/mcp/lib/file-nav-helpers.d.ts.map +1 -0
  151. package/dist/mcp/lib/file-nav-helpers.js +305 -0
  152. package/dist/mcp/lib/file-nav-helpers.js.map +1 -0
  153. package/dist/mcp/lib/telemetry.d.ts +69 -0
  154. package/dist/mcp/lib/telemetry.d.ts.map +1 -0
  155. package/dist/mcp/lib/telemetry.js +113 -0
  156. package/dist/mcp/lib/telemetry.js.map +1 -0
  157. package/dist/mcp/lib/tool-response.d.ts +46 -0
  158. package/dist/mcp/lib/tool-response.d.ts.map +1 -0
  159. package/dist/mcp/lib/tool-response.js +83 -0
  160. package/dist/mcp/lib/tool-response.js.map +1 -0
  161. package/dist/mcp/server.d.ts +13 -2
  162. package/dist/mcp/server.d.ts.map +1 -1
  163. package/dist/mcp/server.js +209 -150
  164. package/dist/mcp/server.js.map +1 -1
  165. package/dist/models/code-skeleton.d.ts +4 -2
  166. package/dist/models/code-skeleton.d.ts.map +1 -1
  167. package/dist/models/code-skeleton.js +2 -1
  168. package/dist/models/code-skeleton.js.map +1 -1
  169. package/dist/models/module-spec.d.ts +18 -0
  170. package/dist/models/module-spec.d.ts.map +1 -1
  171. package/dist/models/module-spec.js +6 -0
  172. package/dist/models/module-spec.js.map +1 -1
  173. package/dist/panoramic/builders/doc-graph-builder.d.ts +13 -0
  174. package/dist/panoramic/builders/doc-graph-builder.d.ts.map +1 -1
  175. package/dist/panoramic/builders/doc-graph-builder.js +20 -0
  176. package/dist/panoramic/builders/doc-graph-builder.js.map +1 -1
  177. package/dist/panoramic/graph/graph-builder.d.ts +72 -2
  178. package/dist/panoramic/graph/graph-builder.d.ts.map +1 -1
  179. package/dist/panoramic/graph/graph-builder.js +257 -51
  180. package/dist/panoramic/graph/graph-builder.js.map +1 -1
  181. package/dist/panoramic/graph/graph-query.d.ts +16 -0
  182. package/dist/panoramic/graph/graph-query.d.ts.map +1 -1
  183. package/dist/panoramic/graph/graph-query.js +49 -1
  184. package/dist/panoramic/graph/graph-query.js.map +1 -1
  185. package/dist/panoramic/graph/index.d.ts +2 -1
  186. package/dist/panoramic/graph/index.d.ts.map +1 -1
  187. package/dist/panoramic/graph/index.js +1 -1
  188. package/dist/panoramic/graph/index.js.map +1 -1
  189. package/dist/panoramic/pipelines/adr-evidence-verifier.d.ts.map +1 -1
  190. package/dist/panoramic/pipelines/adr-evidence-verifier.js +1 -29
  191. package/dist/panoramic/pipelines/adr-evidence-verifier.js.map +1 -1
  192. package/dist/panoramic/query.d.ts +1 -0
  193. package/dist/panoramic/query.d.ts.map +1 -1
  194. package/dist/panoramic/query.js +5 -0
  195. package/dist/panoramic/query.js.map +1 -1
  196. package/dist/scaffold-kb/api-entities-serializer.d.ts +12 -0
  197. package/dist/scaffold-kb/api-entities-serializer.d.ts.map +1 -0
  198. package/dist/scaffold-kb/api-entities-serializer.js +144 -0
  199. package/dist/scaffold-kb/api-entities-serializer.js.map +1 -0
  200. package/dist/scaffold-kb/arbitration.d.ts +44 -0
  201. package/dist/scaffold-kb/arbitration.d.ts.map +1 -0
  202. package/dist/scaffold-kb/arbitration.js +144 -0
  203. package/dist/scaffold-kb/arbitration.js.map +1 -0
  204. package/dist/scaffold-kb/chunk-splitter.d.ts +20 -0
  205. package/dist/scaffold-kb/chunk-splitter.d.ts.map +1 -0
  206. package/dist/scaffold-kb/chunk-splitter.js +309 -0
  207. package/dist/scaffold-kb/chunk-splitter.js.map +1 -0
  208. package/dist/scaffold-kb/doc-graph-builder.d.ts +26 -0
  209. package/dist/scaffold-kb/doc-graph-builder.d.ts.map +1 -0
  210. package/dist/scaffold-kb/doc-graph-builder.js +72 -0
  211. package/dist/scaffold-kb/doc-graph-builder.js.map +1 -0
  212. package/dist/scaffold-kb/entity-extractor.d.ts +40 -0
  213. package/dist/scaffold-kb/entity-extractor.d.ts.map +1 -0
  214. package/dist/scaffold-kb/entity-extractor.js +0 -0
  215. package/dist/scaffold-kb/entity-extractor.js.map +1 -0
  216. package/dist/scaffold-kb/entity-heuristic.d.ts +14 -0
  217. package/dist/scaffold-kb/entity-heuristic.d.ts.map +1 -0
  218. package/dist/scaffold-kb/entity-heuristic.js +107 -0
  219. package/dist/scaffold-kb/entity-heuristic.js.map +1 -0
  220. package/dist/scaffold-kb/entity-matcher.d.ts +20 -0
  221. package/dist/scaffold-kb/entity-matcher.d.ts.map +1 -0
  222. package/dist/scaffold-kb/entity-matcher.js +53 -0
  223. package/dist/scaffold-kb/entity-matcher.js.map +1 -0
  224. package/dist/scaffold-kb/entity-util.d.ts +11 -0
  225. package/dist/scaffold-kb/entity-util.d.ts.map +1 -0
  226. package/dist/scaffold-kb/entity-util.js +23 -0
  227. package/dist/scaffold-kb/entity-util.js.map +1 -0
  228. package/dist/scaffold-kb/evidence-envelope.d.ts +17 -0
  229. package/dist/scaffold-kb/evidence-envelope.d.ts.map +1 -0
  230. package/dist/scaffold-kb/evidence-envelope.js +32 -0
  231. package/dist/scaffold-kb/evidence-envelope.js.map +1 -0
  232. package/dist/scaffold-kb/index.d.ts +20 -0
  233. package/dist/scaffold-kb/index.d.ts.map +1 -0
  234. package/dist/scaffold-kb/index.js +102 -0
  235. package/dist/scaffold-kb/index.js.map +1 -0
  236. package/dist/scaffold-kb/ingest/ingest-core.d.ts +47 -0
  237. package/dist/scaffold-kb/ingest/ingest-core.d.ts.map +1 -0
  238. package/dist/scaffold-kb/ingest/ingest-core.js +231 -0
  239. package/dist/scaffold-kb/ingest/ingest-core.js.map +1 -0
  240. package/dist/scaffold-kb/ingest/office-parser.d.ts +33 -0
  241. package/dist/scaffold-kb/ingest/office-parser.d.ts.map +1 -0
  242. package/dist/scaffold-kb/ingest/office-parser.js +187 -0
  243. package/dist/scaffold-kb/ingest/office-parser.js.map +1 -0
  244. package/dist/scaffold-kb/ingest/url-fetcher.d.ts +43 -0
  245. package/dist/scaffold-kb/ingest/url-fetcher.d.ts.map +1 -0
  246. package/dist/scaffold-kb/ingest/url-fetcher.js +245 -0
  247. package/dist/scaffold-kb/ingest/url-fetcher.js.map +1 -0
  248. package/dist/scaffold-kb/ingester.d.ts +38 -0
  249. package/dist/scaffold-kb/ingester.d.ts.map +1 -0
  250. package/dist/scaffold-kb/ingester.js +261 -0
  251. package/dist/scaffold-kb/ingester.js.map +1 -0
  252. package/dist/scaffold-kb/injection-format.d.ts +25 -0
  253. package/dist/scaffold-kb/injection-format.d.ts.map +1 -0
  254. package/dist/scaffold-kb/injection-format.js +55 -0
  255. package/dist/scaffold-kb/injection-format.js.map +1 -0
  256. package/dist/scaffold-kb/kb-writer.d.ts +13 -0
  257. package/dist/scaffold-kb/kb-writer.d.ts.map +1 -0
  258. package/dist/scaffold-kb/kb-writer.js +69 -0
  259. package/dist/scaffold-kb/kb-writer.js.map +1 -0
  260. package/dist/scaffold-kb/keyword-extract.d.ts +16 -0
  261. package/dist/scaffold-kb/keyword-extract.d.ts.map +1 -0
  262. package/dist/scaffold-kb/keyword-extract.js +49 -0
  263. package/dist/scaffold-kb/keyword-extract.js.map +1 -0
  264. package/dist/scaffold-kb/query-sanitizer.d.ts +28 -0
  265. package/dist/scaffold-kb/query-sanitizer.d.ts.map +1 -0
  266. package/dist/scaffold-kb/query-sanitizer.js +38 -0
  267. package/dist/scaffold-kb/query-sanitizer.js.map +1 -0
  268. package/dist/scaffold-kb/recall-eval.d.ts +48 -0
  269. package/dist/scaffold-kb/recall-eval.d.ts.map +1 -0
  270. package/dist/scaffold-kb/recall-eval.js +55 -0
  271. package/dist/scaffold-kb/recall-eval.js.map +1 -0
  272. package/dist/scaffold-kb/schema-compat.d.ts +22 -0
  273. package/dist/scaffold-kb/schema-compat.d.ts.map +1 -0
  274. package/dist/scaffold-kb/schema-compat.js +32 -0
  275. package/dist/scaffold-kb/schema-compat.js.map +1 -0
  276. package/dist/scaffold-kb/search-core.d.ts +42 -0
  277. package/dist/scaffold-kb/search-core.d.ts.map +1 -0
  278. package/dist/scaffold-kb/search-core.js +89 -0
  279. package/dist/scaffold-kb/search-core.js.map +1 -0
  280. package/dist/scaffold-kb/sqlite-engine.d.ts +58 -0
  281. package/dist/scaffold-kb/sqlite-engine.d.ts.map +1 -0
  282. package/dist/scaffold-kb/sqlite-engine.js +61 -0
  283. package/dist/scaffold-kb/sqlite-engine.js.map +1 -0
  284. package/dist/scaffold-kb/sqlite-writer.d.ts +18 -0
  285. package/dist/scaffold-kb/sqlite-writer.d.ts.map +1 -0
  286. package/dist/scaffold-kb/sqlite-writer.js +76 -0
  287. package/dist/scaffold-kb/sqlite-writer.js.map +1 -0
  288. package/dist/scaffold-kb/tokenizer.d.ts +26 -0
  289. package/dist/scaffold-kb/tokenizer.d.ts.map +1 -0
  290. package/dist/scaffold-kb/tokenizer.js +97 -0
  291. package/dist/scaffold-kb/tokenizer.js.map +1 -0
  292. package/dist/scaffold-kb/types.d.ts +157 -0
  293. package/dist/scaffold-kb/types.d.ts.map +1 -0
  294. package/dist/scaffold-kb/types.js +5 -0
  295. package/dist/scaffold-kb/types.js.map +1 -0
  296. package/dist/utils/file-scanner.d.ts +15 -0
  297. package/dist/utils/file-scanner.d.ts.map +1 -1
  298. package/dist/utils/file-scanner.js +17 -0
  299. package/dist/utils/file-scanner.js.map +1 -1
  300. package/dist/utils/string-distance.d.ts +20 -0
  301. package/dist/utils/string-distance.d.ts.map +1 -0
  302. package/dist/utils/string-distance.js +38 -0
  303. package/dist/utils/string-distance.js.map +1 -0
  304. package/package.json +11 -2
  305. package/plugins/demo-kb-en/.claude-plugin/plugin.json +11 -0
  306. package/plugins/demo-kb-en/.mcp.json +8 -0
  307. package/plugins/demo-kb-en/FIXTURE.json +23 -0
  308. package/plugins/demo-kb-en/FIXTURE.md +53 -0
  309. package/plugins/demo-kb-en/ingest-samples/meeting-notes.md +13 -0
  310. package/plugins/demo-kb-en/kb/api-entities.json +3445 -0
  311. package/plugins/demo-kb-en/kb/chunks.sqlite +0 -0
  312. package/plugins/demo-kb-en/kb/doc-graph.json +113 -0
  313. package/plugins/demo-kb-zh/.claude-plugin/plugin.json +11 -0
  314. package/plugins/demo-kb-zh/.mcp.json +8 -0
  315. package/plugins/demo-kb-zh/FIXTURE.json +23 -0
  316. package/plugins/demo-kb-zh/FIXTURE.md +63 -0
  317. package/plugins/demo-kb-zh/ingest-samples/meeting-notes.md +13 -0
  318. package/plugins/demo-kb-zh/kb/api-entities.json +990 -0
  319. package/plugins/demo-kb-zh/kb/chunks.sqlite +0 -0
  320. package/plugins/demo-kb-zh/kb/doc-graph.json +113 -0
  321. package/plugins/spec-driver/.claude-plugin/plugin.json +1 -1
  322. package/plugins/spec-driver/README.md +33 -1
  323. package/plugins/spec-driver/agents/verify.md +88 -0
  324. package/plugins/spec-driver/config/orchestration.yaml +39 -31
  325. package/plugins/spec-driver/contracts/orchestration-overrides-contract.yaml +27 -0
  326. package/plugins/spec-driver/contracts/orchestration-schema.mjs +272 -227
  327. package/plugins/spec-driver/contracts/wrapper-source-of-truth.yaml +3 -0
  328. package/plugins/spec-driver/hooks/hooks.json +9 -0
  329. package/plugins/spec-driver/hooks/stop-fix-compliance-check.sh +36 -0
  330. package/plugins/spec-driver/lib/delegation-contract.mjs +84 -0
  331. package/plugins/spec-driver/lib/orchestration-resolver.mjs +49 -0
  332. package/plugins/spec-driver/lib/orchestrator.mjs +29 -1
  333. package/plugins/spec-driver/scripts/codex-skills.sh +28 -28
  334. package/plugins/spec-driver/scripts/dev/spike-fix-compliance-e2e.mjs +130 -0
  335. package/plugins/spec-driver/scripts/fix-compliance-judge.mjs +415 -0
  336. package/plugins/spec-driver/scripts/generate-adoption-insights.mjs +48 -1
  337. package/plugins/spec-driver/scripts/goal-loop-cli.mjs +307 -0
  338. package/plugins/spec-driver/scripts/init-project.sh +56 -0
  339. package/plugins/spec-driver/scripts/kb-prequery.mjs +125 -0
  340. package/plugins/spec-driver/scripts/lib/config-schema.mjs +200 -94
  341. package/plugins/spec-driver/scripts/lib/ensure-gitignore.sh +277 -0
  342. package/plugins/spec-driver/scripts/lib/extract-wrapper-body.mjs +136 -0
  343. package/plugins/spec-driver/scripts/lib/fix-compliance-core.mjs +434 -0
  344. package/plugins/spec-driver/scripts/lib/fix-compliance-io.mjs +335 -0
  345. package/plugins/spec-driver/scripts/lib/goal-loop-core.mjs +783 -0
  346. package/plugins/spec-driver/scripts/lib/init-project-output.sh +28 -0
  347. package/plugins/spec-driver/scripts/lib/load-zod.mjs +62 -0
  348. package/plugins/spec-driver/scripts/lib/project-profile-resolver.mjs +141 -49
  349. package/plugins/spec-driver/scripts/lib/project-profile-schema.mjs +88 -54
  350. package/plugins/spec-driver/scripts/postinstall.sh +13 -1
  351. package/plugins/spec-driver/scripts/record-workflow-run.mjs +75 -0
  352. package/plugins/spec-driver/scripts/sync-delegation-contract.mjs +187 -0
  353. package/plugins/spec-driver/scripts/validate-orchestrator-models.mjs +156 -0
  354. package/plugins/spec-driver/scripts/validate-wrapper-sources.mjs +46 -1
  355. package/plugins/spec-driver/skills/spec-driver-feature/SKILL.md +292 -0
  356. package/plugins/spec-driver/skills/spec-driver-fix/SKILL.md +87 -8
  357. package/plugins/spec-driver/skills/spec-driver-implement/SKILL.md +8 -0
  358. package/plugins/spec-driver/skills/spec-driver-resume/SKILL.md +12 -1
  359. package/plugins/spec-driver/skills/spec-driver-story/SKILL.md +20 -0
  360. package/plugins/spec-driver/templates/delegation-contract.md +27 -0
  361. package/plugins/spec-driver/templates/goal-loop-override-template.yaml +284 -0
  362. package/plugins/spec-driver/templates/specify-base/project-context-template.yaml +10 -0
  363. package/plugins/spectra/.claude-plugin/plugin.json +1 -1
  364. package/plugins/spectra/README.md +1 -1
  365. package/templates/module-spec.hbs +1 -0
@@ -1,35 +1,58 @@
1
1
  /**
2
- * import-resolver.ts — TS/JS import specifier 解析(Feature 156 W1.0 / CRITICAL-1 关闭)
2
+ * import-resolver.ts — TS/JS import specifier 单一权威解析(Feature 181 收口)
3
3
  *
4
- * 背景:
5
- * ast-analyzer.ts:376tree-sitter-fallback.ts / typescript-mapper.ts 在提取 import 时
6
- * `resolvedPath` 置为 null("不解析路径,性能优化")。删除 dependency-cruiser 后,
7
- * knowledge-graph/index.ts deriveImportEdges 跳过 `resolvedPath=null` import,
8
- * 导致 TS/JS depends-on 边数为 0
9
- *
10
- * 本模块提供两个核心能力:
11
- * 1. resolveTsJsImport specifier 绝对路径(无法解析返回 null)
12
- * - 覆盖 4 import:static / dynamic / commonjs-require / type-only(路径解析逻辑相同)
13
- * - 支持相对路径、扩展名补全、index.{ts,js} fallback、tsconfig path alias
14
- * - node_modules / 外部包:返回 null(不解析外部依赖)
15
- * 2. detectImportType — 从 ts-morph 节点派生 ImportType 字面量
16
- * - 用于 CodeSkeleton.imports[].importType + ModuleEdge.importType
4
+ * 背景(Feature 181 单一权威收口):
5
+ * 历史上 `core/import-resolver.ts``knowledge-graph/import-resolver.ts` 各有一份
6
+ * `resolveTsJsImport`,行为分叉(core 返回绝对路径无 projectRoot 守卫 + ESM ext map;
7
+ * kg 返回相对 POSIX + kind + isInsideProjectRoot 守卫)。两者经 AST 路径
8
+ * (ast-analyzer / tree-sitter-*)与 batch 路径(collect / module-derivation)都进 graph.json
9
+ * 本模块收口为唯一权威实现,合并两侧能力:
10
+ * - 相对解析:ESM ext map(`./foo.js`→`foo.ts`)+ 直接命中 + .mjs/.cjs(源自 core)
11
+ * - alias/baseUrl:tsconfig paths(最长前缀 + 多候选 + baseUrl 叠加,TS 官方语义)
12
+ * - projectRoot 边界守卫(防图污染,源自 kg;relative / disk-absolute 分支启用)
13
+ * - kind 分类 + external/unresolved 区分 + .json/.d.ts external
17
14
  *
18
15
  * 设计约束:
19
- * - 不引入新 npm 依赖(仅用 node:fs / node:path
20
- * - 不抛异常(无法解析返回 null + 静默忽略)
21
- * - 跨平台:path.resolve / path.posix 兼容 win32 + posix
16
+ * - node:fs / node:path(+ typescript 仅用于 tsconfig 解析,含 extends 链)
17
+ * - 不抛异常(无法解析返回 { resolvedPath: null, kind: 'unresolved' })
18
+ * - 跨平台:resolvedPath 输出统一为相对 projectRoot POSIX 路径;
19
+ * projectRoot 为空(独立 AST 调用)时返回绝对路径且跳过守卫(保持历史 core 行为)
20
+ *
21
+ * 注:Python import 解析(resolvePythonImport)不在本次收口范围,仍在
22
+ * knowledge-graph/import-resolver.ts;其依赖的 ResolveResult / toPosix / isInsideProjectRoot
23
+ * 由本模块导出供其下行 import(knowledge-graph → core,层级方向干净)。
22
24
  */
23
25
  import * as fs from 'node:fs';
24
26
  import * as path from 'node:path';
25
- import { Node, SyntaxKind } from 'ts-morph';
26
- const DEFAULT_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'];
27
- const DEFAULT_INDEX_FILES = ['index.ts', 'index.tsx', 'index.js', 'index.jsx'];
27
+ // 复用 ts-morph 再导出的 typescript 命名空间(避免新增对 'typescript' 的直接运行时依赖;
28
+ // ts-morph 已是本仓运行时依赖)
29
+ import { ts } from 'ts-morph';
30
+ import { createLogger } from '../panoramic/utils/logger.js';
31
+ // F183 修复 2:buildTsConfigContext 失败时不再静默——warn 出 configPath + error 摘要。
32
+ const logger = createLogger('import-resolver');
33
+ // 仅缓存已 warn 的 configPath 做 emission 限频(monorepo 同一损坏 tsconfig 会被每个
34
+ // import-resolve 循环触发,造成日志洪泛)。注意:仅限频 warn,绝不跳过解析——
35
+ // buildTsConfigContext 始终尝试解析,失败仍 return null。
36
+ const warnedConfigPaths = new Set();
37
+ /** 同一 configPath 仅发一次 warn(emission 限频,不影响解析)。 */
38
+ function warnTsConfigFailureOnce(configPath, errorSummary) {
39
+ if (warnedConfigPaths.has(configPath))
40
+ return;
41
+ logger.warn(`[import-resolver] buildTsConfigContext 失败(${configPath}):${errorSummary}`);
42
+ warnedConfigPaths.add(configPath);
43
+ }
44
+ // ───────────────────────────────────────────────────────────
45
+ // 常量
46
+ // ───────────────────────────────────────────────────────────
47
+ /** TS/JS 候补扩展名顺序(含 .mjs/.cjs,源自历史 core 实现) */
48
+ const TS_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'];
49
+ /** index 文件名顺序 */
50
+ const TS_INDEX_FILES = ['index.ts', 'index.tsx', 'index.js', 'index.jsx'];
51
+ /** alias-like 前缀列表(C-2):以这些前缀开头的 moduleSpec 无 paths 命中时返回 unresolved */
52
+ const ALIAS_PREFIXES = ['~/', '#/', '$/'];
28
53
  /**
29
- * Feature 156 W1.0 v2 / CRIT-1:TypeScript ESM 惯例下,`import './foo.js'` 实际指向 `./foo.ts`。
30
- *
31
- * 当 specifier 以 JS 扩展名结尾时,先尝试对应的 TS 扩展名候选;命中则返回,否则 fallback
32
- * 到原扩展名继续走标准解析流程(保持非 TS 项目的兼容性)。
54
+ * TypeScript ESM 惯例:`import './foo.js'` 实际指向 `./foo.ts`(Feature 156 CRIT-1)。
55
+ * specifier 以 JS 扩展名结尾时,先尝试对应 TS 扩展名候选;命中即返回,否则 fallback 标准解析。
33
56
  */
34
57
  const ESM_TS_EXT_MAP = {
35
58
  '.js': ['.ts', '.tsx'],
@@ -38,107 +61,65 @@ const ESM_TS_EXT_MAP = {
38
61
  '.cjs': ['.cts'],
39
62
  };
40
63
  // ───────────────────────────────────────────────────────────
41
- // resolveTsJsImport specifier 绝对路径
64
+ // 共享辅助(导出供 knowledge-graph Python resolver 下行复用)
42
65
  // ───────────────────────────────────────────────────────────
66
+ /** 将路径转换为 POSIX 格式(确保跨平台 resolvedPath 始终用 '/' 分隔符)。 */
67
+ export function toPosix(p) {
68
+ return p.split(path.sep).join('/');
69
+ }
43
70
  /**
44
- * TS/JS import specifier 解析为绝对文件路径。
71
+ * 判断 candidate 路径是否严格在 projectRoot 子树内(C-5 + N-2)。
45
72
  *
46
- * 解析顺序:
47
- * 1. node 内置模块(如 'fs')/ npm 包(不带相对前缀且无 alias 命中)→ null
48
- * 2. tsconfig path alias 命中 → 替换前缀后按相对路径流程解析
49
- * 3. 相对路径(`./xxx` / `../xxx` / `/xxx`)→ 基于 fromFile 拼接
50
- *
51
- * 文件存在性检查:
52
- * - 直接命中(specifier 已含 .ts/.js 等扩展名)
53
- * - 扩展名补全(依次尝试 options.extensions)
54
- * - 目录 + index 文件 fallback(如果 specifier 指向目录)
55
- *
56
- * 失败时静默返回 null,不抛异常(FR-28 best-effort 语义)。
57
- *
58
- * @param specifier - import 字符串字面量(如 './foo')
59
- * @param fromFile - 发起 import 的源文件绝对路径
60
- * @param projectRoot - 项目根目录(用于 alias 相对路径解析;可为空字符串,alias 失效)
61
- * @param options - 扩展名 / index 文件 / alias 配置
62
- * @returns 解析到的绝对路径;外部模块或解析失败返回 null
73
+ * 用 path.relative 逐 component 检查第一段是否为 '..',避免字典序误判(/proj vs /projection)。
74
+ * candidate === projectRoot 返回 false(projectRoot 自身不算"在子树内")。
63
75
  */
64
- export function resolveTsJsImport(specifier, fromFile, projectRoot, options) {
65
- if (!specifier || typeof specifier !== 'string')
66
- return null;
67
- const extensions = options?.extensions ?? DEFAULT_EXTENSIONS;
68
- const indexFiles = options?.indexFiles ?? DEFAULT_INDEX_FILES;
69
- // 1. node 内置模块('fs', 'path', 'node:fs')→ null(不解析外部依赖)
70
- if (specifier.startsWith('node:'))
71
- return null;
72
- // 2. 尝试 tsconfig path alias(多候选最长前缀匹配,CRIT-2 v2)
73
- const aliasCandidates = resolveAliasCandidates(specifier, projectRoot, options?.pathAliases);
74
- for (const cand of aliasCandidates) {
75
- const hit = tryFilePathVariants(cand, extensions, indexFiles);
76
- if (hit)
77
- return hit;
78
- }
79
- // 3. 相对路径前缀('.', '..', '/')才进入文件系统解析
80
- if (specifier.startsWith('.') || specifier.startsWith('/')) {
81
- const fromDir = path.dirname(path.resolve(fromFile));
82
- const baseSpecifier = path.resolve(fromDir, specifier);
83
- return tryFilePathVariants(baseSpecifier, extensions, indexFiles);
84
- }
85
- // 4. baseUrl fallback(CRIT-2 v2):未命中 alias / 非相对路径,
86
- // 若 baseUrl 提供,则按非相对 module 解析(baseUrl + specifier)
87
- if (options?.baseUrl) {
88
- const baseRel = path.resolve(options.baseUrl, specifier);
89
- const hit = tryFilePathVariants(baseRel, extensions, indexFiles);
90
- if (hit)
91
- return hit;
92
- }
93
- // 5. 不带相对前缀、无 alias / baseUrl 命中:视为 npm 包 / 外部模块 → null
94
- return null;
76
+ export function isInsideProjectRoot(candidate, projectRoot) {
77
+ const rel = path.relative(projectRoot, candidate);
78
+ if (rel.length === 0)
79
+ return false; // candidate === projectRoot
80
+ if (path.isAbsolute(rel))
81
+ return false; // 跨盘符(Windows)
82
+ const firstSeg = rel.split(path.sep)[0];
83
+ if (firstSeg === '..')
84
+ return false;
85
+ return true;
95
86
  }
96
87
  /**
97
- * 尝试 alias 替换,返回所有候选绝对路径列表(CRIT-2 v2)。
98
- *
99
- * 改进:
100
- * 1. 支持单值与多候选数组(`Record<string, string | readonly string[]>`)
101
- * 2. 最长前缀匹配(`@app/utils/foo` 命中 `@app/utils/*` 优先于 `@app/*`)
102
- * 3. 一个 alias 可对应多个目标,全部作为候选返回(调用方逐个 tryFilePathVariants)
103
- *
104
- * @returns 候选绝对路径列表(按优先级排序:最长前缀优先 + 配置顺序);未命中返回空数组
88
+ * 判断路径是否指向非源文件目标(W-1)。
89
+ * .json 和 .d.ts 文件不进入 callSites graph,返回 external。
105
90
  */
106
- function resolveAliasCandidates(specifier, projectRoot, pathAliases) {
107
- if (!pathAliases || !projectRoot)
108
- return [];
109
- // 收集所有命中的 alias,按 prefix 长度降序(最长前缀优先)
110
- const matched = [];
111
- for (const [aliasPattern, targetValue] of Object.entries(pathAliases)) {
112
- const aliasPrefix = aliasPattern.replace(/\*$/, '');
113
- if (!specifier.startsWith(aliasPrefix))
114
- continue;
115
- const remainder = specifier.slice(aliasPrefix.length);
116
- const targets = Array.isArray(targetValue) ? targetValue : [targetValue];
117
- matched.push({ prefixLen: aliasPrefix.length, remainder, targets });
91
+ export function isNonSourceTarget(p) {
92
+ return p.endsWith('.json') || p.endsWith('.d.ts');
93
+ }
94
+ // ───────────────────────────────────────────────────────────
95
+ // 内部:文件候选尝试(合并 core ESM ext map + 直接命中 + 扩展名 + index)
96
+ // ───────────────────────────────────────────────────────────
97
+ function existsAndIsFile(p) {
98
+ try {
99
+ return fs.statSync(p).isFile();
118
100
  }
119
- if (matched.length === 0)
120
- return [];
121
- matched.sort((a, b) => b.prefixLen - a.prefixLen);
122
- const candidates = [];
123
- for (const m of matched) {
124
- for (const target of m.targets) {
125
- const targetPrefix = target.replace(/\*$/, '');
126
- const joined = path.posix.join(targetPrefix, m.remainder);
127
- // target 可能已是绝对路径(ts.parseJsonConfigFileContent 解析后);保留绝对路径不再 join projectRoot
128
- candidates.push(path.isAbsolute(joined) ? joined : path.resolve(projectRoot, joined));
129
- }
101
+ catch {
102
+ return false;
103
+ }
104
+ }
105
+ function existsAndIsDir(p) {
106
+ try {
107
+ return fs.statSync(p).isDirectory();
108
+ }
109
+ catch {
110
+ return false;
130
111
  }
131
- return candidates;
132
112
  }
133
113
  /**
134
- * 给定一个 base 路径,依次尝试:
135
- * 1. base 直接存在(含扩展名)
136
- * 2. base + 扩展名(.ts, .tsx, .js, ...)
137
- * 3. base / index.ts、base / index.js 等
114
+ * 给定 base 路径,依次尝试(顺序严格复刻历史 core,护 graph.json byte-identical):
115
+ * 1. ESM TS ext map(若 base 以 JS 扩展名结尾,先试 .ts/.tsx 等 TS 候选)
116
+ * 2. base 直接命中(已含扩展名)
117
+ * 3. base + 扩展名(.ts .cjs)
118
+ * 4. base/index.{ts,tsx,js,jsx}
119
+ * @returns 命中的绝对路径;否则 null
138
120
  */
139
- function tryFilePathVariants(base, extensions, indexFiles) {
140
- // CRIT-1:TS ESM 惯例 base 以 JS 扩展名结尾,先尝试 TS 候选(如 foo.js → foo.ts)
141
- // 命中则直接返回;不命中再 fallback 到原 base 走后续标准解析流程
121
+ function tryFilePathVariants(base) {
122
+ // 1. ESM TS ext map(如 foo.js → foo.ts)
142
123
  const sourceExt = path.extname(base);
143
124
  const tsCandidatesExts = ESM_TS_EXT_MAP[sourceExt];
144
125
  if (tsCandidatesExts) {
@@ -148,20 +129,19 @@ function tryFilePathVariants(base, extensions, indexFiles) {
148
129
  if (existsAndIsFile(tsCandidate))
149
130
  return tsCandidate;
150
131
  }
151
- // TS 候选未命中:继续 fallback 到下方标准解析(直接命中 / 扩展名补全 / index)
152
132
  }
153
- // 1. 直接命中
133
+ // 2. 直接命中
154
134
  if (existsAndIsFile(base))
155
135
  return base;
156
- // 2. 扩展名补全
157
- for (const ext of extensions) {
136
+ // 3. 扩展名补全
137
+ for (const ext of TS_EXTENSIONS) {
158
138
  const candidate = base + ext;
159
139
  if (existsAndIsFile(candidate))
160
140
  return candidate;
161
141
  }
162
- // 3. 目录 + index 文件
142
+ // 4. 目录 + index 文件
163
143
  if (existsAndIsDir(base)) {
164
- for (const idxFile of indexFiles) {
144
+ for (const idxFile of TS_INDEX_FILES) {
165
145
  const candidate = path.join(base, idxFile);
166
146
  if (existsAndIsFile(candidate))
167
147
  return candidate;
@@ -169,105 +149,266 @@ function tryFilePathVariants(base, extensions, indexFiles) {
169
149
  }
170
150
  return null;
171
151
  }
172
- function existsAndIsFile(p) {
173
- try {
174
- const st = fs.statSync(p);
175
- return st.isFile();
152
+ /**
153
+ * 把命中绝对路径归一为 ResolveResult。
154
+ * projectRoot 为真:相对 POSIX;projectRoot 为空:保留绝对路径(历史 core 独立调用行为)。
155
+ */
156
+ function makeHit(absHit, projectRoot, kind) {
157
+ return {
158
+ resolvedPath: projectRoot ? toPosix(path.relative(projectRoot, absHit)) : absHit,
159
+ kind,
160
+ };
161
+ }
162
+ // ───────────────────────────────────────────────────────────
163
+ // Public API — resolveTsJsImport(单一权威)
164
+ // ───────────────────────────────────────────────────────────
165
+ /**
166
+ * 解析 TypeScript/JavaScript import 路径(单一权威实现,Feature 181)。
167
+ *
168
+ * 解析顺序:
169
+ * 1. node: 内置 → external
170
+ * 2. 磁盘绝对路径('/' 前缀)→ 文件候选 + projectRoot 守卫
171
+ * 3. 相对路径('./' '../')→ 文件候选 + projectRoot 守卫;.json/.d.ts → external
172
+ * 4. 非相对:tsconfig paths alias(最长前缀 + 多候选 + baseUrl 叠加)→ baseUrl 解析 → external/unresolved
173
+ *
174
+ * 守卫策略(逐分支复刻历史 kg):relative / disk-absolute 分支启用 isInsideProjectRoot
175
+ * (仅 projectRoot 为真时);paths-alias / baseUrl 分支不加守卫(保持 alias 语义)。
176
+ *
177
+ * @param moduleSpec - import 说明符(如 './engine'、'~/utils'、'express')
178
+ * @param callerFile - 调用方文件的绝对路径
179
+ * @param projectRoot - 项目根目录绝对路径(空字符串:返回绝对路径 + 跳过守卫)
180
+ * @param tsConfigContext - tsconfig 解析上下文(null/undefined:仅走相对/绝对路径)
181
+ * @returns ResolveResult(失败返回 { resolvedPath: null, kind: 'unresolved' },不抛异常)
182
+ */
183
+ export function resolveTsJsImport(moduleSpec, callerFile, projectRoot, tsConfigContext) {
184
+ if (!moduleSpec || typeof moduleSpec !== 'string') {
185
+ return { resolvedPath: null, kind: 'unresolved' };
176
186
  }
177
- catch {
178
- return false;
187
+ // 1. node 内置模块 → external(不解析外部依赖)
188
+ if (moduleSpec.startsWith('node:')) {
189
+ return { resolvedPath: null, kind: 'external' };
179
190
  }
180
- }
181
- function existsAndIsDir(p) {
182
- try {
183
- const st = fs.statSync(p);
184
- return st.isDirectory();
191
+ // 2. 磁盘绝对路径(罕见)
192
+ if (moduleSpec.startsWith('/')) {
193
+ const hit = tryFilePathVariants(moduleSpec);
194
+ if (hit) {
195
+ // 守卫:候选必须在 projectRoot 内(仅 projectRoot 为真时启用)
196
+ if (projectRoot && !isInsideProjectRoot(hit, projectRoot)) {
197
+ return { resolvedPath: null, kind: 'unresolved' };
198
+ }
199
+ return makeHit(hit, projectRoot, 'absolute');
200
+ }
201
+ return { resolvedPath: null, kind: 'unresolved' };
185
202
  }
186
- catch {
187
- return false;
203
+ // 3. 相对路径(./ 或 ../)
204
+ if (moduleSpec.startsWith('./') || moduleSpec.startsWith('../') || moduleSpec.startsWith('.')) {
205
+ // W-1:moduleSpec 自身已带 .json / .d.ts 后缀 → external
206
+ if (isNonSourceTarget(moduleSpec)) {
207
+ return { resolvedPath: null, kind: 'external' };
208
+ }
209
+ const base = path.resolve(path.dirname(callerFile), moduleSpec);
210
+ const hit = tryFilePathVariants(base);
211
+ if (hit) {
212
+ if (projectRoot && !isInsideProjectRoot(hit, projectRoot)) {
213
+ return { resolvedPath: null, kind: 'unresolved' };
214
+ }
215
+ // 命中 .json / .d.ts(如 base 无扩展名却命中同名 .d.ts)双保险 external
216
+ if (isNonSourceTarget(hit)) {
217
+ return { resolvedPath: null, kind: 'external' };
218
+ }
219
+ return makeHit(hit, projectRoot, 'relative');
220
+ }
221
+ // 候补失败但 base 本身指向 .json / .d.ts → external
222
+ if (isNonSourceTarget(base)) {
223
+ return { resolvedPath: null, kind: 'external' };
224
+ }
225
+ return { resolvedPath: null, kind: 'unresolved' };
226
+ }
227
+ // 4. 非相对路径:先 paths alias,再 baseUrl,再判定 external/unresolved
228
+ if (tsConfigContext) {
229
+ const aliasHit = resolvePathsAlias(moduleSpec, projectRoot, tsConfigContext);
230
+ if (aliasHit)
231
+ return aliasHit;
232
+ // baseUrl 解析(baseUrl 为字符串时)
233
+ if (typeof tsConfigContext.baseUrl === 'string') {
234
+ const absBase = path.resolve(tsConfigContext.configDir, tsConfigContext.baseUrl, moduleSpec);
235
+ const hit = tryFilePathVariants(absBase);
236
+ if (hit) {
237
+ return makeHit(hit, projectRoot, 'absolute');
238
+ }
239
+ }
240
+ }
241
+ // C-2:区分 alias-like(unresolved)与 bare npm package(external)
242
+ if (ALIAS_PREFIXES.some((p) => moduleSpec.startsWith(p)) || moduleSpec.startsWith('@/')) {
243
+ return { resolvedPath: null, kind: 'unresolved' };
244
+ }
245
+ // 合法 npm 包判定
246
+ const firstSeg = moduleSpec.split('/')[0] ?? '';
247
+ if (firstSeg.startsWith('@')) {
248
+ // scoped 包形如 @org/lib
249
+ return { resolvedPath: null, kind: 'external' };
250
+ }
251
+ if (/^[a-z0-9][a-z0-9-_.]*$/.test(firstSeg)) {
252
+ return { resolvedPath: null, kind: 'external' };
188
253
  }
254
+ // 含 '/' 但首段不是合法 npm 包名 → alias-like 配置缺失
255
+ return { resolvedPath: null, kind: 'unresolved' };
256
+ }
257
+ /**
258
+ * AST 消费方便捷封装:解析并归一为**绝对路径**。
259
+ *
260
+ * ast-analyzer / tree-sitter-analyzer / tree-sitter-fallback 三处历史上把
261
+ * resolvedPath 以绝对路径写入 CodeSkeleton.imports[](module-derivation.normalizeSkeletonPaths
262
+ * 期望绝对输入再相对化;collect EC-10 亦绝对)。本封装保持该形态:
263
+ * - projectRoot 为真:把权威 resolver 的相对 POSIX 结果 path.resolve 回绝对
264
+ * - projectRoot 为空:权威 resolver 已返回绝对路径,原样返回
265
+ * external/unresolved(resolvedPath=null)统一返回 null(与历史 core string|null 语义一致)。
266
+ */
267
+ export function resolveTsJsImportToAbsolute(moduleSpec, callerFile, projectRoot, tsConfigContext) {
268
+ const r = resolveTsJsImport(moduleSpec, callerFile, projectRoot, tsConfigContext);
269
+ if (!r.resolvedPath)
270
+ return null;
271
+ return path.isAbsolute(r.resolvedPath)
272
+ ? r.resolvedPath
273
+ : path.resolve(projectRoot, r.resolvedPath);
189
274
  }
190
- // ───────────────────────────────────────────────────────────
191
- // detectImportType — 从 ts-morph 节点派生 ImportType
192
- // ───────────────────────────────────────────────────────────
193
275
  /**
194
- * ts-morph 节点派生 ImportType 字面量(FR-28 / plan §2.5)。
276
+ * tsconfig paths alias 解析(最长前缀 + 多候选 + baseUrl 叠加,TS 官方语义)。
195
277
  *
196
- * 判断规则:
197
- * 1. 'type-only':ImportDeclaration isTypeOnly()=true
198
- * 2. 'dynamic':CallExpression callee 为 import 关键字(SyntaxKind.ImportKeyword
199
- * 3. 'commonjs-require'CallExpression callee 文本 = "require"
200
- * 4. 'static':默认(标准 ImportDeclaration
278
+ * 合并历史两侧:
279
+ * - 最长前缀匹配(core:`@app/utils/*` 优先于 `@app/*`;精确 key 因 prefixLen 最长天然居首)
280
+ * - 多候选数组逐个尝试(core + kg
281
+ * - replacement 叠加 baseUrlpath.resolve(configDir, baseUrl ?? '.', replacement)(kg C-3)
282
+ * - 文件候选用统一 tryFilePathVariants(含 ESM ext map / .mjs.cjs / index
201
283
  *
202
- * 注:tree-sitter / 正则降级路径不调用此函数,由调用点直接根据 query 节点类型映射
203
- * (tree-sitter typescript-mapper 仅处理 import_statement,importType 始终为 'static' 或 'type-only',
204
- * 由 isTypeOnly 字段派生)。
284
+ * @returns 命中的 ResolveResult(kind='paths-alias');未命中返回 null
205
285
  */
206
- export function detectImportType(node) {
207
- if (Node.isImportDeclaration(node)) {
208
- return detectImportDeclarationType(node);
286
+ function resolvePathsAlias(moduleSpec, projectRoot, ctx) {
287
+ // 收集所有命中 alias,排序规则(TS 官方语义):
288
+ // 1. 最长前缀优先(`@app/utils/*` 胜过 `@app/*`)
289
+ // 2. 同前缀长度时精确 key(无 wildcard)优先于 wildcard(`react` 胜过 `react*`)
290
+ const matched = [];
291
+ for (const [pattern, replacements] of ctx.paths) {
292
+ const tail = matchPathsPattern(moduleSpec, pattern);
293
+ if (tail === null)
294
+ continue;
295
+ const isWildcard = pattern.includes('*');
296
+ const prefixLen = isWildcard ? pattern.indexOf('*') : pattern.length;
297
+ matched.push({ prefixLen, isWildcard, tail, replacements });
209
298
  }
210
- if (Node.isCallExpression(node)) {
211
- return detectCallExpressionImportType(node);
299
+ if (matched.length === 0)
300
+ return null;
301
+ matched.sort((a, b) => {
302
+ if (b.prefixLen !== a.prefixLen)
303
+ return b.prefixLen - a.prefixLen;
304
+ return (a.isWildcard ? 1 : 0) - (b.isWildcard ? 1 : 0); // 同前缀长度:exact 优先
305
+ });
306
+ const baseUrl = typeof ctx.baseUrl === 'string' ? ctx.baseUrl : '.';
307
+ for (const m of matched) {
308
+ for (const replacement of m.replacements) {
309
+ const resolved = replacement.includes('*') ? replacement.replace('*', m.tail) : replacement;
310
+ const absBase = path.resolve(ctx.configDir, baseUrl, resolved);
311
+ const hit = tryFilePathVariants(absBase);
312
+ if (hit) {
313
+ return makeHit(hit, projectRoot, 'paths-alias');
314
+ }
315
+ }
212
316
  }
213
- // 兜底:未识别节点视为 static(保持非破坏性行为)
214
- return 'static';
317
+ return null;
215
318
  }
216
- function detectImportDeclarationType(decl) {
217
- // (a) 顶层 `import type` 关键字
218
- if (decl.isTypeOnly())
219
- return 'type-only';
220
- // WARN-1 修订:混合 import(含 default 或 namespace)即使 named import 全是 type-only,
221
- // 也保留 'static' 语义 —— default / namespace 在运行时仍是值导入。
222
- // 仅当 (a) 没有 default + 没有 namespace + (b) 有 named import 且全为 type-only 才归 type-only。
223
- const hasDefaultImport = decl.getDefaultImport() != null;
224
- const hasNamespaceImport = decl.getNamespaceImport() != null;
225
- if (hasDefaultImport || hasNamespaceImport) {
226
- return 'static';
227
- }
228
- const named = decl.getNamedImports();
229
- if (named.length > 0 && named.every((n) => n.isTypeOnly())) {
230
- return 'type-only';
319
+ /**
320
+ * 匹配 tsconfig paths pattern(精确 key + wildcard)。
321
+ * @returns null(不匹配),或 wildcard 截取的尾缀(精确 key 时为 '')
322
+ */
323
+ function matchPathsPattern(moduleSpec, pattern) {
324
+ if (pattern.includes('*')) {
325
+ const starIdx = pattern.indexOf('*');
326
+ const prefix = pattern.slice(0, starIdx);
327
+ const suffix = pattern.slice(starIdx + 1);
328
+ if (moduleSpec.startsWith(prefix) && moduleSpec.endsWith(suffix) &&
329
+ moduleSpec.length >= prefix.length + suffix.length) {
330
+ return moduleSpec.slice(prefix.length, moduleSpec.length - suffix.length);
331
+ }
332
+ return null;
231
333
  }
232
- return 'static';
334
+ return moduleSpec === pattern ? '' : null;
233
335
  }
234
- function detectCallExpressionImportType(call) {
235
- if (!Node.isCallExpression(call))
236
- return 'static';
237
- const expr = call.getExpression();
238
- // 动态 import:callee ImportKeyword
239
- if (expr.getKind() === SyntaxKind.ImportKeyword)
240
- return 'dynamic';
241
- // CommonJS require:callee Identifier("require")
242
- if (Node.isIdentifier(expr) && expr.getText() === 'require')
243
- return 'commonjs-require';
244
- return 'static';
336
+ // ───────────────────────────────────────────────────────────
337
+ // Public API — tsconfig loader(单一实现:ts.parseJsonConfigFileContent,含 extends 链)
338
+ // ───────────────────────────────────────────────────────────
339
+ /**
340
+ * filePath 向上查找最近的 tsconfig.json(monorepo-aware)。
341
+ *
342
+ * 上溯范围:从 path.dirname(filePath) 向上,直到 projectRoot 边界(含 projectRoot 本身)。
343
+ * 越过 projectRoot 停止,不抛异常。
344
+ *
345
+ * @returns tsconfig.json 的绝对路径;未找到返回 null
346
+ */
347
+ export function findNearestTsConfig(filePath, projectRoot) {
348
+ let dir = path.dirname(filePath);
349
+ // eslint-disable-next-line no-constant-condition
350
+ while (true) {
351
+ // 边界检查先于 fs 操作(C-3:避免越界路径误读)
352
+ if (dir !== projectRoot && !isInsideProjectRoot(dir, projectRoot)) {
353
+ break;
354
+ }
355
+ const candidate = path.join(dir, 'tsconfig.json');
356
+ if (fs.existsSync(candidate)) {
357
+ return candidate;
358
+ }
359
+ if (dir === projectRoot)
360
+ break;
361
+ const parent = path.dirname(dir);
362
+ if (parent === dir)
363
+ break; // 文件系统根
364
+ dir = parent;
365
+ }
366
+ return null;
245
367
  }
246
368
  /**
247
- * 给定一个文件的 imports 列表,逐项调用 resolveTsJsImport 填充 resolvedPath。
369
+ * 解析 tsconfig.json TsConfigResolutionContext(单一 loader,ts API 处理 extends 链)。
370
+ *
371
+ * 用 ts.readConfigFile + ts.parseJsonConfigFileContent:
372
+ * - 自动处理 extends 链(优于历史 kg 手写 JSON.parse 的 YAGNI 无 extends)
373
+ * - baseUrl 解析为绝对路径(alias 算法 path.resolve 正确覆盖 configDir)
374
+ * - paths 保留原始相对字符串(由 alias 算法做唯一一次 resolve,避免双重 resolve)
248
375
  *
249
- * 复用动机:ast-analyzer / tree-sitter-analyzer / tree-sitter-fallback 三处都需要
250
- * 在 imports 收集完毕后批量 resolve;提取此 helper 避免重复实现。
376
+ * 失败时返回 null(不抛异常,按未配置 alias 处理)。
251
377
  *
252
- * - importType 默认从 `isTypeOnly` 派生(type-only / static);ts-morph 路径可在调用前覆盖。
253
- * - 仅处理 TS/JS 语言;其他语言(Python / Go / Java)应跳过此函数。
378
+ * @param configPath - tsconfig.json 的绝对路径(由 findNearestTsConfig / 调用方提供)
254
379
  */
255
- export function resolveImportsForFile(imports, fromFile, projectRoot, options) {
256
- return imports.map((imp) => {
257
- // resolve 过则保留(如 ts-morph 路径已在 ast-analyzer 中显式调用)
258
- const alreadyResolved = imp.resolvedPath != null && imp.resolvedPath !== '';
259
- const resolvedPath = alreadyResolved
260
- ? imp.resolvedPath
261
- : resolveTsJsImport(imp.moduleSpecifier, fromFile, projectRoot, options);
262
- // 如果未显式设置 importType,从 isTypeOnly 派生
263
- const inferredImportType = imp.isTypeOnly ? 'type-only' : 'static';
264
- return {
265
- ...imp,
266
- resolvedPath: resolvedPath ?? null,
267
- // ImportReferenceSchema 已扩展 importType?: ImportType(v152 W1.0)
268
- importType: imp.importType
269
- ?? inferredImportType,
270
- };
271
- });
380
+ export function buildTsConfigContext(configPath) {
381
+ try {
382
+ const configFile = ts.readConfigFile(configPath, ts.sys.readFile);
383
+ if (configFile.error || !configFile.config) {
384
+ // 解析失败仍 return null(行为不变),但不再静默——首次为该 configPath 发一次 warn
385
+ const errorSummary = configFile.error
386
+ ? ts.flattenDiagnosticMessageText(configFile.error.messageText, ' ')
387
+ : 'config 为空';
388
+ warnTsConfigFailureOnce(configPath, errorSummary);
389
+ return null;
390
+ }
391
+ const configDir = path.dirname(configPath);
392
+ const parsed = ts.parseJsonConfigFileContent(configFile.config, ts.sys, configDir);
393
+ const compilerOptions = parsed.options;
394
+ const baseUrl = typeof compilerOptions.baseUrl === 'string' ? compilerOptions.baseUrl : null;
395
+ const paths = new Map();
396
+ if (compilerOptions.paths) {
397
+ for (const [key, value] of Object.entries(compilerOptions.paths)) {
398
+ if (Array.isArray(value)) {
399
+ const strs = value.filter((s) => typeof s === 'string');
400
+ if (strs.length > 0)
401
+ paths.set(key, strs);
402
+ }
403
+ }
404
+ }
405
+ return { configDir, baseUrl, paths };
406
+ }
407
+ catch (err) {
408
+ // 解析抛错仍 return null(行为不变),但不再静默——首次为该 configPath 发一次 warn
409
+ const errorSummary = err instanceof Error ? err.message : String(err);
410
+ warnTsConfigFailureOnce(configPath, errorSummary);
411
+ return null;
412
+ }
272
413
  }
273
414
  //# sourceMappingURL=import-resolver.js.map