@aiwg/cli 0.0.0-bootstrap.0 → 2026.7.17

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 (466) hide show
  1. package/README.md +21 -1
  2. package/bin/aiwg.mjs +349 -0
  3. package/dist/src/a2a/agent-card.js +145 -0
  4. package/dist/src/a2a/client.js +411 -0
  5. package/dist/src/a2a/hitl-cli.js +171 -0
  6. package/dist/src/a2a/hitl-driver.js +370 -0
  7. package/dist/src/a2a/hitl.js +181 -0
  8. package/dist/src/a2a/http.js +193 -0
  9. package/dist/src/a2a/jcs.js +128 -0
  10. package/dist/src/a2a/jws.js +187 -0
  11. package/dist/src/a2a/types.js +24 -0
  12. package/dist/src/a2a/webhook.js +240 -0
  13. package/dist/src/activity-log/cli.js +348 -0
  14. package/dist/src/activity-log/parser.js +81 -0
  15. package/dist/src/activity-log/types.js +50 -0
  16. package/dist/src/agents/agent-deployer.js +367 -0
  17. package/dist/src/agents/agent-packager.js +271 -0
  18. package/dist/src/agents/agent-validator.js +271 -0
  19. package/dist/src/agents/grounding/knowledge-base.js +161 -0
  20. package/dist/src/agents/grounding/types.js +12 -0
  21. package/dist/src/agents/index.js +10 -0
  22. package/dist/src/agents/packaged-agent-inventory.js +75 -0
  23. package/dist/src/agents/types.js +7 -0
  24. package/dist/src/api/index.d.ts +10 -0
  25. package/dist/src/api/index.js +10 -0
  26. package/dist/src/artifacts/address-parser.js +73 -0
  27. package/dist/src/artifacts/audit/cli.js +183 -0
  28. package/dist/src/artifacts/audit/drift.js +122 -0
  29. package/dist/src/artifacts/audit/types.js +12 -0
  30. package/dist/src/artifacts/backends/graphology-backend.js +160 -0
  31. package/dist/src/artifacts/backends/json-backend.js +129 -0
  32. package/dist/src/artifacts/backends/sqlite-backend.js +199 -0
  33. package/dist/src/artifacts/browser-export.js +622 -0
  34. package/dist/src/artifacts/capability-resolver.js +94 -0
  35. package/dist/src/artifacts/checksum-manifest.js +91 -0
  36. package/dist/src/artifacts/citation-parser.js +162 -0
  37. package/dist/src/artifacts/cli.js +1462 -0
  38. package/dist/src/artifacts/corpus-tools/citation-densify.js +311 -0
  39. package/dist/src/artifacts/corpus-tools/cli.js +393 -0
  40. package/dist/src/artifacts/corpus-tools/corpus-graph.js +225 -0
  41. package/dist/src/artifacts/corpus-tools/curator.js +186 -0
  42. package/dist/src/artifacts/corpus-tools/discovery-log.js +70 -0
  43. package/dist/src/artifacts/corpus-tools/funder-network.js +141 -0
  44. package/dist/src/artifacts/corpus-tools/induction-audit.js +233 -0
  45. package/dist/src/artifacts/corpus-tools/integrity-scan.js +202 -0
  46. package/dist/src/artifacts/corpus-tools/profile-communities.js +166 -0
  47. package/dist/src/artifacts/corpus-tools/profile-edges.js +72 -0
  48. package/dist/src/artifacts/corpus-tools/profile-embed.js +113 -0
  49. package/dist/src/artifacts/corpus-tools/profile-generate-fm.js +167 -0
  50. package/dist/src/artifacts/corpus-tools/profile-generate.js +181 -0
  51. package/dist/src/artifacts/corpus-tools/profile-metrics.js +98 -0
  52. package/dist/src/artifacts/corpus-tools/profile-status.js +86 -0
  53. package/dist/src/artifacts/corpus-tools/profile-temporal.js +139 -0
  54. package/dist/src/artifacts/corpus-tools/radar-init.js +93 -0
  55. package/dist/src/artifacts/corpus-tools/radar-report.js +118 -0
  56. package/dist/src/artifacts/corpus-tools/radar-shared.js +189 -0
  57. package/dist/src/artifacts/corpus-tools/radar-status.js +68 -0
  58. package/dist/src/artifacts/corpus-tools/sidecar-lint.js +233 -0
  59. package/dist/src/artifacts/corpus-tools/sidecar-repair.js +277 -0
  60. package/dist/src/artifacts/corpus-tools/snapshot.js +591 -0
  61. package/dist/src/artifacts/corpus-tools/source-types.js +164 -0
  62. package/dist/src/artifacts/corpus-tools/vision-extract.js +243 -0
  63. package/dist/src/artifacts/corpus-views/build.js +126 -0
  64. package/dist/src/artifacts/corpus-views/corpus-config.js +124 -0
  65. package/dist/src/artifacts/corpus-views/ref-parser.js +437 -0
  66. package/dist/src/artifacts/corpus-views/renderers.js +353 -0
  67. package/dist/src/artifacts/corpus-views/taxonomies.js +131 -0
  68. package/dist/src/artifacts/dep-graph.js +173 -0
  69. package/dist/src/artifacts/discover-facets.js +375 -0
  70. package/dist/src/artifacts/embedding-index.js +354 -0
  71. package/dist/src/artifacts/enrichment/cli.js +240 -0
  72. package/dist/src/artifacts/enrichment/prompt.js +47 -0
  73. package/dist/src/artifacts/enrichment/store.js +90 -0
  74. package/dist/src/artifacts/enrichment/types.js +12 -0
  75. package/dist/src/artifacts/fortemi-core-query-adapter.js +475 -0
  76. package/dist/src/artifacts/fortemi-core-sync.js +251 -0
  77. package/dist/src/artifacts/fortemi-shard-export.js +44 -0
  78. package/dist/src/artifacts/fulltext.js +108 -0
  79. package/dist/src/artifacts/graph-backend.js +51 -0
  80. package/dist/src/artifacts/graph-query.js +247 -0
  81. package/dist/src/artifacts/hybrid-query.js +225 -0
  82. package/dist/src/artifacts/index-builder.js +1089 -0
  83. package/dist/src/artifacts/index-reader.js +112 -0
  84. package/dist/src/artifacts/index-status.js +193 -0
  85. package/dist/src/artifacts/legacy-index-migration.js +169 -0
  86. package/dist/src/artifacts/move.js +154 -0
  87. package/dist/src/artifacts/operational-state.js +239 -0
  88. package/dist/src/artifacts/prebuilt-build-lock.js +49 -0
  89. package/dist/src/artifacts/query-engine.js +1955 -0
  90. package/dist/src/artifacts/source-graph.js +529 -0
  91. package/dist/src/artifacts/stats.js +169 -0
  92. package/dist/src/artifacts/types.js +606 -0
  93. package/dist/src/artifacts/views/cli.js +293 -0
  94. package/dist/src/artifacts/views/definition.js +170 -0
  95. package/dist/src/artifacts/views/store.js +149 -0
  96. package/dist/src/artifacts/views/types.js +8 -0
  97. package/dist/src/artifacts/watcher.js +255 -0
  98. package/dist/src/catalog/builtin-models.json +636 -0
  99. package/dist/src/catalog/cli.js +228 -0
  100. package/dist/src/catalog/cli.mjs +289 -0
  101. package/dist/src/catalog/index.js +15 -0
  102. package/dist/src/catalog/loader.js +217 -0
  103. package/dist/src/catalog/loader.mjs +251 -0
  104. package/dist/src/catalog/sources.json +41 -0
  105. package/dist/src/catalog/types.js +8 -0
  106. package/dist/src/channel/manager.mjs +504 -0
  107. package/dist/src/cli/agent-spawn.js +178 -0
  108. package/dist/src/cli/cli-extension-loader.js +245 -0
  109. package/dist/src/cli/command-log.js +275 -0
  110. package/dist/src/cli/config-loader.js +232 -0
  111. package/dist/src/cli/env.js +82 -0
  112. package/dist/src/cli/errors.js +150 -0
  113. package/dist/src/cli/find-package-root.js +40 -0
  114. package/dist/src/cli/git-hooks.js +250 -0
  115. package/dist/src/cli/handlers/agentcard.js +206 -0
  116. package/dist/src/cli/handlers/artifacts.js +76 -0
  117. package/dist/src/cli/handlers/best-practices-audit.js +280 -0
  118. package/dist/src/cli/handlers/cockpit.js +137 -0
  119. package/dist/src/cli/handlers/command-log.js +37 -0
  120. package/dist/src/cli/handlers/daemon.js +59 -0
  121. package/dist/src/cli/handlers/diagnose.js +300 -0
  122. package/dist/src/cli/handlers/execution-mode.js +105 -0
  123. package/dist/src/cli/handlers/feedback.js +341 -0
  124. package/dist/src/cli/handlers/help.js +147 -0
  125. package/dist/src/cli/handlers/index.js +302 -0
  126. package/dist/src/cli/handlers/init.js +179 -0
  127. package/dist/src/cli/handlers/install.js +129 -0
  128. package/dist/src/cli/handlers/issues.js +46 -0
  129. package/dist/src/cli/handlers/lint.js +42 -0
  130. package/dist/src/cli/handlers/local-executor.js +315 -0
  131. package/dist/src/cli/handlers/marketplace.js +168 -0
  132. package/dist/src/cli/handlers/mc.js +1010 -0
  133. package/dist/src/cli/handlers/models.js +352 -0
  134. package/dist/src/cli/handlers/packages.js +155 -0
  135. package/dist/src/cli/handlers/ralph-launcher.js +717 -0
  136. package/dist/src/cli/handlers/ralph.js +575 -0
  137. package/dist/src/cli/handlers/refresh.js +432 -0
  138. package/dist/src/cli/handlers/regenerate.js +336 -0
  139. package/dist/src/cli/handlers/repo-access.js +109 -0
  140. package/dist/src/cli/handlers/run.js +165 -0
  141. package/dist/src/cli/handlers/runtime-info.js +301 -0
  142. package/dist/src/cli/handlers/sandbox.js +197 -0
  143. package/dist/src/cli/handlers/scaffolding.js +149 -0
  144. package/dist/src/cli/handlers/script-runner.js +121 -0
  145. package/dist/src/cli/handlers/sdlc-accelerate.js +148 -0
  146. package/dist/src/cli/handlers/serve.js +1835 -0
  147. package/dist/src/cli/handlers/session.js +348 -0
  148. package/dist/src/cli/handlers/setup.js +440 -0
  149. package/dist/src/cli/handlers/skill-lint.js +367 -0
  150. package/dist/src/cli/handlers/skill-usage.js +74 -0
  151. package/dist/src/cli/handlers/steward.js +567 -0
  152. package/dist/src/cli/handlers/subcommands.js +1702 -0
  153. package/dist/src/cli/handlers/team.js +337 -0
  154. package/dist/src/cli/handlers/types.js +11 -0
  155. package/dist/src/cli/handlers/use.js +2772 -0
  156. package/dist/src/cli/handlers/utilities.js +703 -0
  157. package/dist/src/cli/handlers/version.js +134 -0
  158. package/dist/src/cli/handlers/workspace-context.js +93 -0
  159. package/dist/src/cli/handlers/workspace.js +126 -0
  160. package/dist/src/cli/help-generator.js +178 -0
  161. package/dist/src/cli/hooks/builtin/activity-log-hook.js +126 -0
  162. package/dist/src/cli/hooks/executor.js +96 -0
  163. package/dist/src/cli/hooks/index.js +15 -0
  164. package/dist/src/cli/hooks/registry.js +143 -0
  165. package/dist/src/cli/hooks/types.js +13 -0
  166. package/dist/src/cli/log.js +492 -0
  167. package/dist/src/cli/project-isolation/detect.js +70 -0
  168. package/dist/src/cli/project-isolation/index.js +7 -0
  169. package/dist/src/cli/project-isolation/signals.js +17 -0
  170. package/dist/src/cli/project-isolation/warning.js +102 -0
  171. package/dist/src/cli/prompt-utils.js +155 -0
  172. package/dist/src/cli/provider-deployment-plan.js +126 -0
  173. package/dist/src/cli/provider-resolution.js +119 -0
  174. package/dist/src/cli/router.js +305 -0
  175. package/dist/src/cli/scope-resolver.js +392 -0
  176. package/dist/src/cli/skill-usage.js +616 -0
  177. package/dist/src/cli/ui.js +224 -0
  178. package/dist/src/cli/watch-service.js +203 -0
  179. package/dist/src/cli/workflow-orchestrator.js +478 -0
  180. package/dist/src/cli/workspace-signals.js +321 -0
  181. package/dist/src/community/footer.js +14 -0
  182. package/dist/src/community/links.js +123 -0
  183. package/dist/src/community/milestones.js +46 -0
  184. package/dist/src/community/nudge-policy.js +93 -0
  185. package/dist/src/config/aiwg-config.js +883 -0
  186. package/dist/src/config/cli.js +703 -0
  187. package/dist/src/config/gitignore.js +156 -0
  188. package/dist/src/config/project-artifacts.js +76 -0
  189. package/dist/src/config/user-config.js +428 -0
  190. package/dist/src/config/user-registry.js +127 -0
  191. package/dist/src/config/workspace.js +276 -0
  192. package/dist/src/contributors/detect.js +39 -0
  193. package/dist/src/contributors/discover.js +172 -0
  194. package/dist/src/contributors/heuristic.js +186 -0
  195. package/dist/src/contributors/index.js +11 -0
  196. package/dist/src/contributors/types.js +13 -0
  197. package/dist/src/contributors/validation.js +116 -0
  198. package/dist/src/data/community.schema.json +43 -0
  199. package/dist/src/data/community.yaml +16 -0
  200. package/dist/src/extensions/capability-index.js +270 -0
  201. package/dist/src/extensions/claude-hooks-installer.js +268 -0
  202. package/dist/src/extensions/commands/definitions.js +3485 -0
  203. package/dist/src/extensions/deployment-registration.js +463 -0
  204. package/dist/src/extensions/index.js +22 -0
  205. package/dist/src/extensions/loader.js +132 -0
  206. package/dist/src/extensions/managed-marker.js +100 -0
  207. package/dist/src/extensions/manifest.js +196 -0
  208. package/dist/src/extensions/project-local-activity.js +114 -0
  209. package/dist/src/extensions/project-local-discovery.js +261 -0
  210. package/dist/src/extensions/project-local-doctor.js +281 -0
  211. package/dist/src/extensions/project-local-gitignore.js +176 -0
  212. package/dist/src/extensions/project-local-paths.js +121 -0
  213. package/dist/src/extensions/project-local-promote.js +218 -0
  214. package/dist/src/extensions/project-local-remove.js +373 -0
  215. package/dist/src/extensions/project-local-scaffold.js +252 -0
  216. package/dist/src/extensions/project-quickref.js +291 -0
  217. package/dist/src/extensions/registry.js +368 -0
  218. package/dist/src/extensions/shadow-resolver.js +271 -0
  219. package/dist/src/extensions/types.js +86 -0
  220. package/dist/src/extensions/upstream-registry.js +195 -0
  221. package/dist/src/extensions/validation.js +631 -0
  222. package/dist/src/features/catalog.js +88 -0
  223. package/dist/src/features/cli.js +197 -0
  224. package/dist/src/features/installer.js +57 -0
  225. package/dist/src/features/paths.js +14 -0
  226. package/dist/src/features/runtime.js +21 -0
  227. package/dist/src/features/status.js +134 -0
  228. package/dist/src/hooks/laziness-detection.js +532 -0
  229. package/dist/src/intake/git-history-analyzer.js +486 -0
  230. package/dist/src/intake/index.js +9 -0
  231. package/dist/src/issues/cli.js +327 -0
  232. package/dist/src/issues/index.js +5 -0
  233. package/dist/src/issues/live.js +154 -0
  234. package/dist/src/issues/local.js +666 -0
  235. package/dist/src/issues/sync.js +143 -0
  236. package/dist/src/issues/types.js +2 -0
  237. package/dist/src/issues/workflows.js +164 -0
  238. package/dist/src/kb/cli.js +173 -0
  239. package/dist/src/lint/cli.js +171 -0
  240. package/dist/src/lint/loader.js +225 -0
  241. package/dist/src/lint/reporters.js +141 -0
  242. package/dist/src/lint/runner.js +364 -0
  243. package/dist/src/lint/types.js +10 -0
  244. package/dist/src/marketplace/cache.js +110 -0
  245. package/dist/src/marketplace/identifier.js +63 -0
  246. package/dist/src/marketplace/registry.js +36 -0
  247. package/dist/src/marketplace/sources/clawhub.js +53 -0
  248. package/dist/src/marketplace/sources/git.js +45 -0
  249. package/dist/src/marketplace/types.js +9 -0
  250. package/dist/src/mcp/adapters/codex-runtime.js +224 -0
  251. package/dist/src/mcp/cli.mjs +1197 -0
  252. package/dist/src/mcp/elicitation.mjs +149 -0
  253. package/dist/src/mcp/helpers.mjs +287 -0
  254. package/dist/src/mcp/index.mjs +11 -0
  255. package/dist/src/mcp/profiles.js +263 -0
  256. package/dist/src/mcp/profiles.mjs +360 -0
  257. package/dist/src/mcp/registry.js +376 -0
  258. package/dist/src/mcp/registry.mjs +387 -0
  259. package/dist/src/mcp/server.mjs +633 -0
  260. package/dist/src/mcp/test-simple.mjs +271 -0
  261. package/dist/src/mcp/tools/command-run.mjs +101 -0
  262. package/dist/src/mcp/tools/discovery.mjs +291 -0
  263. package/dist/src/mcp/tools/interaction.mjs +87 -0
  264. package/dist/src/mcp/tools/orchestration.mjs +320 -0
  265. package/dist/src/mcp/tools/subsystems.mjs +640 -0
  266. package/dist/src/memory/cli.js +166 -0
  267. package/dist/src/memory/project-registry.js +282 -0
  268. package/dist/src/metrics/artifact-metrics.js +184 -0
  269. package/dist/src/metrics/context-budget.js +174 -0
  270. package/dist/src/metrics/token-counter.js +133 -0
  271. package/dist/src/models/config-loader.js +228 -0
  272. package/dist/src/models/index.js +22 -0
  273. package/dist/src/models/model-capabilities.v1.json +120 -0
  274. package/dist/src/models/model-catalog.v1.json +96 -0
  275. package/dist/src/models/model-discovery.js +521 -0
  276. package/dist/src/models/provider-models.js +73 -0
  277. package/dist/src/models/provider-policy.js +273 -0
  278. package/dist/src/models/resolver.js +245 -0
  279. package/dist/src/models/router.js +70 -0
  280. package/dist/src/models/types.js +8 -0
  281. package/dist/src/models/wrapper-deployment.js +178 -0
  282. package/dist/src/models/wrapper-route.js +54 -0
  283. package/dist/src/ops/cli.js +268 -0
  284. package/dist/src/ops/registry.js +623 -0
  285. package/dist/src/packages/adapters/clawhub.js +113 -0
  286. package/dist/src/packages/adapters/git.js +153 -0
  287. package/dist/src/packages/adapters/gitea.js +93 -0
  288. package/dist/src/packages/adapters/github.js +69 -0
  289. package/dist/src/packages/adapters/local-cache.js +89 -0
  290. package/dist/src/packages/package-registry.js +143 -0
  291. package/dist/src/packages/registry.js +238 -0
  292. package/dist/src/packages/types.js +11 -0
  293. package/dist/src/plugin/cross-framework-ops.js +475 -0
  294. package/dist/src/plugin/framework-config-loader.js +235 -0
  295. package/dist/src/plugin/framework-detector.js +186 -0
  296. package/dist/src/plugin/framework-isolator.js +341 -0
  297. package/dist/src/plugin/framework-migration.js +371 -0
  298. package/dist/src/plugin/metadata-validator.js +906 -0
  299. package/dist/src/plugin/plugin-installer.js +436 -0
  300. package/dist/src/plugin/plugin-status.js +369 -0
  301. package/dist/src/plugin/plugin-uninstaller.js +473 -0
  302. package/dist/src/plugin/registry-validator.js +344 -0
  303. package/dist/src/plugin/skill-command-translator.js +415 -0
  304. package/dist/src/plugin/workspace-creator.js +189 -0
  305. package/dist/src/plugin/workspace-migrator.js +821 -0
  306. package/dist/src/policy/authorization.js +390 -0
  307. package/dist/src/policy/repo-access.js +215 -0
  308. package/dist/src/provenance/cli.js +16 -0
  309. package/dist/src/providers/capability-matrix.js +300 -0
  310. package/dist/src/providers/capability-matrix.yaml +514 -0
  311. package/dist/src/providers/provider-definitions.js +977 -0
  312. package/dist/src/providers/provider-definitions.mjs +159 -0
  313. package/dist/src/providers/provider-inventory.js +169 -0
  314. package/dist/src/quality/feedback-ab.js +174 -0
  315. package/dist/src/quality/feedback-collector.js +176 -0
  316. package/dist/src/quality/feedback-tracker.js +123 -0
  317. package/dist/src/quality/patterns/adr.json +24 -0
  318. package/dist/src/quality/patterns/sad.json +24 -0
  319. package/dist/src/quality/patterns/test-plan.json +24 -0
  320. package/dist/src/quality/patterns/use-case.json +24 -0
  321. package/dist/src/quality/scoring.js +157 -0
  322. package/dist/src/reflections/cli.js +15 -0
  323. package/dist/src/release/plan.js +139 -0
  324. package/dist/src/research/cache/manager.js +227 -0
  325. package/dist/src/research/clients/arxiv.js +156 -0
  326. package/dist/src/research/clients/base.js +197 -0
  327. package/dist/src/research/clients/crossref.js +112 -0
  328. package/dist/src/research/clients/semantic-scholar.js +126 -0
  329. package/dist/src/research/clients/unpaywall.js +87 -0
  330. package/dist/src/research/index.js +14 -0
  331. package/dist/src/research/query-cli.js +336 -0
  332. package/dist/src/research/services/acquisition.js +313 -0
  333. package/dist/src/research/services/archival.js +225 -0
  334. package/dist/src/research/services/citation.js +279 -0
  335. package/dist/src/research/services/discovery.js +243 -0
  336. package/dist/src/research/services/documentation.js +269 -0
  337. package/dist/src/research/services/index.js +14 -0
  338. package/dist/src/research/services/provenance.js +279 -0
  339. package/dist/src/research/services/quality.js +370 -0
  340. package/dist/src/research/services/types.js +7 -0
  341. package/dist/src/research/storage-cli.js +20 -0
  342. package/dist/src/research/types.js +47 -0
  343. package/dist/src/resources/index.d.ts +2 -0
  344. package/dist/src/resources/index.js +2 -0
  345. package/dist/src/resources/web-release.d.ts +89 -0
  346. package/dist/src/resources/web-release.js +882 -0
  347. package/dist/src/rlm/cache/cli.js +162 -0
  348. package/dist/src/rlm/cache/hash.js +35 -0
  349. package/dist/src/rlm/cache/store.js +170 -0
  350. package/dist/src/rlm/cache/types.js +8 -0
  351. package/dist/src/rlm/cli.js +650 -0
  352. package/dist/src/serve/a2a-terminal-observer.js +120 -0
  353. package/dist/src/serve/agent-router.js +205 -0
  354. package/dist/src/serve/dispatch-router.js +171 -0
  355. package/dist/src/serve/executor-registry.js +715 -0
  356. package/dist/src/serve/mission-conductor.js +177 -0
  357. package/dist/src/serve/orchestrator-adapter.js +113 -0
  358. package/dist/src/serve/orchestrator-override.js +94 -0
  359. package/dist/src/serve/orchestrator-pty.js +133 -0
  360. package/dist/src/serve/pty-bridge.js +799 -0
  361. package/dist/src/serve/sandbox-registry.js +832 -0
  362. package/dist/src/serve/screen-reader.js +228 -0
  363. package/dist/src/serve/stack-adapters.js +68 -0
  364. package/dist/src/serve/telemetry.js +143 -0
  365. package/dist/src/skills/adapters/clawhub.js +250 -0
  366. package/dist/src/skills/adapters/local.js +335 -0
  367. package/dist/src/skills/adapters/openclaw.js +316 -0
  368. package/dist/src/skills/cli.js +334 -0
  369. package/dist/src/skills/registry.js +117 -0
  370. package/dist/src/skills/run.js +259 -0
  371. package/dist/src/skills/runtime.js +94 -0
  372. package/dist/src/skills/types.js +10 -0
  373. package/dist/src/smiths/agentsmith/demo.js +116 -0
  374. package/dist/src/smiths/agentsmith/examples.js +300 -0
  375. package/dist/src/smiths/agentsmith/generator.js +503 -0
  376. package/dist/src/smiths/agentsmith/index.js +11 -0
  377. package/dist/src/smiths/agentsmith/types.js +8 -0
  378. package/dist/src/smiths/commandsmith/example.js +220 -0
  379. package/dist/src/smiths/commandsmith/generator.js +288 -0
  380. package/dist/src/smiths/commandsmith/index.js +41 -0
  381. package/dist/src/smiths/commandsmith/templates.js +296 -0
  382. package/dist/src/smiths/commandsmith/types.js +7 -0
  383. package/dist/src/smiths/context-pipeline/aiwg-md.js +104 -0
  384. package/dist/src/smiths/context-pipeline/allowlist.js +195 -0
  385. package/dist/src/smiths/context-pipeline/claude-hook.js +127 -0
  386. package/dist/src/smiths/context-pipeline/discovery.js +187 -0
  387. package/dist/src/smiths/context-pipeline/external-links-section.js +49 -0
  388. package/dist/src/smiths/context-pipeline/finalization.js +147 -0
  389. package/dist/src/smiths/context-pipeline/generator.js +477 -0
  390. package/dist/src/smiths/context-pipeline/index.js +30 -0
  391. package/dist/src/smiths/context-pipeline/legacy-inject.js +103 -0
  392. package/dist/src/smiths/context-pipeline/managed-hook.js +95 -0
  393. package/dist/src/smiths/context-pipeline/overflow.js +212 -0
  394. package/dist/src/smiths/context-pipeline/parallelism-section.js +94 -0
  395. package/dist/src/smiths/context-pipeline/provider-policy.js +64 -0
  396. package/dist/src/smiths/context-pipeline/sanitizer.js +93 -0
  397. package/dist/src/smiths/context-pipeline/types.js +23 -0
  398. package/dist/src/smiths/context-pipeline/workspace-context.js +988 -0
  399. package/dist/src/smiths/hook-bridge/codex-translator.js +129 -0
  400. package/dist/src/smiths/hook-bridge/copilot-translator.js +64 -0
  401. package/dist/src/smiths/hook-bridge/factory-translator.js +43 -0
  402. package/dist/src/smiths/hook-bridge/hermes-translator.js +88 -0
  403. package/dist/src/smiths/hook-bridge/index.js +79 -0
  404. package/dist/src/smiths/hook-bridge/loader.js +116 -0
  405. package/dist/src/smiths/hook-bridge/shim.js +89 -0
  406. package/dist/src/smiths/hook-bridge/types.js +31 -0
  407. package/dist/src/smiths/index.js +39 -0
  408. package/dist/src/smiths/mcpsmith/analyzers/cli-analyzer.js +390 -0
  409. package/dist/src/smiths/mcpsmith/example.js +119 -0
  410. package/dist/src/smiths/mcpsmith/generator.js +236 -0
  411. package/dist/src/smiths/mcpsmith/index.js +17 -0
  412. package/dist/src/smiths/mcpsmith/types.js +10 -0
  413. package/dist/src/smiths/platform-paths.js +101 -0
  414. package/dist/src/smiths/skillsmith/codex-sidecar.js +85 -0
  415. package/dist/src/smiths/skillsmith/collision-detector.js +249 -0
  416. package/dist/src/smiths/skillsmith/demo.js +83 -0
  417. package/dist/src/smiths/skillsmith/examples.js +161 -0
  418. package/dist/src/smiths/skillsmith/generator.js +316 -0
  419. package/dist/src/smiths/skillsmith/index.js +12 -0
  420. package/dist/src/smiths/skillsmith/namespace-adapter.js +180 -0
  421. package/dist/src/smiths/skillsmith/platform-resolver.js +157 -0
  422. package/dist/src/smiths/skillsmith/types.js +9 -0
  423. package/dist/src/smiths/toolsmith/index.js +10 -0
  424. package/dist/src/smiths/toolsmith/runtime-discovery.mjs +710 -0
  425. package/dist/src/smiths/toolsmith/types.js +8 -0
  426. package/dist/src/storage/backends/fortemi.js +227 -0
  427. package/dist/src/storage/backends/fs.js +134 -0
  428. package/dist/src/storage/backends/logseq.js +309 -0
  429. package/dist/src/storage/backends/obsidian.js +233 -0
  430. package/dist/src/storage/cli.js +468 -0
  431. package/dist/src/storage/config.js +272 -0
  432. package/dist/src/storage/index.js +108 -0
  433. package/dist/src/storage/subsystem-cli.js +201 -0
  434. package/dist/src/storage/types.js +33 -0
  435. package/dist/src/testing/corpus/corpus-builder.js +383 -0
  436. package/dist/src/testing/corpus/ground-truth-manager.js +460 -0
  437. package/dist/src/testing/corpus/index.js +10 -0
  438. package/dist/src/testing/fixtures/index.js +9 -0
  439. package/dist/src/testing/fixtures/test-data-factory.js +472 -0
  440. package/dist/src/testing/generators/index.js +11 -0
  441. package/dist/src/testing/generators/test-case-generator.js +393 -0
  442. package/dist/src/testing/generators/test-code-generator.js +384 -0
  443. package/dist/src/testing/generators/use-case-parser.js +325 -0
  444. package/dist/src/testing/index.js +11 -0
  445. package/dist/src/tracker/capability-protocol.js +118 -0
  446. package/dist/src/update/checker.mjs +396 -0
  447. package/dist/src/update/notifier.mjs +235 -0
  448. package/dist/src/writing/content-diversifier.js +804 -0
  449. package/dist/src/writing/example-generator.js +959 -0
  450. package/dist/src/writing/example-templates.js +99 -0
  451. package/dist/src/writing/pattern-library.js +486 -0
  452. package/dist/src/writing/patterns/banned-phrases.json +4413 -0
  453. package/dist/src/writing/patterns/formulaic-structures.json +1300 -0
  454. package/dist/src/writing/patterns/generic-adjectives.json +1510 -0
  455. package/dist/src/writing/patterns/hedging-language.json +2096 -0
  456. package/dist/src/writing/patterns/transition-words.json +1285 -0
  457. package/dist/src/writing/patterns/weak-verbs.json +1112 -0
  458. package/dist/src/writing/prompt-optimizer.js +660 -0
  459. package/dist/src/writing/prompt-templates.js +583 -0
  460. package/dist/src/writing/scoring-config-loader.js +168 -0
  461. package/dist/src/writing/validation-engine.js +595 -0
  462. package/dist/src/writing/validation-rules.js +380 -0
  463. package/dist/src/writing/voice-analyzer.js +439 -0
  464. package/dist/src/writing/voice-calibration.js +661 -0
  465. package/dist/src/writing/voice-profiles.json +588 -0
  466. package/package.json +64 -9
@@ -0,0 +1,1089 @@
1
+ /**
2
+ * Artifact Index Builder
3
+ *
4
+ * Scans .aiwg/ directories, extracts metadata from artifact frontmatter,
5
+ * computes checksums, extracts @-mention dependencies, and builds a
6
+ * structured index at .aiwg/.index/.
7
+ *
8
+ * @implements #415
9
+ * @source @src/artifacts/types.ts
10
+ * @tests @test/unit/artifacts/index-builder.test.ts
11
+ */
12
+ import fs from 'fs';
13
+ import path from 'path';
14
+ import { createHash } from 'crypto';
15
+ import { load as loadYaml } from 'js-yaml';
16
+ import { DEFAULT_INDEX_EXTENSIONS, INDEX_EXTRACTOR_VERSION, INDEX_VERSION, INDEX_DIR, OPERATIONAL_DISCOVERY_TYPES, PHASE_DIRECTORIES, GRAPH_CONFIGS, TEMPLATE_INDEX_EXTENSIONS, resolveGraphScanDir, loadUserGraphConfigs, loadGlobalGraphConfigs, } from './types.js';
17
+ import { parseCitationSidecar, citationResultToEdges, buildRefToPathMap } from './citation-parser.js';
18
+ import { writeIndexFile, resolveIndexDir, loadGraphIndexFile } from './index-reader.js';
19
+ import { loadManifest, writeManifest, statMatches, makeEntry } from './checksum-manifest.js';
20
+ import { workspaceLinkedFiles } from '../smiths/context-pipeline/workspace-context.js';
21
+ import { normalizeOperationalState } from './operational-state.js';
22
+ import { DEFAULT_PROJECT_AIWG_DIR, resolveProjectAiwgDir, } from '../config/project-artifacts.js';
23
+ function pathContains(parent, child) {
24
+ const relative = path.relative(parent, child);
25
+ return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
26
+ }
27
+ function toPosixPath(value) {
28
+ return value.split(path.sep).join('/');
29
+ }
30
+ function indexPathFor(cwd, fullPath, graph) {
31
+ if (!graph || graph === 'project') {
32
+ const artifactRoot = resolveProjectAiwgDir(cwd);
33
+ if (pathContains(artifactRoot, fullPath)) {
34
+ const relative = toPosixPath(path.relative(artifactRoot, fullPath));
35
+ return relative ? `${DEFAULT_PROJECT_AIWG_DIR}/${relative}` : DEFAULT_PROJECT_AIWG_DIR;
36
+ }
37
+ }
38
+ const rel = path.relative(cwd, fullPath);
39
+ if (!rel.startsWith('..') && !path.isAbsolute(rel))
40
+ return toPosixPath(rel);
41
+ return fullPath;
42
+ }
43
+ function absoluteEntryPath(cwd, entryPath, graph) {
44
+ if ((!graph || graph === 'project') && entryPath.startsWith(`${DEFAULT_PROJECT_AIWG_DIR}/`)) {
45
+ return path.join(resolveProjectAiwgDir(cwd), entryPath.slice(DEFAULT_PROJECT_AIWG_DIR.length + 1));
46
+ }
47
+ return path.isAbsolute(entryPath) ? entryPath : path.join(cwd, entryPath);
48
+ }
49
+ function loadIndexFromDir(indexDir, filename) {
50
+ const filePath = path.join(indexDir, filename);
51
+ if (!fs.existsSync(filePath))
52
+ return null;
53
+ try {
54
+ return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
55
+ }
56
+ catch {
57
+ return null;
58
+ }
59
+ }
60
+ /**
61
+ * Parse YAML frontmatter from markdown content
62
+ */
63
+ export function parseFrontmatter(content) {
64
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);
65
+ if (!match)
66
+ return { data: {}, body: content };
67
+ try {
68
+ const data = (loadYaml(match[1]) ?? {});
69
+ return { data, body: match[2] };
70
+ }
71
+ catch {
72
+ return { data: {}, body: content };
73
+ }
74
+ }
75
+ /**
76
+ * Extract @-mention references from content
77
+ */
78
+ export function extractMentions(content) {
79
+ const mentions = new Set();
80
+ // Match @path/to/file.ext, @.aiwg/path, and framework references rooted at
81
+ // @$AIWG_ROOT/. The latter must normalize to repo-relative paths so framework
82
+ // skill references resolve to their rule files in the graph index.
83
+ const pattern = /@(?:\$AIWG_ROOT\/)?(\.?aiwg\/[\w./-]+|[a-zA-Z][\w./-]+\.\w+)/g;
84
+ let match;
85
+ while ((match = pattern.exec(content)) !== null) {
86
+ mentions.add(match[1]);
87
+ }
88
+ return Array.from(mentions);
89
+ }
90
+ /**
91
+ * Extract title from content (first # heading or frontmatter title)
92
+ */
93
+ function extractTitle(data, body) {
94
+ if (typeof data.title === 'string')
95
+ return data.title;
96
+ const headingMatch = body.match(/^#\s+(.+)$/m);
97
+ return headingMatch ? headingMatch[1].trim() : 'Untitled';
98
+ }
99
+ /**
100
+ * Extract canonical short name (#1233) used for exact-name search match.
101
+ *
102
+ * Priority:
103
+ * 1. frontmatter `name:` field if present
104
+ * 2. parent directory basename for skills/agents/commands/rules layouts
105
+ * (skills conventionally live in `<...>/skills/<name>/SKILL.md`)
106
+ * 3. filename without extension as final fallback
107
+ *
108
+ * The result is the literal slug a user would type to find the artifact —
109
+ * `aiwg-doctor`, `intake-wizard`, `flow-deploy-to-production` — preserving
110
+ * hyphens so the scorer can match queries that contain them.
111
+ */
112
+ function extractCanonicalName(data, relativePath) {
113
+ if (typeof data.name === 'string' && data.name.trim().length > 0) {
114
+ return data.name.trim();
115
+ }
116
+ const filename = path.basename(relativePath);
117
+ // For slug layouts: <type>s/<name>/<TYPE>.md
118
+ const isCanonicalLayout = /^(SKILL|AGENT|COMMAND|RULE|BEHAVIOR)\.md$/i.test(filename);
119
+ if (isCanonicalLayout) {
120
+ return path.basename(path.dirname(relativePath));
121
+ }
122
+ return path.basename(filename, path.extname(filename));
123
+ }
124
+ /**
125
+ * Extract summary from content (first 500 chars of description or body)
126
+ */
127
+ function extractSummary(data, body) {
128
+ if (typeof data.description === 'string')
129
+ return data.description.slice(0, 500);
130
+ // Skip headings, get first paragraph
131
+ const lines = body.split('\n').filter(l => l.trim() && !l.startsWith('#'));
132
+ return lines.slice(0, 5).join(' ').slice(0, 500).trim();
133
+ }
134
+ /**
135
+ * Determine SDLC phase from file path
136
+ */
137
+ function inferPhase(filePath) {
138
+ for (const [phase, dir] of Object.entries(PHASE_DIRECTORIES)) {
139
+ if (filePath.startsWith(dir))
140
+ return phase;
141
+ }
142
+ return 'other';
143
+ }
144
+ /**
145
+ * Determine artifact type from frontmatter or filename.
146
+ *
147
+ * Path-based detection for AIWG artifact kinds (#1214) takes precedence
148
+ * over the legacy basename heuristics so `agentic/code/.../skills/foo/SKILL.md`
149
+ * always lands as `type: 'skill'` regardless of frontmatter.
150
+ */
151
+ function inferType(data, filePath) {
152
+ if (typeof data.type === 'string')
153
+ return data.type;
154
+ // Normalize separators so matchers are cross-platform.
155
+ const normalized = filePath.replace(/\\/g, '/');
156
+ const basename = path.basename(filePath, path.extname(filePath)).toLowerCase();
157
+ // Nearest-type-dir-ancestor classification (#1221 audit).
158
+ //
159
+ // The AIWG corpus uses many different nesting depths for artifacts:
160
+ // frameworks/<f>/skills/<slug>/SKILL.md
161
+ // frameworks/<f>/extensions/<sub>/skills/<slug>/SKILL.md
162
+ // frameworks/<f>/templates/hermes/skills/<slug>/SKILL.md
163
+ // frameworks/<f>/elaboration/agents/<n>.md (research-complete)
164
+ // addons/<a>/agents/<n>.md
165
+ // addons/<a>/prompts/agents/<n>.md (aiwg-utils)
166
+ // addons/<a>/skills/skills/SKILL.md (aiwg-utils self-skill)
167
+ // addons/<a>/templates/<n>.md
168
+ // addons/<a>/behaviors/<n>.md
169
+ // addons/<a>/hooks/<n>.md
170
+ // extensions/<e>/skills/<n>.md (flat — no slug dir)
171
+ // extensions/<e>/rules/<n>.md
172
+ // agentic/code/behaviors/<sub>/... (top-level behaviors)
173
+ //
174
+ // Walk the path segments from the file upward; the first segment matching
175
+ // a known artifact-type directory determines the kind. This handles every
176
+ // nested layout uniformly.
177
+ const segments = normalized.split('/');
178
+ const skipBasenames = new Set(['readme', 'rules-index', 'index']);
179
+ const isMarkdown = /\.md$/i.test(filePath);
180
+ const isTemplateAsset = TEMPLATE_INDEX_EXTENSIONS.some(ext => normalized.endsWith(ext));
181
+ if (!skipBasenames.has(basename) && (isMarkdown || isTemplateAsset)) {
182
+ // Look at directory segments only (exclude the file itself).
183
+ for (let i = segments.length - 2; i >= 0; i--) {
184
+ const seg = segments[i];
185
+ switch (seg) {
186
+ case 'skills': {
187
+ // Skills come in two layouts:
188
+ // slug-style: skills/<slug>/SKILL.md → file is SKILL.md
189
+ // flat-style: skills/<name>.md → file is directly under skills/
190
+ //
191
+ // Do not classify nested reference/support markdown under
192
+ // skills/<slug>/... as standalone skills; those files should remain
193
+ // ordinary artifacts so discovery does not advertise them as
194
+ // invokable capabilities.
195
+ const afterSkills = segments.length - i - 1;
196
+ const isSlugSkill = basename === 'skill' && afterSkills === 2;
197
+ const isFlatSkill = afterSkills === 1;
198
+ if (isSlugSkill || isFlatSkill)
199
+ return 'skill';
200
+ break;
201
+ }
202
+ case 'agents':
203
+ if (isMarkdown)
204
+ return 'agent';
205
+ break;
206
+ case 'commands':
207
+ if (isMarkdown)
208
+ return 'command';
209
+ break;
210
+ case 'rules':
211
+ // Rules/RULES-INDEX.md is a curated index file, not a rule.
212
+ if (basename === 'rules-index' || basename === 'index')
213
+ break;
214
+ if (isMarkdown)
215
+ return 'rule';
216
+ break;
217
+ case 'templates':
218
+ return 'template';
219
+ case 'behaviors':
220
+ if (isMarkdown)
221
+ return 'behavior';
222
+ break;
223
+ case 'hooks':
224
+ if (isMarkdown)
225
+ return 'hook';
226
+ break;
227
+ }
228
+ }
229
+ }
230
+ // Legacy SDLC artifact heuristics (existing behavior preserved).
231
+ if (basename.startsWith('uc-') || basename.includes('use-case'))
232
+ return 'use-case';
233
+ if (basename.startsWith('adr-') || basename.includes('adr'))
234
+ return 'adr';
235
+ if (basename.startsWith('tp-') || basename.includes('test-plan'))
236
+ return 'test-plan';
237
+ if (basename.startsWith('tc-') || basename.includes('test-case'))
238
+ return 'test-case';
239
+ if (basename.startsWith('tm-') || basename.includes('threat'))
240
+ return 'threat-model';
241
+ if (basename.startsWith('nfr-') || basename.includes('nfr'))
242
+ return 'nfr';
243
+ if (basename.includes('sad') || basename.includes('architecture'))
244
+ return 'architecture';
245
+ if (basename.includes('risk'))
246
+ return 'risk';
247
+ if (basename.includes('deploy'))
248
+ return 'deployment';
249
+ return 'document';
250
+ }
251
+ const RUNBOOK_ACTION_HEADING = /\b(procedure|steps?|recovery steps?|installation|setup|start|stop|containment|remediation|response actions?|execution)\b/i;
252
+ const RUNBOOK_CONTROL_HEADING = /\b(prerequisites?|verification|validation|rollback|health check|troubleshooting|monitoring|escalation|post-recovery|evidence|postmortem|diagnosis)\b/i;
253
+ function markdownSections(body) {
254
+ const matches = [...body.matchAll(/^#{2,4}\s+(.+)$/gm)];
255
+ return matches.map((match, index) => ({
256
+ heading: match[1].replace(/\s+#+\s*$/, '').trim(),
257
+ body: body.slice((match.index ?? 0) + match[0].length, matches[index + 1]?.index ?? body.length).trim(),
258
+ }));
259
+ }
260
+ function compactSectionText(text, maxLength = 240) {
261
+ const blocks = text
262
+ .replace(/```[\s\S]*?```/g, ' ')
263
+ .split(/\n\s*\n/)
264
+ .map(block => block
265
+ .replace(/^\s*[-*+]\s+/gm, '')
266
+ .replace(/^\s*\|.*\|\s*$/gm, '')
267
+ .replace(/`([^`]+)`/g, '$1')
268
+ .replace(/\s+/g, ' ')
269
+ .trim())
270
+ .filter(Boolean);
271
+ const first = blocks.find(block => !block.startsWith('#'));
272
+ return first ? first.slice(0, maxLength) : undefined;
273
+ }
274
+ /**
275
+ * Detect procedural Markdown runbooks without promoting every runbook-shaped
276
+ * template. Explicit `type: runbook` is authoritative; otherwise a runbook
277
+ * marker must be accompanied by both an action section and a control/validation
278
+ * section. The extracted terms favor the operational sections people search
279
+ * for (procedure, verification, rollback, diagnosis, remediation, and so on).
280
+ */
281
+ export function parseRunbookDoc(data, body, filePath) {
282
+ if (!/\.md$/i.test(filePath))
283
+ return null;
284
+ const title = extractTitle(data, body);
285
+ const explicit = typeof data.type === 'string' && data.type.toLowerCase() === 'runbook';
286
+ const hasMarker = /\brun[- ]?book\b/i.test(`${filePath} ${title}`);
287
+ const sections = markdownSections(body);
288
+ const headings = sections.map(section => section.heading);
289
+ const hasAction = headings.some(heading => RUNBOOK_ACTION_HEADING.test(heading));
290
+ const hasControl = headings.some(heading => RUNBOOK_CONTROL_HEADING.test(heading));
291
+ if (!explicit && !(hasMarker && hasAction && hasControl))
292
+ return null;
293
+ const preferredCapability = sections.find(section => /\b(purpose|overview|objective|intent|scope)\b/i.test(section.heading));
294
+ const lifecycleHeadings = sections
295
+ .map(section => section.heading)
296
+ .filter(heading => RUNBOOK_ACTION_HEADING.test(heading) || RUNBOOK_CONTROL_HEADING.test(heading));
297
+ const lifecycleCapability = lifecycleHeadings.length > 0
298
+ ? `${title}: ${lifecycleHeadings.slice(0, 8).join('; ')}`.slice(0, 240)
299
+ : undefined;
300
+ const capability = (typeof data.description === 'string' ? data.description.trim().slice(0, 240) : undefined)
301
+ ?? (preferredCapability ? compactSectionText(preferredCapability.body) : undefined)
302
+ ?? lifecycleCapability
303
+ ?? extractCapability(data, body);
304
+ const terms = new Set(['runbook']);
305
+ const relevantHeading = new RegExp(`${RUNBOOK_ACTION_HEADING.source}|${RUNBOOK_CONTROL_HEADING.source}|\\b(purpose|overview|objective|intent|scope|incident classification|communication|monitoring|symptoms?)\\b`, 'i');
306
+ for (const section of sections) {
307
+ if (!relevantHeading.test(section.heading))
308
+ continue;
309
+ terms.add(section.heading);
310
+ const compact = compactSectionText(section.body, 320);
311
+ if (compact)
312
+ terms.add(compact);
313
+ if (terms.size >= 40)
314
+ break;
315
+ }
316
+ return {
317
+ kind: 'Runbook',
318
+ capability,
319
+ searchTerms: [...terms],
320
+ };
321
+ }
322
+ /**
323
+ * Extract trigger phrases from a SKILL.md / agent body.
324
+ *
325
+ * Skills declare alternate activation phrases under a `## Triggers`
326
+ * heading; the body typically lists them as bullet points. This
327
+ * function pulls each bullet's leading phrase (the part before any
328
+ * `→` arrow or em-dash explanation), lowercased and trimmed.
329
+ *
330
+ * Returns an empty array when no `## Triggers` section is found —
331
+ * non-skill artifacts get `triggers: undefined` after this is wired.
332
+ *
333
+ * @implements #1214
334
+ */
335
+ export function extractTriggers(body, frontmatter) {
336
+ const phrases = [];
337
+ const declaredTriggers = Array.isArray(frontmatter?.triggers)
338
+ ? frontmatter.triggers
339
+ : [];
340
+ for (const trigger of declaredTriggers) {
341
+ if (typeof trigger !== 'string')
342
+ continue;
343
+ const phrase = trigger.trim().toLowerCase();
344
+ if (phrase.length === 0)
345
+ continue;
346
+ if (phrase.length > 200)
347
+ continue;
348
+ phrases.push(phrase);
349
+ }
350
+ // Find a triggers heading (case-insensitive). Accepted variants:
351
+ // ## Triggers
352
+ // ## Natural Language Triggers (used by orchestration skills)
353
+ // ## Activation Phrases (alternate vocabulary)
354
+ // ## When to invoke (intent-style)
355
+ // Capture the section content until the next `## ` heading or EOF.
356
+ // Note: avoid the multi-line `m` flag with `$` — `$` would match
357
+ // every line terminator and stop capture at the first blank line.
358
+ const sectionMatch = body.match(/(?:^|\n)##\s+(?:(?:Natural\s+Language\s+)?Triggers|Activation\s+Phrases|When\s+to\s+invoke)\b[^\n]*\n([\s\S]*?)(?=\n##\s|$)/i);
359
+ if (!sectionMatch)
360
+ return [...new Set(phrases)];
361
+ const section = sectionMatch[1];
362
+ for (const rawLine of section.split('\n')) {
363
+ const line = rawLine.trim();
364
+ if (!line.startsWith('-') && !line.startsWith('*') && !line.startsWith('+'))
365
+ continue;
366
+ // Strip the bullet marker, optional surrounding quotes, and split on
367
+ // common explanation separators ("→", " — ", " - " when followed by
368
+ // explanatory text).
369
+ let phrase = line.replace(/^[-*+]\s+/, '').trim();
370
+ // Drop any leading quote characters
371
+ phrase = phrase.replace(/^["“”'`]+/, '').trim();
372
+ // Cut at the first explanation separator
373
+ const sepMatch = phrase.match(/^(.*?)\s*(?:→|—|--|\s-\s)/);
374
+ if (sepMatch)
375
+ phrase = sepMatch[1];
376
+ // Strip trailing quotes / colons
377
+ phrase = phrase.replace(/["“”'`:.]+\s*$/, '').trim();
378
+ if (phrase.length === 0)
379
+ continue;
380
+ if (phrase.length > 200)
381
+ continue; // Reject pathological lines
382
+ phrases.push(phrase.toLowerCase());
383
+ }
384
+ return [...new Set(phrases)];
385
+ }
386
+ /**
387
+ * Extract a capability summary for a skill/agent/command/rule.
388
+ *
389
+ * Prefers the frontmatter `description` field (used uniformly across
390
+ * AIWG SKILL.md / agent files). Falls back to the first non-heading
391
+ * paragraph of the body. Capped at 240 chars so the index stays
392
+ * token-tight when surfaced via `aiwg index discover`.
393
+ *
394
+ * @implements #1214
395
+ */
396
+ export function extractCapability(data, body) {
397
+ if (typeof data.description === 'string' && data.description.trim().length > 0) {
398
+ return data.description.trim().slice(0, 240);
399
+ }
400
+ // Fallback: first non-empty paragraph that isn't a heading.
401
+ const stripped = body.replace(/^---\n[\s\S]*?\n---\n/, '');
402
+ for (const block of stripped.split(/\n\s*\n/)) {
403
+ const trimmed = block.trim();
404
+ if (!trimmed || trimmed.startsWith('#'))
405
+ continue;
406
+ return trimmed.replace(/\s+/g, ' ').slice(0, 240);
407
+ }
408
+ return undefined;
409
+ }
410
+ /**
411
+ * Extract a SkillScriptSpec from skill frontmatter (#1227).
412
+ *
413
+ * The `script:` block is optional — only skills with a backing executable
414
+ * declare it. Schema:
415
+ *
416
+ * script:
417
+ * entrypoint: scripts/voice_loader.py # required, relative to skill dir
418
+ * runtime: python3 # required (node|python3|bash|...)
419
+ * cwd: project-root # optional, default project-root
420
+ * argsHint: "--voice <name> --input <path>" # optional UX hint
421
+ *
422
+ * Returns undefined when the block is absent or malformed. Malformed
423
+ * blocks are silently dropped — index builder logs a warning so authors
424
+ * see it, but the artifact still indexes as a non-executable skill.
425
+ */
426
+ export function extractSkillScript(data) {
427
+ const raw = data.script;
428
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw))
429
+ return undefined;
430
+ const block = raw;
431
+ const entrypoint = typeof block.entrypoint === 'string' ? block.entrypoint.trim() : '';
432
+ const runtime = typeof block.runtime === 'string' ? block.runtime.trim() : '';
433
+ if (!entrypoint || !runtime)
434
+ return undefined;
435
+ const cwdRaw = typeof block.cwd === 'string' ? block.cwd.trim() : 'project-root';
436
+ const cwd = cwdRaw === 'skill-dir' || cwdRaw === 'aiwg-root' ? cwdRaw : 'project-root';
437
+ const argsHint = typeof block.argsHint === 'string' ? block.argsHint.trim() : undefined;
438
+ return {
439
+ entrypoint,
440
+ runtime,
441
+ cwd,
442
+ ...(argsHint ? { argsHint } : {}),
443
+ };
444
+ }
445
+ /**
446
+ * Compute truncated SHA-256 checksum (16 hex chars)
447
+ */
448
+ function computeChecksum(content) {
449
+ return createHash('sha256').update(content).digest('hex').slice(0, 16);
450
+ }
451
+ /**
452
+ * Convert Python-style named capture groups (?P<name>...) to JS-style (?<name>...)
453
+ */
454
+ export function normalizeNamedCaptures(pattern) {
455
+ return pattern.replace(/\(\?P</g, '(?<');
456
+ }
457
+ /**
458
+ * Build a MetadataEntry from filename regex captures instead of file content.
459
+ * Used when graphConfig.nodeStrategy === 'filename-metadata'.
460
+ *
461
+ * @implements #723
462
+ */
463
+ export function buildFilenameMetadataEntry(relativePath, fullPath, filenamePattern) {
464
+ const basename = path.basename(fullPath);
465
+ let captures = {};
466
+ if (filenamePattern) {
467
+ const normalizedPattern = normalizeNamedCaptures(filenamePattern);
468
+ const regex = new RegExp(normalizedPattern);
469
+ const match = basename.match(regex);
470
+ if (match?.groups) {
471
+ captures = match.groups;
472
+ }
473
+ }
474
+ const ref = captures.ref ? `REF-${captures.ref}` : '';
475
+ const titleParts = [ref, captures.author, captures.year, captures.slug].filter(Boolean);
476
+ const title = titleParts.length > 0 ? titleParts.join(' — ') : basename;
477
+ // Use file stat for timestamps; checksum is based on filename (content not read)
478
+ const stat = fs.statSync(fullPath);
479
+ const checksum = createHash('sha256').update(basename).digest('hex').slice(0, 16);
480
+ return {
481
+ path: relativePath,
482
+ type: captures.ref ? 'paper' : 'document',
483
+ phase: 'other',
484
+ title,
485
+ tags: [],
486
+ created: stat.birthtime.toISOString(),
487
+ updated: stat.mtime.toISOString(),
488
+ checksum,
489
+ summary: '',
490
+ dependencies: [],
491
+ dependents: [],
492
+ // Store captures as a non-standard field for downstream consumers
493
+ ...(Object.keys(captures).length > 0 ? { captures } : {}),
494
+ };
495
+ }
496
+ /**
497
+ * Apply metadata supplements — merge frontmatter fields from sidecar files.
498
+ *
499
+ * @implements #723
500
+ */
501
+ function applyMetadataSupplements(entries, supplements, cwd) {
502
+ for (const supplement of supplements) {
503
+ const sidecarDir = path.join(cwd, supplement.scanDir);
504
+ if (!fs.existsSync(sidecarDir))
505
+ continue;
506
+ if (!supplement.matchOn || !supplement.nodeKey)
507
+ continue;
508
+ // Parse matchOn: "frontmatter.ref" -> field "ref"
509
+ const matchField = supplement.matchOn.replace(/^frontmatter\./, '');
510
+ // Build a map of sidecar files: matchField value -> frontmatter data
511
+ const sidecarFiles = findArtifactFiles(sidecarDir, ['.md', '.yaml', '.json']);
512
+ const sidecarMap = new Map();
513
+ for (const sidecarPath of sidecarFiles) {
514
+ const content = fs.readFileSync(sidecarPath, 'utf-8');
515
+ const { data } = parseFrontmatter(content);
516
+ const matchValue = data[matchField];
517
+ if (typeof matchValue === 'string') {
518
+ sidecarMap.set(matchValue, data);
519
+ }
520
+ }
521
+ // Match entries to sidecars and merge fields
522
+ for (const entry of Object.values(entries)) {
523
+ const entryCaptures = entry.captures;
524
+ if (!entryCaptures)
525
+ continue;
526
+ const nodeValue = entryCaptures[supplement.nodeKey];
527
+ if (!nodeValue)
528
+ continue;
529
+ // Build the full ref ID for matching (e.g., captures.ref="008" -> "REF-008")
530
+ const matchValue = supplement.nodeKey === 'ref' ? `REF-${nodeValue}` : nodeValue;
531
+ const sidecarData = sidecarMap.get(matchValue);
532
+ if (!sidecarData)
533
+ continue;
534
+ for (const field of supplement.mergeFields) {
535
+ const value = sidecarData[field];
536
+ if (value === undefined)
537
+ continue;
538
+ if (field === 'title' && typeof value === 'string') {
539
+ entry.title = value;
540
+ }
541
+ else if (field === 'tags' && Array.isArray(value)) {
542
+ entry.tags = [...new Set([...entry.tags, ...value.map(String)])];
543
+ }
544
+ else if (field === 'authors' && typeof value === 'string') {
545
+ entry.summary = value;
546
+ }
547
+ // Other fields stored via captures — downstream consumers can access them
548
+ }
549
+ }
550
+ }
551
+ }
552
+ /**
553
+ * Recursively find all indexable files under a directory
554
+ */
555
+ function findArtifactFiles(dir, extensions = [...DEFAULT_INDEX_EXTENSIONS]) {
556
+ const results = [];
557
+ if (!fs.existsSync(dir))
558
+ return results;
559
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
560
+ for (const entry of entries) {
561
+ const fullPath = path.join(dir, entry.name);
562
+ if (entry.isSymbolicLink() && !fs.existsSync(fullPath)) {
563
+ continue;
564
+ }
565
+ if (entry.isDirectory()) {
566
+ // Skip hidden dirs and .index
567
+ if (entry.name.startsWith('.'))
568
+ continue;
569
+ results.push(...findArtifactFiles(fullPath, extensions));
570
+ }
571
+ else if (extensions.some(ext => entry.name.endsWith(ext))) {
572
+ results.push(fullPath);
573
+ }
574
+ }
575
+ return results;
576
+ }
577
+ /**
578
+ * Build the artifact index
579
+ */
580
+ /**
581
+ * Detect an AIWG workflow "Flow" document — the declarative YAML metalanguage
582
+ * (`apiVersion: workflow.aiwg.io/v1`, the legacy `ops.aiwg.io/v1` alias, and
583
+ * domain-extension namespaces like `<domain>.workflow.aiwg.io/v1`). Pure-YAML
584
+ * Flow docs carry no markdown frontmatter, so the standard frontmatter path
585
+ * leaves them unclassified and invisible to discovery. This surfaces them as
586
+ * `type: 'flow'` with discovery metadata drawn from `metadata.name`,
587
+ * `spec.description`, and `metadata.labels` (#1540). Returns null for any file
588
+ * that isn't a recognizable Flow document.
589
+ */
590
+ export function parseFlowDoc(content, filePath) {
591
+ if (!/\.ya?ml$/i.test(filePath))
592
+ return null;
593
+ let doc;
594
+ try {
595
+ doc = loadYaml(content);
596
+ }
597
+ catch {
598
+ return null;
599
+ }
600
+ if (!doc || typeof doc !== 'object')
601
+ return null;
602
+ const d = doc;
603
+ const api = typeof d.apiVersion === 'string' ? d.apiVersion : '';
604
+ // core (workflow.aiwg.io/v1), the forward Flows alias (flow.aiwg.io/v1,
605
+ // #1536), the legacy alias (ops.aiwg.io/v1), and any domain-extension
606
+ // namespace ending in one of those.
607
+ const isFlow = /(^|\.)(workflow|flow|ops)\.aiwg\.io\/v1$/.test(api);
608
+ if (!isFlow)
609
+ return null;
610
+ const kind = typeof d.kind === 'string' ? d.kind.trim() : '';
611
+ // The namespace alone is insufficient: other domain schemas may end in an
612
+ // `ops.aiwg.io` suffix. Accept only the workflow metalanguage resource
613
+ // families and their Workflow/Flow/Ops aliases.
614
+ const isFlowKind = /^(?:(?:Workflow|Flow|Ops)?(?:Capability|Playbook|Inventory|Target|Gate|Role|Extension|Runbook))$/.test(kind);
615
+ if (!isFlowKind)
616
+ return null;
617
+ const meta = (d.metadata && typeof d.metadata === 'object' ? d.metadata : {});
618
+ const spec = (d.spec && typeof d.spec === 'object' ? d.spec : {});
619
+ const labelMap = meta.labels && typeof meta.labels === 'object'
620
+ ? meta.labels
621
+ : {};
622
+ const labels = Object.values(labelMap).map(String);
623
+ const name = typeof meta.name === 'string' ? meta.name : undefined;
624
+ const description = typeof spec.description === 'string' ? spec.description : undefined;
625
+ const searchTerms = new Set([kind, ...Object.keys(labelMap), ...labels]);
626
+ const excludedKeys = new Set(['body', 'content', 'script', 'source', 'template']);
627
+ const collectTerms = (value, key = '', depth = 0) => {
628
+ if (depth > 7 || searchTerms.size >= 80)
629
+ return;
630
+ if (typeof value === 'string') {
631
+ const compact = value.replace(/\s+/g, ' ').trim();
632
+ if (compact && compact.length <= 320)
633
+ searchTerms.add(compact);
634
+ return;
635
+ }
636
+ if (Array.isArray(value)) {
637
+ for (const item of value)
638
+ collectTerms(item, key, depth + 1);
639
+ return;
640
+ }
641
+ if (!value || typeof value !== 'object')
642
+ return;
643
+ for (const [childKey, childValue] of Object.entries(value)) {
644
+ if (excludedKeys.has(childKey.toLowerCase()))
645
+ continue;
646
+ if (/^(id|name|kind|agent|capability|command|action|expect|description|role|inventory|target|group|schema|rollback[_-]?capability)$/i.test(childKey)) {
647
+ searchTerms.add(childKey.replace(/[_-]+/g, ' '));
648
+ }
649
+ collectTerms(childValue, childKey, depth + 1);
650
+ }
651
+ };
652
+ collectTerms(spec);
653
+ const fallbackTerms = [...searchTerms]
654
+ .filter(term => term !== kind && term !== name)
655
+ .slice(0, 4);
656
+ const capability = description
657
+ ?? `${kind.replace(/([a-z])([A-Z])/g, '$1 $2')} ${name ?? ''}${fallbackTerms.length > 0 ? `: ${fallbackTerms.join('; ')}` : ''}`
658
+ .replace(/\s+/g, ' ')
659
+ .trim()
660
+ .slice(0, 240);
661
+ return {
662
+ type: /Runbook$/.test(kind) ? 'runbook' : 'flow',
663
+ kind,
664
+ name,
665
+ description,
666
+ capability,
667
+ tags: labels,
668
+ searchTerms: [...searchTerms],
669
+ };
670
+ }
671
+ export async function buildIndex(cwd, options = {}) {
672
+ const { force = false, verbose = false, scope, outputDir, graph, explicit = true } = options;
673
+ const startTime = Date.now();
674
+ // Ensure user-defined graphs are loaded
675
+ loadUserGraphConfigs(cwd);
676
+ loadGlobalGraphConfigs();
677
+ // Determine scan directories based on graph type
678
+ const graphConfig = graph ? GRAPH_CONFIGS[graph] : undefined;
679
+ let scanDirs;
680
+ let fileExtensions;
681
+ if (scope) {
682
+ // Explicit scope overrides graph config
683
+ scanDirs = [path.join(cwd, scope)];
684
+ fileExtensions = [...DEFAULT_INDEX_EXTENSIONS];
685
+ }
686
+ else if (graphConfig) {
687
+ scanDirs = graphConfig.scanDirs.map(d => resolveGraphScanDir(cwd, d));
688
+ fileExtensions = graphConfig.extensions;
689
+ }
690
+ else {
691
+ // Default: scan .aiwg/ (backward compatible)
692
+ scanDirs = [resolveProjectAiwgDir(cwd)];
693
+ fileExtensions = [...DEFAULT_INDEX_EXTENSIONS];
694
+ }
695
+ // Verify at least one scan directory exists
696
+ const existingDirs = scanDirs.filter(d => fs.existsSync(d));
697
+ if (existingDirs.length === 0) {
698
+ // If this graph was auto-selected rather than explicitly requested via --graph,
699
+ // skip gracefully — docs/corpus repos should not be forced to have every
700
+ // built-in or user-defined graph root when `--all` asks for the available set.
701
+ if (!explicit) {
702
+ const relDirs = scanDirs.map(d => path.relative(cwd, d)).join(', ');
703
+ console.warn(`Warning: ${graph} graph: scan directories not found (${relDirs}), skipping`);
704
+ return;
705
+ }
706
+ console.error(`Error: No scan directories found: ${scanDirs.join(', ')}`);
707
+ console.log('Run this command from a project with the required directories.');
708
+ process.exit(1);
709
+ }
710
+ // Determine output index directory
711
+ let indexOutputDir;
712
+ if (outputDir) {
713
+ // Test/custom override — write to outputDir/.aiwg/.index/ (or graph subdir)
714
+ indexOutputDir = graph
715
+ ? path.join(outputDir, '.aiwg', '.index', graph)
716
+ : path.join(outputDir, INDEX_DIR);
717
+ }
718
+ else if (graph) {
719
+ indexOutputDir = resolveIndexDir(cwd, graph);
720
+ }
721
+ else {
722
+ indexOutputDir = resolveIndexDir(cwd);
723
+ }
724
+ fs.mkdirSync(indexOutputDir, { recursive: true });
725
+ // effectiveOutputCwd is used for backward-compat loadMetadataIndex calls
726
+ const effectiveOutputCwd = outputDir ?? cwd;
727
+ if (graph === 'source') {
728
+ const { buildSourceGraphIndex } = await import('./source-graph.js');
729
+ await buildSourceGraphIndex({
730
+ cwd,
731
+ outputDir: indexOutputDir,
732
+ effectiveOutputCwd,
733
+ verbose,
734
+ });
735
+ const buildTimeMs = Date.now() - startTime;
736
+ console.log(`Source graph built in ${buildTimeMs}ms`);
737
+ console.log(` Output: ${INDEX_DIR}/source/`);
738
+ return;
739
+ }
740
+ // Load existing index for incremental updates
741
+ const existingIndex = force
742
+ ? null
743
+ : outputDir
744
+ ? loadIndexFromDir(indexOutputDir, 'metadata.json')
745
+ : loadGraphIndexFile(effectiveOutputCwd, 'metadata.json', graph);
746
+ const canReuseExisting = existingIndex?.version === INDEX_VERSION
747
+ && existingIndex.extractorVersion === INDEX_EXTRACTOR_VERSION;
748
+ const existingEntries = canReuseExisting ? existingIndex.entries : {};
749
+ // Load checksum manifest for fast stat-based change detection (#794).
750
+ // When --force is set we skip the manifest entirely and rebuild everything.
751
+ const manifest = force || !canReuseExisting
752
+ ? { version: 1, generated: '', entries: {} }
753
+ : loadManifest(indexOutputDir);
754
+ const nextManifestEntries = {};
755
+ const manifestStats = {
756
+ checked: 0,
757
+ statSkipped: 0,
758
+ checksumSkipped: 0,
759
+ reindexed: 0,
760
+ pruned: 0,
761
+ };
762
+ // Collect files from all scan directories
763
+ const files = [];
764
+ for (const dir of existingDirs) {
765
+ files.push(...findArtifactFiles(dir, fileExtensions));
766
+ }
767
+ // WORKSPACE.md is the root of the project context graph. Index it and its
768
+ // local Markdown-linked nodes without copying them into provider trees.
769
+ if (!scope && (!graph || graph === 'project')) {
770
+ const workspacePath = path.join(cwd, 'WORKSPACE.md');
771
+ const contextFiles = [
772
+ ...(fs.existsSync(workspacePath) ? [workspacePath] : []),
773
+ ...await workspaceLinkedFiles(cwd),
774
+ ];
775
+ for (const contextFile of contextFiles) {
776
+ if (fileExtensions.some((extension) => contextFile.endsWith(extension)) && !files.includes(contextFile)) {
777
+ files.push(contextFile);
778
+ }
779
+ }
780
+ }
781
+ const entries = {};
782
+ const tagIndex = {};
783
+ const depGraph = {};
784
+ let newCount = 0;
785
+ let updatedCount = 0;
786
+ let unchangedCount = 0;
787
+ const useFilenameMetadata = graphConfig?.nodeStrategy === 'filename-metadata';
788
+ for (const fullPath of files) {
789
+ const relativePath = indexPathFor(cwd, fullPath, graph);
790
+ let entry;
791
+ if (useFilenameMetadata) {
792
+ // Filename-metadata strategy: derive metadata from filename, skip content read.
793
+ // The checksum manifest doesn't help here (no content read to skip) but we still
794
+ // preserve any existing manifest entry so cross-graph builds don't drop it.
795
+ const checksum = createHash('sha256').update(path.basename(fullPath)).digest('hex').slice(0, 16);
796
+ const existingManifestEntry = manifest.entries[relativePath];
797
+ if (existingManifestEntry) {
798
+ nextManifestEntries[relativePath] = existingManifestEntry;
799
+ }
800
+ // Skip unchanged files in incremental mode
801
+ if (!force && existingEntries[relativePath]?.checksum === checksum) {
802
+ entries[relativePath] = existingEntries[relativePath];
803
+ unchangedCount++;
804
+ if (verbose)
805
+ console.log(` unchanged: ${relativePath}`);
806
+ continue;
807
+ }
808
+ entry = buildFilenameMetadataEntry(relativePath, fullPath, graphConfig?.filenamePattern);
809
+ }
810
+ else {
811
+ // Default strategy: read content, parse frontmatter
812
+ manifestStats.checked++;
813
+ // Phase 1: stat-based quick filter — skip content read if mtime+size match manifest.
814
+ // This is the fast path: unchanged files don't touch the filesystem beyond fs.statSync.
815
+ let stat;
816
+ try {
817
+ stat = fs.statSync(fullPath);
818
+ }
819
+ catch (err) {
820
+ if (err.code === 'ENOENT') {
821
+ manifestStats.pruned++;
822
+ if (verbose)
823
+ console.log(` skipped missing: ${relativePath}`);
824
+ continue;
825
+ }
826
+ throw err;
827
+ }
828
+ const manifestEntry = manifest.entries[relativePath];
829
+ if (!force && statMatches(stat, manifestEntry) && existingEntries[relativePath]?.checksum === manifestEntry?.checksum) {
830
+ // Stat matches manifest AND the stored index entry references the same checksum.
831
+ // Safe to reuse both without reading the file.
832
+ entries[relativePath] = existingEntries[relativePath];
833
+ nextManifestEntries[relativePath] = manifestEntry;
834
+ unchangedCount++;
835
+ manifestStats.statSkipped++;
836
+ if (verbose)
837
+ console.log(` unchanged (stat): ${relativePath}`);
838
+ continue;
839
+ }
840
+ // Phase 2: content read + checksum verification for files that looked changed.
841
+ const content = fs.readFileSync(fullPath, 'utf-8');
842
+ const checksum = computeChecksum(content);
843
+ // Skip unchanged files in incremental mode (checksum matched despite stat drift)
844
+ if (!force && existingEntries[relativePath]?.checksum === checksum) {
845
+ entries[relativePath] = existingEntries[relativePath];
846
+ // Update manifest with current stat so Phase 1 succeeds next time.
847
+ nextManifestEntries[relativePath] = makeEntry(checksum, stat);
848
+ unchangedCount++;
849
+ manifestStats.checksumSkipped++;
850
+ if (verbose)
851
+ console.log(` unchanged (checksum): ${relativePath}`);
852
+ continue;
853
+ }
854
+ manifestStats.reindexed++;
855
+ nextManifestEntries[relativePath] = makeEntry(checksum, stat);
856
+ const { data, body } = parseFrontmatter(content);
857
+ // YAML Flow documents (workflow.aiwg.io/v1) are pure YAML, not
858
+ // markdown+frontmatter — detect them up front so they classify and
859
+ // become discoverable (#1540).
860
+ const flow = parseFlowDoc(content, relativePath);
861
+ const inferredType = inferType(data, relativePath);
862
+ const physicalType = inferType({ ...data, type: undefined }, relativePath);
863
+ const runbook = flow ? null : parseRunbookDoc(data, body, relativePath);
864
+ const title = flow?.name ?? extractTitle(data, body);
865
+ const phase = typeof data.phase === 'string' ? data.phase : inferPhase(relativePath);
866
+ const type = flow?.type ?? (runbook ? 'runbook' : inferredType);
867
+ const tags = flow ? flow.tags : (Array.isArray(data.tags) ? data.tags.map(String) : []);
868
+ const summary = flow?.description ?? runbook?.capability ?? extractSummary(data, body);
869
+ const dependencies = extractMentions(content);
870
+ // Discovery metadata (#1214, #1540, #1792) — meaningful for operational
871
+ // AIWG artifact kinds. Kept undefined on document types so the index file
872
+ // stays small for the common case.
873
+ const isDiscoverable = OPERATIONAL_DISCOVERY_TYPES.includes(type);
874
+ // Declarative processes have no trigger phrases — they rely on their
875
+ // capability and structure-aware search terms.
876
+ const triggers = isDiscoverable && !flow ? extractTriggers(body, data) : undefined;
877
+ const capability = flow?.capability ?? runbook?.capability ?? (isDiscoverable ? extractCapability(data, body) : undefined);
878
+ const kind = flow?.kind ?? runbook?.kind;
879
+ const sourceType = runbook && physicalType !== 'runbook' ? physicalType : undefined;
880
+ const searchTerms = flow?.searchTerms ?? runbook?.searchTerms;
881
+ const kernel = data.kernel === true || data.kernel === 'true' ? true : undefined;
882
+ // Script entrypoint metadata is meaningful for skills only (#1227).
883
+ const script = type === 'skill' ? extractSkillScript(data) : undefined;
884
+ const operationalState = normalizeOperationalState(data.operational_state);
885
+ // Canonical short name (#1233) — used by the scorer to floor exact-name
886
+ // queries to 1.0 so hyphenated kernel-skill names like `aiwg-doctor`
887
+ // remain searchable even when the rendered title strips the hyphen.
888
+ const name = flow ? flow.name : (isDiscoverable ? extractCanonicalName(data, relativePath) : undefined);
889
+ entry = {
890
+ path: relativePath,
891
+ type,
892
+ ...(kind ? { kind } : {}),
893
+ ...(sourceType ? { sourceType } : {}),
894
+ phase,
895
+ title,
896
+ tags,
897
+ created: typeof data.created === 'string' ? data.created : stat.birthtime.toISOString(),
898
+ updated: stat.mtime.toISOString(),
899
+ checksum,
900
+ summary,
901
+ dependencies,
902
+ dependents: [], // Computed after all entries are processed
903
+ ...(name ? { name } : {}),
904
+ ...(triggers && triggers.length > 0 ? { triggers } : {}),
905
+ ...(capability ? { capability } : {}),
906
+ ...(searchTerms && searchTerms.length > 0 ? { searchTerms } : {}),
907
+ ...(kernel ? { kernel } : {}),
908
+ ...(script ? { script } : {}),
909
+ ...(operationalState ? { operationalState } : {}),
910
+ };
911
+ }
912
+ entries[relativePath] = entry;
913
+ if (existingEntries[relativePath]) {
914
+ updatedCount++;
915
+ if (verbose)
916
+ console.log(` updated: ${relativePath}`);
917
+ }
918
+ else {
919
+ newCount++;
920
+ if (verbose)
921
+ console.log(` new: ${relativePath}`);
922
+ }
923
+ }
924
+ // Apply metadata supplements if configured (enriches filename-metadata nodes from sidecars)
925
+ if (graphConfig?.metadataSupplements?.length) {
926
+ applyMetadataSupplements(entries, graphConfig.metadataSupplements, cwd);
927
+ }
928
+ // Build tag reverse index
929
+ for (const entry of Object.values(entries)) {
930
+ for (const tag of entry.tags) {
931
+ if (!tagIndex[tag])
932
+ tagIndex[tag] = [];
933
+ tagIndex[tag].push(entry.path);
934
+ }
935
+ }
936
+ // Build dependency graph and compute dependents
937
+ for (const entry of Object.values(entries)) {
938
+ if (!depGraph[entry.path]) {
939
+ depGraph[entry.path] = { upstream: [], downstream: [] };
940
+ }
941
+ for (const dep of entry.dependencies) {
942
+ // Normalize: check if referenced path exists in the index
943
+ const normalizedDep = Object.keys(entries).find(p => p === dep || p.endsWith(dep));
944
+ if (normalizedDep && normalizedDep !== entry.path) {
945
+ const upEdge = { path: normalizedDep, type: 'depends-on' };
946
+ depGraph[entry.path].upstream.push(upEdge);
947
+ if (!depGraph[normalizedDep]) {
948
+ depGraph[normalizedDep] = { upstream: [], downstream: [] };
949
+ }
950
+ const downEdge = { path: entry.path, type: 'depends-on' };
951
+ depGraph[normalizedDep].downstream.push(downEdge);
952
+ // Also update the dependents field on the target entry
953
+ if (entries[normalizedDep]) {
954
+ entries[normalizedDep].dependents.push(entry.path);
955
+ }
956
+ }
957
+ }
958
+ }
959
+ // Run citation sidecar edge extraction if configured
960
+ if (graphConfig?.edgeExtraction?.parser === 'citation-sidecar') {
961
+ // Build REF-XXX → path map from all entries with ref frontmatter
962
+ const entryFrontmatter = new Map();
963
+ for (const entryPath of Object.keys(entries)) {
964
+ const fullPath = absoluteEntryPath(cwd, entryPath, graph);
965
+ if (fs.existsSync(fullPath)) {
966
+ const content = fs.readFileSync(fullPath, 'utf-8');
967
+ const { data } = parseFrontmatter(content);
968
+ entryFrontmatter.set(entryPath, data);
969
+ }
970
+ }
971
+ const refToPath = buildRefToPathMap(entryFrontmatter);
972
+ // Parse each entry as a citation sidecar and extract edges
973
+ let citationEdgeCount = 0;
974
+ for (const entryPath of Object.keys(entries)) {
975
+ const fullPath = absoluteEntryPath(cwd, entryPath, graph);
976
+ if (!fs.existsSync(fullPath))
977
+ continue;
978
+ const content = fs.readFileSync(fullPath, 'utf-8');
979
+ const result = parseCitationSidecar(content);
980
+ if (!result)
981
+ continue;
982
+ const edges = citationResultToEdges(result, refToPath);
983
+ if (!depGraph[entryPath]) {
984
+ depGraph[entryPath] = { upstream: [], downstream: [] };
985
+ }
986
+ // Add upstream "cites" edges
987
+ for (const edge of edges.upstream) {
988
+ depGraph[entryPath].upstream.push(edge);
989
+ // Add reciprocal downstream edge on the target
990
+ if (!depGraph[edge.path]) {
991
+ depGraph[edge.path] = { upstream: [], downstream: [] };
992
+ }
993
+ depGraph[edge.path].downstream.push({ path: entryPath, type: 'cites' });
994
+ citationEdgeCount++;
995
+ }
996
+ // Add downstream "cited-by" edges
997
+ for (const edge of edges.downstream) {
998
+ depGraph[entryPath].downstream.push(edge);
999
+ // Add reciprocal upstream edge on the source
1000
+ if (!depGraph[edge.path]) {
1001
+ depGraph[edge.path] = { upstream: [], downstream: [] };
1002
+ }
1003
+ depGraph[edge.path].upstream.push({ path: entryPath, type: 'cited-by' });
1004
+ citationEdgeCount++;
1005
+ }
1006
+ }
1007
+ if (verbose && citationEdgeCount > 0) {
1008
+ console.log(` citation edges: ${citationEdgeCount}`);
1009
+ }
1010
+ }
1011
+ // Deduplicate dependents
1012
+ for (const entry of Object.values(entries)) {
1013
+ entry.dependents = [...new Set(entry.dependents)];
1014
+ }
1015
+ const buildTimeMs = Date.now() - startTime;
1016
+ // Write index files
1017
+ const index = {
1018
+ version: INDEX_VERSION,
1019
+ extractorVersion: INDEX_EXTRACTOR_VERSION,
1020
+ builtAt: new Date().toISOString(),
1021
+ buildTimeMs,
1022
+ entries,
1023
+ };
1024
+ writeIndexFile(effectiveOutputCwd, 'metadata.json', index, indexOutputDir);
1025
+ writeIndexFile(effectiveOutputCwd, 'tags.json', tagIndex, indexOutputDir);
1026
+ writeIndexFile(effectiveOutputCwd, 'dependencies.json', depGraph, indexOutputDir);
1027
+ // Update and persist the checksum manifest for faster future builds (#794).
1028
+ // The next manifest contains entries for every file we processed this build.
1029
+ // Files that disappeared from disk are pruned; the resulting manifest is
1030
+ // written atomically.
1031
+ const nextManifest = {
1032
+ version: 1,
1033
+ generated: new Date().toISOString(),
1034
+ entries: nextManifestEntries,
1035
+ };
1036
+ manifestStats.pruned = Object.keys(manifest.entries).length - Object.keys(nextManifestEntries).length;
1037
+ if (manifestStats.pruned < 0)
1038
+ manifestStats.pruned = 0;
1039
+ writeManifest(indexOutputDir, nextManifest);
1040
+ // Write stats
1041
+ const totalEdges = Object.values(depGraph).reduce((sum, node) => sum + node.downstream.length, 0);
1042
+ const orphaned = Object.entries(depGraph).filter(([, node]) => node.upstream.length === 0 && node.downstream.length === 0).length;
1043
+ const mostReferenced = Object.entries(depGraph)
1044
+ .map(([p, node]) => ({ path: p, count: node.downstream.length }))
1045
+ .sort((a, b) => b.count - a.count)[0] ?? null;
1046
+ const byPhase = {};
1047
+ const byType = {};
1048
+ const tagDist = {};
1049
+ for (const entry of Object.values(entries)) {
1050
+ byPhase[entry.phase] = (byPhase[entry.phase] || 0) + 1;
1051
+ byType[entry.type] = (byType[entry.type] || 0) + 1;
1052
+ for (const tag of entry.tags) {
1053
+ tagDist[tag] = (tagDist[tag] || 0) + 1;
1054
+ }
1055
+ }
1056
+ writeIndexFile(effectiveOutputCwd, 'stats.json', {
1057
+ version: INDEX_VERSION,
1058
+ extractorVersion: INDEX_EXTRACTOR_VERSION,
1059
+ builtAt: new Date().toISOString(),
1060
+ buildTimeMs,
1061
+ totalArtifacts: Object.keys(entries).length,
1062
+ byPhase,
1063
+ byType,
1064
+ tagDistribution: tagDist,
1065
+ graphMetrics: {
1066
+ totalEdges,
1067
+ orphanedArtifacts: orphaned,
1068
+ mostReferenced,
1069
+ },
1070
+ }, indexOutputDir);
1071
+ // Report
1072
+ const total = Object.keys(entries).length;
1073
+ console.log(`Artifact index built in ${buildTimeMs}ms`);
1074
+ console.log(` Indexed ${newCount} new, updated ${updatedCount}, unchanged ${unchangedCount}`);
1075
+ console.log(` Total: ${total} artifacts`);
1076
+ // Report manifest-driven change detection stats (only meaningful when !force
1077
+ // and we actually went through the content-read path for at least one file)
1078
+ if (!force && manifestStats.checked > 0) {
1079
+ const fastPath = manifestStats.statSkipped;
1080
+ const slowPath = manifestStats.checked - manifestStats.statSkipped;
1081
+ console.log(` Change detection: ${manifestStats.checked} checked, ${fastPath} skipped via stat, ${slowPath} content-read, ${manifestStats.reindexed} re-indexed`);
1082
+ if (manifestStats.pruned > 0) {
1083
+ console.log(` Pruned ${manifestStats.pruned} stale manifest entries (files no longer on disk)`);
1084
+ }
1085
+ }
1086
+ const displayDir = graph ? indexOutputDir : `${INDEX_DIR}/`;
1087
+ console.log(` Output: ${displayDir}`);
1088
+ }
1089
+ //# sourceMappingURL=index-builder.js.map