akm-cli 0.9.1 → 0.9.2-alpha.2

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 (350) hide show
  1. package/CHANGELOG.md +103 -28
  2. package/README.md +3 -1
  3. package/SECURITY.md +1 -1
  4. package/STABILITY.md +1 -1
  5. package/dist/akm +2 -2
  6. package/dist/akm-migrate +2 -2
  7. package/dist/assets/hints/cli-hints-full.md +14 -9
  8. package/dist/assets/improve-strategies/proactive-maintenance.json +1 -1
  9. package/dist/assets/improve-strategies/reflect-distill.json +1 -1
  10. package/dist/assets/models.json +35 -0
  11. package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +3 -4
  12. package/dist/assets/stash-skeleton/facts/conventions/organization.md +1 -3
  13. package/dist/assets/tasks/core/extract.yml +6 -5
  14. package/dist/assets/tasks/core/improve.yml +6 -5
  15. package/dist/assets/tasks/core/index-refresh.yml +6 -5
  16. package/dist/assets/tasks/core/sync.yml +6 -5
  17. package/dist/assets/tasks/core/version-check.yml +6 -5
  18. package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +6 -5
  19. package/dist/assets/tasks/improve/akm-improve-catchup.yml +6 -5
  20. package/dist/assets/tasks/improve/akm-improve-consolidate.yml +6 -5
  21. package/dist/assets/tasks/improve/akm-improve-frequent.yml +6 -5
  22. package/dist/assets/tasks/improve/akm-improve-nightly.yml +6 -5
  23. package/dist/cli/confirm.js +2 -2
  24. package/dist/cli/parse-args.js +3 -24
  25. package/dist/cli/retired-commands.js +1 -1
  26. package/dist/cli/shared.js +2 -2
  27. package/dist/cli.js +11 -9
  28. package/dist/commands/agent/agent-dispatch.js +55 -89
  29. package/dist/commands/agent/contribute-cli.js +12 -45
  30. package/dist/commands/command/builtin-action.js +32 -0
  31. package/dist/commands/command/command-cli.js +99 -0
  32. package/dist/commands/command/command-execution.js +308 -0
  33. package/dist/commands/command/execution-source-loader.js +176 -0
  34. package/dist/commands/command/portable-template.js +60 -0
  35. package/dist/commands/config-cli.js +10 -4
  36. package/dist/commands/env/env.js +4 -2
  37. package/dist/commands/feedback-cli.js +1 -1
  38. package/dist/commands/health/checks.js +241 -29
  39. package/dist/commands/health/html-report.js +0 -14
  40. package/dist/commands/health/report-view-model.js +0 -1
  41. package/dist/commands/health/surfaces.js +6 -7
  42. package/dist/commands/health/types.js +0 -2
  43. package/dist/commands/health.js +63 -18
  44. package/dist/commands/improve/collapse-detector.js +5 -6
  45. package/dist/commands/improve/consolidate.js +251 -214
  46. package/dist/commands/improve/distill/promote-memory.js +71 -34
  47. package/dist/commands/improve/distill/quality-gate.js +17 -5
  48. package/dist/commands/improve/distill.js +232 -155
  49. package/dist/commands/improve/eligibility.js +112 -79
  50. package/dist/commands/improve/execution.js +57 -0
  51. package/dist/commands/improve/extract-cli.js +5 -5
  52. package/dist/commands/improve/extract-prompt.js +64 -22
  53. package/dist/commands/improve/extract.js +608 -360
  54. package/dist/commands/improve/improve-strategies.js +43 -14
  55. package/dist/commands/improve/improve.js +249 -29
  56. package/dist/commands/improve/loop-stages.js +11 -17
  57. package/dist/commands/improve/memory/memory-contradiction-detect.js +90 -66
  58. package/dist/commands/improve/outcome-loop.js +22 -38
  59. package/dist/commands/improve/planner.js +134 -0
  60. package/dist/commands/improve/preparation.js +730 -409
  61. package/dist/commands/improve/reflect.js +386 -223
  62. package/dist/commands/improve/run-context.js +3 -4
  63. package/dist/commands/improve/salience.js +6 -58
  64. package/dist/commands/improve/session-asset.js +12 -12
  65. package/dist/commands/lint/index.js +101 -29
  66. package/dist/commands/migrate-cli.js +11 -69
  67. package/dist/commands/migration-tool.js +6 -9
  68. package/dist/commands/models-cli.js +27 -0
  69. package/dist/commands/proposal/drain.js +258 -186
  70. package/dist/commands/proposal/proposal-cli.js +32 -10
  71. package/dist/commands/proposal/proposal.js +2 -5
  72. package/dist/commands/proposal/propose.js +192 -172
  73. package/dist/commands/proposal/repository.js +54 -91
  74. package/dist/commands/proposal/validators/proposal-validators.js +9 -7
  75. package/dist/commands/read/curate.js +53 -22
  76. package/dist/commands/read/registry-search.js +25 -9
  77. package/dist/commands/read/remember-cli.js +14 -2
  78. package/dist/commands/read/search.js +10 -4
  79. package/dist/commands/read/show.js +139 -153
  80. package/dist/commands/registry-cli.js +16 -7
  81. package/dist/commands/remember.js +33 -18
  82. package/dist/commands/sources/add-cli.js +19 -178
  83. package/dist/commands/sources/bundle-cli.js +15 -3
  84. package/dist/commands/sources/dangerous-env-audit.js +135 -0
  85. package/dist/commands/sources/info.js +2 -1
  86. package/dist/commands/sources/installed-stashes.js +901 -177
  87. package/dist/commands/sources/schema-repair.js +174 -95
  88. package/dist/commands/sources/self-update.js +30 -74
  89. package/dist/commands/sources/source-add.js +3 -5
  90. package/dist/commands/sources/sources-cli.js +2 -15
  91. package/dist/commands/sources/update-transaction.js +220 -0
  92. package/dist/commands/tasks/tasks-cli.js +3 -3
  93. package/dist/commands/tasks/tasks.js +736 -317
  94. package/dist/commands/workflow-cli.js +2 -2
  95. package/dist/core/adapter/adapters/agent-skills-adapter.js +3 -0
  96. package/dist/core/adapter/adapters/akm-adapter.js +85 -35
  97. package/dist/core/adapter/adapters/akm-lint.js +54 -39
  98. package/dist/core/adapter/adapters/akm-metadata.js +45 -45
  99. package/dist/core/adapter/adapters/akm-task-adapter.js +32 -49
  100. package/dist/core/adapter/adapters/akm-workflow-adapter.js +38 -23
  101. package/dist/core/adapter/adapters/dotenv-adapter.js +30 -1
  102. package/dist/core/adapter/adapters/generic-files-adapter.js +11 -0
  103. package/dist/core/adapter/adapters/index.js +0 -9
  104. package/dist/core/adapter/adapters/llm-wiki-adapter.js +4 -0
  105. package/dist/core/adapter/adapters/okf-adapter.js +4 -0
  106. package/dist/core/adapter/adapters/opencode-adapter.js +5 -8
  107. package/dist/core/adapter/adapters/tool-dir-shared.js +63 -6
  108. package/dist/core/adapter/adapters/website-snapshot-adapter.js +4 -0
  109. package/dist/core/adapter/execution-source.js +308 -0
  110. package/dist/core/adapter/recognize-match.js +36 -13
  111. package/dist/core/adapter/registry.js +0 -9
  112. package/dist/core/asset/stash-meta.js +94 -4
  113. package/dist/core/common.js +6 -11
  114. package/dist/core/config/config-io.js +3 -3
  115. package/dist/core/config/config-schema.js +18 -40
  116. package/dist/core/config/config-sources.js +11 -21
  117. package/dist/core/config/config-walker.js +31 -13
  118. package/dist/core/config/config.js +23 -26
  119. package/dist/core/config/schema/engines.js +8 -7
  120. package/dist/core/config/schema/improve-processes.js +29 -5
  121. package/dist/core/config/schema/index-config.js +0 -27
  122. package/dist/core/config/schema/primitives.js +1 -23
  123. package/dist/core/config/schema/sources-bundles.js +13 -16
  124. package/dist/core/errors.js +2 -0
  125. package/dist/core/events.js +68 -32
  126. package/dist/core/extra-params.js +1 -0
  127. package/dist/core/improve-result.js +315 -0
  128. package/dist/core/lesson-lint.js +0 -6
  129. package/dist/core/maintenance-barrier.js +4 -4
  130. package/dist/core/network-policy.js +152 -0
  131. package/dist/core/paths.js +1 -1
  132. package/dist/core/recognition-util.js +4 -4
  133. package/dist/core/registry-url.js +456 -0
  134. package/dist/core/state/migrations.js +161 -47
  135. package/dist/core/state-db.js +453 -80
  136. package/dist/core/system-error.js +32 -0
  137. package/dist/core/time.js +2 -12
  138. package/dist/core/write-source.js +0 -18
  139. package/dist/execution/directory-identity.js +52 -0
  140. package/dist/execution/executable-identity.js +107 -0
  141. package/dist/execution/guarded-source.js +398 -0
  142. package/dist/execution/json.js +95 -0
  143. package/dist/{commands/health/types-session-log.js → execution/limits.js} +2 -1
  144. package/dist/execution/record.js +55 -0
  145. package/dist/execution/resolved-request.js +730 -0
  146. package/dist/execution/source.js +320 -0
  147. package/dist/indexer/bundle-identity-guard.js +5 -4
  148. package/dist/indexer/db/graph-db.js +33 -0
  149. package/dist/indexer/graph/graph-boost.js +3 -4
  150. package/dist/indexer/graph/graph-extraction.js +562 -373
  151. package/dist/indexer/index-written-assets.js +78 -39
  152. package/dist/indexer/indexer.js +471 -432
  153. package/dist/indexer/installations.js +6 -0
  154. package/dist/indexer/lookup/adapter-concept-owner.js +283 -0
  155. package/dist/indexer/materialize-embeddings.js +155 -0
  156. package/dist/indexer/passes/memory-inference.js +227 -174
  157. package/dist/indexer/passes/metadata.js +263 -118
  158. package/dist/indexer/scan/doc-to-entry.js +7 -10
  159. package/dist/indexer/scan/drain-dir.js +51 -23
  160. package/dist/indexer/search/db-search.js +156 -50
  161. package/dist/indexer/search/fts-query.js +40 -40
  162. package/dist/indexer/search/ranking.js +36 -1
  163. package/dist/indexer/search/search-attribution.js +3 -1
  164. package/dist/indexer/search/search-fields.js +23 -14
  165. package/dist/indexer/search/search-hit-enrichers.js +1 -1
  166. package/dist/indexer/search/search-source.js +7 -16
  167. package/dist/indexer/search/semantic-status.js +10 -1
  168. package/dist/indexer/usage/show-usage.js +105 -0
  169. package/dist/indexer/usage/usage-events.js +7 -2
  170. package/dist/indexer/walk/matchers.js +40 -10
  171. package/dist/indexer/walk/path-resolver.js +5 -2
  172. package/dist/indexer/walk/walker.js +20 -2
  173. package/dist/integrations/agent/builder-shared.js +3 -6
  174. package/dist/integrations/agent/conversation-fallback.js +16 -0
  175. package/dist/integrations/agent/engine-resolution.js +87 -87
  176. package/dist/integrations/agent/execution-cascade.js +566 -0
  177. package/dist/integrations/agent/execution-definitions.js +211 -0
  178. package/dist/integrations/agent/execution-lowering.js +811 -0
  179. package/dist/integrations/agent/execution-preparation.js +67 -0
  180. package/dist/integrations/agent/index.js +0 -2
  181. package/dist/integrations/agent/inline-execution.js +74 -0
  182. package/dist/integrations/agent/model-map.js +515 -0
  183. package/dist/integrations/agent/persona-fallback.js +30 -0
  184. package/dist/integrations/agent/request-lowering.js +186 -0
  185. package/dist/integrations/agent/runner-dispatch.js +230 -37
  186. package/dist/integrations/agent/runner.js +12 -83
  187. package/dist/integrations/harnesses/aider/agent-builder.js +8 -0
  188. package/dist/integrations/harnesses/aider/index.js +0 -1
  189. package/dist/integrations/harnesses/amazonq/agent-builder.js +8 -0
  190. package/dist/integrations/harnesses/amazonq/index.js +0 -1
  191. package/dist/integrations/harnesses/claude/agent-builder.js +14 -1
  192. package/dist/integrations/harnesses/claude/index.js +1 -5
  193. package/dist/integrations/harnesses/claude/session-log.js +3 -33
  194. package/dist/integrations/harnesses/codex/agent-builder.js +8 -0
  195. package/dist/integrations/harnesses/codex/index.js +0 -1
  196. package/dist/integrations/harnesses/copilot/agent-builder.js +8 -0
  197. package/dist/integrations/harnesses/copilot/index.js +0 -1
  198. package/dist/integrations/harnesses/gemini/agent-builder.js +8 -0
  199. package/dist/integrations/harnesses/gemini/index.js +0 -1
  200. package/dist/integrations/harnesses/index.js +4 -44
  201. package/dist/integrations/harnesses/opencode/agent-builder.js +16 -9
  202. package/dist/integrations/harnesses/opencode/index.js +0 -2
  203. package/dist/integrations/harnesses/opencode/session-log.js +14 -204
  204. package/dist/integrations/harnesses/opencode-sdk/harness.js +12 -1
  205. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +40 -42
  206. package/dist/integrations/harnesses/openhands/agent-builder.js +8 -0
  207. package/dist/integrations/harnesses/openhands/index.js +0 -1
  208. package/dist/integrations/harnesses/pi/agent-builder.js +8 -0
  209. package/dist/integrations/harnesses/pi/index.js +0 -1
  210. package/dist/integrations/harnesses/shared.js +0 -1
  211. package/dist/integrations/harnesses/types.js +1 -3
  212. package/dist/integrations/lockfile.js +82 -79
  213. package/dist/integrations/session-logs/index.js +6 -17
  214. package/dist/integrations/session-logs/provider-base.js +1 -29
  215. package/dist/llm/client.js +10 -5
  216. package/dist/llm/embedder.js +6 -7
  217. package/dist/llm/embedders/local.js +37 -88
  218. package/dist/llm/embedders/types.js +1 -1
  219. package/dist/llm/graph-extract.js +75 -50
  220. package/dist/llm/index-passes.js +43 -5
  221. package/dist/llm/memory-infer.js +8 -6
  222. package/dist/llm/metadata-enhance.js +5 -3
  223. package/dist/llm/structured-call.js +122 -25
  224. package/dist/output/format-exempt.js +1 -1
  225. package/dist/output/render-registry.js +0 -16
  226. package/dist/output/renderers.js +12 -7
  227. package/dist/output/shapes/curate.js +1 -0
  228. package/dist/output/shapes/helpers.js +10 -2
  229. package/dist/output/shapes/passthrough.js +2 -0
  230. package/dist/output/text/command-format.js +31 -33
  231. package/dist/output/text/health-format.js +1 -29
  232. package/dist/output/text/migrate.js +6 -56
  233. package/dist/output/text/proposal-format.js +16 -1
  234. package/dist/output/text/workflow-format.js +16 -0
  235. package/dist/registry/network.js +279 -0
  236. package/dist/registry/pinned-request-helper.js +247 -0
  237. package/dist/registry/pinned-transport.js +717 -0
  238. package/dist/registry/providers/skills-sh.js +18 -6
  239. package/dist/registry/providers/static-index.js +20 -7
  240. package/dist/registry/resolve.js +53 -28
  241. package/dist/scripts/akm-migrate-node.js +19334 -52269
  242. package/dist/scripts/akm-migrate.js +19270 -51612
  243. package/dist/setup/registry-stash-loader.js +64 -20
  244. package/dist/setup/semantic-assets.js +9 -34
  245. package/dist/setup/setup.js +12 -30
  246. package/dist/setup/source-identity.js +17 -0
  247. package/dist/setup/steps/sources.js +36 -15
  248. package/dist/setup/steps/tasks.js +39 -11
  249. package/dist/sources/providers/git-provider.js +3 -3
  250. package/dist/sources/providers/npm.js +2 -2
  251. package/dist/sources/providers/provider-utils.js +4 -3
  252. package/dist/sources/providers/website.js +11 -7
  253. package/dist/sources/snapshot-fetchers/host-guard.js +9 -136
  254. package/dist/sources/snapshot-fetchers/website-ingest.js +25 -109
  255. package/dist/sources/website-url.js +73 -0
  256. package/dist/storage/engines/sqlite-migrations.js +81 -26
  257. package/dist/storage/managed-db.js +27 -24
  258. package/dist/storage/repositories/events-repository.js +3 -0
  259. package/dist/storage/repositories/index-connection.js +42 -10
  260. package/dist/storage/repositories/index-entries-repository.js +203 -229
  261. package/dist/storage/repositories/index-entry-mapper.js +8 -12
  262. package/dist/storage/repositories/index-entry-schema.js +255 -0
  263. package/dist/storage/repositories/index-fts-repository.js +64 -71
  264. package/dist/storage/repositories/index-llm-cache-repository.js +8 -13
  265. package/dist/storage/repositories/index-meta-repository.js +0 -11
  266. package/dist/storage/repositories/index-schema.js +74 -350
  267. package/dist/storage/repositories/index-utility-repository.js +12 -17
  268. package/dist/storage/repositories/index-vec-repository.js +56 -7
  269. package/dist/storage/repositories/proposals-repository.js +4 -127
  270. package/dist/storage/repositories/registry-cache.js +2 -1
  271. package/dist/storage/repositories/task-history-repository.js +20 -40
  272. package/dist/storage/repositories/workflow-runs-repository.js +228 -129
  273. package/dist/storage/sqlite-read-snapshot.js +148 -0
  274. package/dist/tasks/backends/cron.js +170 -42
  275. package/dist/tasks/backends/index.js +1 -1
  276. package/dist/tasks/backends/launchd.js +787 -202
  277. package/dist/tasks/backends/schtasks.js +282 -83
  278. package/dist/tasks/embedded.js +7 -7
  279. package/dist/tasks/frozen-script.js +50 -0
  280. package/dist/tasks/resolve-akm-bin.js +5 -1
  281. package/dist/tasks/runner.js +239 -251
  282. package/dist/tasks/runtime-v3.js +281 -0
  283. package/dist/tasks/scheduler-binding.js +272 -0
  284. package/dist/tasks/scheduler-invocation.js +57 -43
  285. package/dist/tasks/scheduler-sync.js +654 -0
  286. package/dist/tasks/source-v3.js +752 -0
  287. package/dist/tasks/standalone-script-entry.js +5 -0
  288. package/dist/tasks/task-id.js +29 -0
  289. package/dist/workflows/authoring/authoring.js +15 -32
  290. package/dist/workflows/exec/dispatch-redaction.js +14 -8
  291. package/dist/workflows/exec/exec-unit.js +7 -28
  292. package/dist/workflows/exec/frozen-judge.js +57 -89
  293. package/dist/workflows/exec/lowering-notices.js +23 -0
  294. package/dist/workflows/exec/native-executor.js +301 -458
  295. package/dist/workflows/exec/param-secrets.js +4 -3
  296. package/dist/workflows/exec/run-workflow.js +26 -32
  297. package/dist/workflows/exec/step-work.js +105 -109
  298. package/dist/workflows/exec/unit-dispatch.js +103 -27
  299. package/dist/workflows/exec/unit-writer.js +3 -3
  300. package/dist/workflows/exec/worktree.js +2 -2
  301. package/dist/workflows/ir/compile.js +86 -72
  302. package/dist/workflows/ir/environment-v4.js +328 -0
  303. package/dist/workflows/ir/freeze-v4.js +122 -0
  304. package/dist/workflows/ir/plan-hash.js +13 -7
  305. package/dist/workflows/ir/schema-v4.js +525 -0
  306. package/dist/workflows/ir/schema.js +25 -284
  307. package/dist/workflows/ir/source-freeze-v4.js +506 -0
  308. package/dist/workflows/parser.js +27 -24
  309. package/dist/workflows/program/schema.js +1 -2
  310. package/dist/workflows/renderer.js +42 -29
  311. package/dist/workflows/resource-limits.js +4 -5
  312. package/dist/workflows/runtime/agent-identity.js +11 -13
  313. package/dist/workflows/runtime/plan-classifier.js +8 -8
  314. package/dist/workflows/runtime/runs.js +27 -43
  315. package/dist/workflows/runtime/workflow-asset-loader.js +45 -205
  316. package/dist/workflows/source-files.js +373 -0
  317. package/dist/workflows/source-ir/compile.js +196 -0
  318. package/dist/workflows/source-ir/github-yaml.js +577 -0
  319. package/dist/workflows/source-ir/ordering.js +38 -0
  320. package/dist/workflows/source-ir/program.js +50 -0
  321. package/dist/workflows/source-ir/result.js +26 -0
  322. package/dist/workflows/source-ir/schema.js +772 -0
  323. package/dist/workflows/source-ir/semantics.js +242 -0
  324. package/dist/workflows/source-ir/uses.js +14 -0
  325. package/docs/README.md +2 -0
  326. package/docs/migration/README.md +3 -1
  327. package/docs/migration/release-notes/0.9.2.md +55 -0
  328. package/docs/migration/release-notes/README.md +5 -0
  329. package/docs/migration/v0.8-to-v0.9.md +76 -1077
  330. package/docs/migration/v0.9.0-troubleshooting.md +104 -516
  331. package/docs/migration/v0.9.1-to-v0.9.2.md +150 -0
  332. package/docs/reference/README.md +1 -0
  333. package/docs/reference/cli.md +230 -98
  334. package/docs/reference/configuration.md +159 -36
  335. package/docs/reference/data-and-telemetry.md +19 -1
  336. package/docs/reference/supported-formats.md +23 -3
  337. package/docs/reference/tasks.md +182 -0
  338. package/docs/reference/workflow-schema.md +91 -40
  339. package/docs/reference/workflows.md +33 -6
  340. package/package.json +10 -6
  341. package/schemas/akm-config.json +372 -224
  342. package/schemas/akm-task.json +324 -80
  343. package/schemas/akm-workflow.json +6 -9
  344. package/dist/core/migration-operation.js +0 -75
  345. package/dist/integrations/agent/model-aliases.js +0 -74
  346. package/dist/tasks/parser.js +0 -380
  347. package/dist/tasks/schema.js +0 -123
  348. package/dist/tasks/validator.js +0 -80
  349. package/dist/workflows/ir/freeze.js +0 -320
  350. package/dist/workflows/runtime/document-cache.js +0 -13
@@ -6,7 +6,6 @@ import path from "node:path";
6
6
  import { parseBundleRef } from "../../core/asset/asset-ref.js";
7
7
  import { parseFrontmatter } from "../../core/asset/frontmatter.js";
8
8
  import { asNonEmptyString } from "../../core/common.js";
9
- import { loadUserConfig } from "../../core/config/config.js";
10
9
  import { isVerbose, warn } from "../../core/warn.js";
11
10
  export const SCOPE_KEYS = ["user", "agent", "run", "channel"];
12
11
  // ── Quality semantics (v1 spec §4.2) ────────────────────────────────────────
@@ -180,10 +179,8 @@ export function validateStashEntry(entry) {
180
179
  if (typeof e.derivedFrom === "string" && e.derivedFrom.trim().length > 0) {
181
180
  result.derivedFrom = e.derivedFrom.trim();
182
181
  }
183
- // SPEC-8: `bodyOpening` must survive the projection. The extractor already
184
- // trimmed and capped it at capture time.
185
- if (typeof e.bodyOpening === "string" && e.bodyOpening.trim().length > 0) {
186
- result.bodyOpening = e.bodyOpening;
182
+ if (typeof e.content === "string" && e.content.trim().length > 0) {
183
+ result.content = e.content;
187
184
  }
188
185
  if (typeof e.scope === "object" && e.scope !== null && !Array.isArray(e.scope)) {
189
186
  const scope = normalizeScopeObject(e.scope);
@@ -835,36 +832,13 @@ export function isEnrichmentComplete(entry) {
835
832
  const hasSearchHints = Array.isArray(entry.searchHints) && entry.searchHints.length > 0;
836
833
  return hasDescription && hasTags && hasSearchHints;
837
834
  }
838
- // ── Body-opening extraction (stash-conventions SPEC-8) ──────────────────────
835
+ // ── Native Markdown search projection ──────────────────────────────────────
839
836
  /**
840
- * Maximum length of a captured self-situating body opening. Bounds index-size
841
- * growth and keeps a single verbose opening from dominating the low-weight
842
- * `content` FTS column.
837
+ * Maximum native Markdown prose carried by the low-weight `content` field.
838
+ * Structured fields remain separate and higher-weighted; this bound prevents
839
+ * large documents from dominating index size or embedding inputs.
843
840
  */
844
- export const BODY_OPENING_MAX_CHARS = 280;
845
- /**
846
- * Minimum characters retained when the cap truncates at a word boundary. A
847
- * boundary cut that would retain less than this falls back to a hard cut, so
848
- * one pathological long token cannot gut the capture.
849
- */
850
- const BODY_OPENING_MIN_RETAINED_CHARS = 250;
851
- /**
852
- * True when `index.indexBodyOpening` is enabled in the user config.
853
- *
854
- * The gate is the GLOBAL user config, read directly by the metadata pass so
855
- * every indexing entry point (stash walk, flat walk, write-path indexing)
856
- * honors the flag without parameter plumbing. Fail-open: an unreadable or
857
- * invalid config must never break indexing (CLI entry points surface config
858
- * errors loudly on their own), so any load failure reads as "off".
859
- */
860
- function isBodyOpeningIndexingEnabled() {
861
- try {
862
- return loadUserConfig().index?.indexBodyOpening === true;
863
- }
864
- catch {
865
- return false;
866
- }
867
- }
841
+ export const MARKDOWN_CONTENT_MAX_CHARS = 16_384;
868
842
  /**
869
843
  * Locate a leading nested frontmatter block in a body: up to three blank
870
844
  * lines, then a `---` line, closed by a later `---` line. Mirrors the
@@ -892,7 +866,7 @@ function findInnerFrontmatterBlock(lines) {
892
866
  * marker — in the outer frontmatter data OR in a nested inner block at the
893
867
  * top of the body (both producer layouts exist; see base-linter's
894
868
  * `extractFrontmatterRefs`). Session bodies are raw transcripts, never a
895
- * self-situating opening.
869
+ * searchable content projection.
896
870
  */
897
871
  function hasSessionMemoryMarker(fmData, body) {
898
872
  if (typeof fmData.akm_memory_kind === "string")
@@ -930,96 +904,271 @@ function isFrontmatterShaped(lines, block) {
930
904
  }
931
905
  return true;
932
906
  }
907
+ function truncateUnicodeSafe(text, maxChars) {
908
+ if (text.length <= maxChars)
909
+ return text;
910
+ let cut = text.slice(0, maxChars);
911
+ const lastCode = cut.charCodeAt(cut.length - 1);
912
+ if (lastCode >= 0xd800 && lastCode <= 0xdbff)
913
+ cut = cut.slice(0, -1);
914
+ const boundary = cut.lastIndexOf(" ");
915
+ if (boundary >= Math.floor(maxChars * 0.9))
916
+ cut = cut.slice(0, boundary);
917
+ return cut.trimEnd();
918
+ }
919
+ function parseMarkdownFenceOpening(line) {
920
+ const match = /^ {0,3}(`{3,}|~{3,})(.*)$/.exec(line);
921
+ const run = match?.[1];
922
+ if (!run)
923
+ return undefined;
924
+ const marker = run[0];
925
+ // CommonMark forbids backticks in the info string of a backtick fence.
926
+ if (marker === "`" && match?.[2]?.includes("`"))
927
+ return undefined;
928
+ return { marker, length: run.length };
929
+ }
930
+ function isMarkdownFenceClosing(line, fence) {
931
+ let cursor = 0;
932
+ while (cursor < line.length && cursor < 3 && line[cursor] === " ")
933
+ cursor += 1;
934
+ const runStart = cursor;
935
+ while (cursor < line.length && line[cursor] === fence.marker)
936
+ cursor += 1;
937
+ if (cursor - runStart < fence.length)
938
+ return false;
939
+ return /^[\t ]*$/.test(line.slice(cursor));
940
+ }
941
+ function stripMarkdownHtmlComments(line, state) {
942
+ let cursor = 0;
943
+ let visible = "";
944
+ while (cursor < line.length) {
945
+ if (state.inComment) {
946
+ const close = line.indexOf("-->", cursor);
947
+ if (close < 0)
948
+ return visible;
949
+ state.inComment = false;
950
+ cursor = close + 3;
951
+ continue;
952
+ }
953
+ const open = line.indexOf("<!--", cursor);
954
+ if (open < 0)
955
+ return `${visible}${line.slice(cursor)}`;
956
+ visible += line.slice(cursor, open);
957
+ state.inComment = true;
958
+ cursor = open + 4;
959
+ }
960
+ return visible;
961
+ }
962
+ function findBalancedMarkdownClose(text, openAt, open, close) {
963
+ let depth = 1;
964
+ for (let cursor = openAt + 1; cursor < text.length; cursor += 1) {
965
+ const char = text[cursor];
966
+ if (char === "\\") {
967
+ cursor += 1;
968
+ continue;
969
+ }
970
+ if (char === open)
971
+ depth += 1;
972
+ if (char !== close)
973
+ continue;
974
+ depth -= 1;
975
+ if (depth === 0)
976
+ return cursor;
977
+ }
978
+ return undefined;
979
+ }
933
980
  /**
934
- * Extract the first prose paragraph of a markdown body (frontmatter already
935
- * stripped by the caller): skip blank lines, ATX headings, setext `=`
936
- * underlines (discarding the heading text above them), thematic breaks,
937
- * fenced code blocks (``` or ~~~, including their contents), and a leading
938
- * nested frontmatter block — skipped only when its interior is actually
939
- * frontmatter-shaped, so prose wrapped in decorative `---` lines is still
940
- * captured; then collect consecutive non-blank lines until the paragraph
941
- * ends. Deliberate asymmetry: a `---` row after captured prose ENDS the
942
- * paragraph and keeps it (favoring the callout/thematic-break reading over
943
- * CommonMark's setext-H2), while a `=+` row can only be a setext underline
944
- * and so discards the pending lines as heading text. The result is capped at
945
- * {@link BODY_OPENING_MAX_CHARS} chars — truncated at the last word boundary
946
- * that still retains a substantial prefix, with a trailing ellipsis. Returns
947
- * `undefined` when the body has no prose (frontmatter-only files,
948
- * headings/fences-only bodies).
981
+ * Find the closing parenthesis of an inline link destination. Parentheses in
982
+ * angle-delimited destinations and quoted titles are data, while unquoted
983
+ * parentheses remain balanced. Backslash escapes suppress delimiter meaning.
949
984
  */
950
- export function extractBodyOpening(body) {
951
- const lines = body.split(/\r?\n/);
952
- const innerBlock = findInnerFrontmatterBlock(lines);
953
- const start = innerBlock && isFrontmatterShaped(lines, innerBlock) ? innerBlock.close + 1 : 0;
954
- const paragraph = [];
955
- let inFence = false;
956
- let fenceChar = "";
957
- let inHtmlComment = false;
958
- for (let i = start; i < lines.length; i += 1) {
959
- const trimmed = lines[i].trim();
960
- const fenceMatch = trimmed.match(/^(`{3,}|~{3,})/);
961
- if (inFence) {
962
- // Fence interiors are never prose (and may be secrets-adjacent command
963
- // text); skip until the matching closing marker.
964
- if (fenceMatch && fenceMatch[1].charAt(0) === fenceChar)
965
- inFence = false;
985
+ function findMarkdownDestinationClose(text, openAt) {
986
+ let depth = 1;
987
+ let phase = "before-destination";
988
+ let quote;
989
+ let parenthesizedTitle = false;
990
+ let titleSeparator = false;
991
+ for (let cursor = openAt + 1; cursor < text.length; cursor += 1) {
992
+ const char = text[cursor];
993
+ if (char === "\\") {
994
+ cursor += 1;
966
995
  continue;
967
996
  }
968
- if (inHtmlComment) {
969
- // Comment interiors are machinery (the skeleton convention facts open
970
- // with a <!-- SOFT guidance --> block), never orientation prose.
971
- if (trimmed.includes("-->"))
972
- inHtmlComment = false;
997
+ if (phase === "before-destination") {
998
+ if (/\s/u.test(char ?? ""))
999
+ continue;
1000
+ if (char === "<") {
1001
+ phase = "angle-destination";
1002
+ continue;
1003
+ }
1004
+ if (char === ")")
1005
+ return cursor;
1006
+ phase = "bare-destination";
1007
+ }
1008
+ if (phase === "angle-destination") {
1009
+ if (char === ">")
1010
+ phase = "after-destination";
973
1011
  continue;
974
1012
  }
975
- if (fenceMatch) {
976
- if (paragraph.length > 0)
977
- break; // a fence ends an open paragraph
978
- inFence = true;
979
- fenceChar = fenceMatch[1].charAt(0);
1013
+ if (phase === "title") {
1014
+ if (quote) {
1015
+ if (char === quote) {
1016
+ quote = undefined;
1017
+ phase = "after-destination";
1018
+ titleSeparator = false;
1019
+ }
1020
+ continue;
1021
+ }
1022
+ if (parenthesizedTitle) {
1023
+ if (char === "(")
1024
+ depth += 1;
1025
+ if (char !== ")")
1026
+ continue;
1027
+ depth -= 1;
1028
+ if (depth === 1) {
1029
+ parenthesizedTitle = false;
1030
+ phase = "after-destination";
1031
+ titleSeparator = false;
1032
+ }
1033
+ }
1034
+ continue;
1035
+ }
1036
+ if (phase === "after-destination") {
1037
+ if (/\s/u.test(char ?? "")) {
1038
+ titleSeparator = true;
1039
+ continue;
1040
+ }
1041
+ if (char === ")")
1042
+ return cursor;
1043
+ if (titleSeparator && (char === '"' || char === "'")) {
1044
+ quote = char;
1045
+ phase = "title";
1046
+ continue;
1047
+ }
1048
+ if (titleSeparator && char === "(") {
1049
+ depth += 1;
1050
+ parenthesizedTitle = true;
1051
+ phase = "title";
1052
+ continue;
1053
+ }
1054
+ // Invalid trailing bytes are still consumed conservatively until the
1055
+ // balanced outer close. Quotes here are ordinary bytes, never titles.
1056
+ phase = "bare-destination";
1057
+ }
1058
+ if (phase !== "bare-destination")
1059
+ continue;
1060
+ if (char === "(") {
1061
+ depth += 1;
1062
+ continue;
1063
+ }
1064
+ if (char === ")") {
1065
+ depth -= 1;
1066
+ if (depth === 0)
1067
+ return cursor;
980
1068
  continue;
981
1069
  }
982
- if (trimmed.startsWith("<!--")) {
983
- if (paragraph.length > 0)
984
- break; // a comment ends an open paragraph
985
- if (!trimmed.includes("-->"))
986
- inHtmlComment = true;
1070
+ if (/\s/u.test(char ?? "") && depth === 1) {
1071
+ phase = "after-destination";
1072
+ titleSeparator = true;
1073
+ }
1074
+ }
1075
+ return undefined;
1076
+ }
1077
+ const MAX_MARKDOWN_LINK_NESTING = 32;
1078
+ /** Retain recursively projected link labels while dropping complete destinations. */
1079
+ function stripMarkdownLinkDestinations(text, nesting = 0) {
1080
+ let visible = "";
1081
+ let cursor = 0;
1082
+ while (cursor < text.length) {
1083
+ const isImage = text[cursor] === "!" && text[cursor + 1] === "[";
1084
+ const labelOpen = isImage ? cursor + 1 : cursor;
1085
+ if (text[labelOpen] !== "[") {
1086
+ visible += text[cursor];
1087
+ cursor += 1;
987
1088
  continue;
988
1089
  }
989
- if (/^=+$/.test(trimmed)) {
990
- // A row of `=` is a setext H1 underline: the pending lines are heading
991
- // text, not prose discard them and keep searching. (A bare `=` row
992
- // with nothing pending is skipped like any other non-prose divider.)
993
- paragraph.length = 0;
1090
+ const labelClose = findBalancedMarkdownClose(text, labelOpen, "[", "]");
1091
+ const destinationOpen = labelClose === undefined ? undefined : labelClose + 1;
1092
+ if (destinationOpen === undefined || text[destinationOpen] !== "(") {
1093
+ visible += text[cursor];
1094
+ cursor += 1;
994
1095
  continue;
995
1096
  }
996
- const isBlank = trimmed === "";
997
- const isHeading = /^#{1,6}(\s|$)/.test(trimmed);
998
- const isThematicBreak = /^(-{3,}|\*{3,}|_{3,})$/.test(trimmed);
999
- if (isBlank || isHeading || isThematicBreak) {
1000
- if (paragraph.length > 0)
1001
- break; // paragraph complete
1002
- continue; // still searching for the first prose line
1097
+ const destinationClose = findMarkdownDestinationClose(text, destinationOpen);
1098
+ if (destinationClose === undefined) {
1099
+ visible += text[cursor];
1100
+ cursor += 1;
1101
+ continue;
1003
1102
  }
1004
- paragraph.push(trimmed);
1103
+ // Labels may themselves contain images/links. Recursively project them so
1104
+ // an inner destination cannot become visible when its outer link is
1105
+ // removed. At an adversarial nesting depth, omit the label rather than
1106
+ // leaking its unparsed bytes into the search projection.
1107
+ if (nesting < MAX_MARKDOWN_LINK_NESTING) {
1108
+ visible += stripMarkdownLinkDestinations(text.slice(labelOpen + 1, labelClose), nesting + 1);
1109
+ }
1110
+ cursor = destinationClose + 1;
1005
1111
  }
1006
- const text = paragraph.join("\n");
1112
+ return visible;
1113
+ }
1114
+ /**
1115
+ * Derive the one low-weight search projection for an AKM-native Markdown body.
1116
+ * Frontmatter is removed by the caller. This projection drops comments,
1117
+ * fenced code, raw link targets, and structural punctuation while retaining
1118
+ * prose, headings, link labels, and inline identifiers. It never receives
1119
+ * secret/env/session bytes; that policy is enforced at the adapter metadata
1120
+ * boundary below.
1121
+ */
1122
+ export function projectMarkdownContent(body) {
1123
+ const lines = body.split(/\r?\n/);
1124
+ const innerBlock = findInnerFrontmatterBlock(lines);
1125
+ const start = innerBlock && isFrontmatterShaped(lines, innerBlock) ? innerBlock.close + 1 : 0;
1126
+ const projected = [];
1127
+ let fence;
1128
+ const htmlComment = { inComment: false };
1129
+ for (let i = start; i < lines.length; i += 1) {
1130
+ const rawLine = lines[i];
1131
+ if (fence) {
1132
+ if (isMarkdownFenceClosing(rawLine, fence))
1133
+ fence = undefined;
1134
+ continue;
1135
+ }
1136
+ // A leading fence owns the whole line, including any info string that
1137
+ // resembles HTML. Comment state therefore cannot begin inside a fence.
1138
+ if (!htmlComment.inComment) {
1139
+ const openingFence = parseMarkdownFenceOpening(rawLine);
1140
+ if (openingFence) {
1141
+ fence = openingFence;
1142
+ continue;
1143
+ }
1144
+ }
1145
+ let trimmed = stripMarkdownHtmlComments(rawLine, htmlComment).trim();
1146
+ const openingFence = parseMarkdownFenceOpening(trimmed);
1147
+ if (openingFence) {
1148
+ fence = openingFence;
1149
+ continue;
1150
+ }
1151
+ if (!trimmed || /^(-{3,}|\*{3,}|_{3,}|=+)$/.test(trimmed))
1152
+ continue;
1153
+ if (/^\s*\[[^\]]+\]:\s*\S+/.test(trimmed))
1154
+ continue;
1155
+ if (/^<[^>]+>$/.test(trimmed))
1156
+ continue;
1157
+ // Preserve human-facing labels and inline identifiers, never destinations.
1158
+ trimmed = stripMarkdownLinkDestinations(trimmed);
1159
+ trimmed = trimmed.replace(/`{1,2}([^`]+)`{1,2}/g, "$1");
1160
+ trimmed = trimmed.replace(/^#{1,6}\s+/, "");
1161
+ trimmed = trimmed.replace(/^(?:>|[-+*]|\d+[.)])\s+/, "");
1162
+ trimmed = trimmed.replace(/<[^>]+>/g, " ");
1163
+ trimmed = trimmed.replace(/[|]+/g, " ");
1164
+ trimmed = trimmed.replace(/\s+/g, " ").trim();
1165
+ if (trimmed)
1166
+ projected.push(trimmed);
1167
+ }
1168
+ const text = projected.join(" ").replace(/\s+/g, " ").trim();
1007
1169
  if (!text)
1008
1170
  return undefined;
1009
- if (text.length <= BODY_OPENING_MAX_CHARS)
1010
- return text;
1011
- // Cap: prefer a word-boundary cut (never mid-token), but only when it keeps
1012
- // a substantial prefix; append a one-char ellipsis inside the budget.
1013
- const slice = text.slice(0, BODY_OPENING_MAX_CHARS - 1);
1014
- const lastBoundary = Math.max(slice.lastIndexOf(" "), slice.lastIndexOf("\n"), slice.lastIndexOf("\t"));
1015
- let cut = lastBoundary >= BODY_OPENING_MIN_RETAINED_CHARS ? slice.slice(0, lastBoundary) : slice;
1016
- // slice() counts UTF-16 code units, so the no-boundary fallback can end on
1017
- // the high half of a surrogate pair — a lone surrogate that corrupts the
1018
- // FTS/embedding text. Drop it rather than emit invalid UTF-16.
1019
- const lastCode = cut.charCodeAt(cut.length - 1);
1020
- if (lastCode >= 0xd800 && lastCode <= 0xdbff)
1021
- cut = cut.slice(0, -1);
1022
- return `${cut.trimEnd()}…`;
1171
+ return truncateUnicodeSafe(text, MARKDOWN_CONTENT_MAX_CHARS);
1023
1172
  }
1024
1173
  // ── Metadata Generation ─────────────────────────────────────────────────────
1025
1174
  /**
@@ -1060,16 +1209,12 @@ export function applyPreContributorFields(entry, file, ctx, pkgMeta) {
1060
1209
  applyWikiFrontmatter(entry, parsed.data);
1061
1210
  // D2 (#730): reread the namespaced `provenance:` block promoteProposal stamps.
1062
1211
  applyProvenanceFrontmatter(entry, parsed.data);
1063
- // Stash-organization conventions (SPEC-8): config-gated capture of the
1064
- // self-situating body opening. Default off enabling it changes indexed
1065
- // text (collapse-detector canary baselines shift, and embeddings for
1066
- // already-embedded entries are NOT regenerated; see docs/reference/configuration.md).
1067
- // Session-kind memories are raw transcripts and are never captured;
1068
- // secrets never reach this branch (guard above) and env files are not .md.
1069
- if (isBodyOpeningIndexingEnabled() && !hasSessionMemoryMarker(parsed.data, parsed.content)) {
1070
- const bodyOpening = extractBodyOpening(parsed.content);
1071
- if (bodyOpening)
1072
- entry.bodyOpening = bodyOpening;
1212
+ // Native Markdown has one bounded low-weight body projection. Sensitive
1213
+ // types and raw session/checkpoint material never cross this boundary.
1214
+ if (entry.type !== "env" && entry.type !== "session" && !hasSessionMemoryMarker(parsed.data, parsed.content)) {
1215
+ const contentProjection = projectMarkdownContent(parsed.content);
1216
+ if (contentProjection)
1217
+ entry.content = contentProjection;
1073
1218
  }
1074
1219
  // Extract parameters from template placeholders ($1, $ARGUMENTS, {{named}})
1075
1220
  if (entry.type === "command") {
@@ -6,13 +6,11 @@
6
6
  * `indexDocumentFromEntry` (`adapters/akm-adapter.ts`). akm 0.9.0 Chunk 5,
7
7
  * milestone F4a (M2 groundwork).
8
8
  *
9
- * The engine swap drains an `IndexDocument` stream (`drainDirDocuments` × the
10
- * dispatched adapter's `recognize`) in place of the per-dir flat-walk matcher-pass
11
- * `IndexDocument` stream, then persists it. But the durable `entries.entry_json`
12
- * column stays a faithful `IndexDocument` every reader (`rowToIndexedEntry` →
13
- * `DbIndexedEntry.entry`) consumes it as one, and the byte-for-byte goldens pin
14
- * it. So the persist path must reconstruct the exact `IndexDocument` the old
15
- * pipeline stored FROM the `IndexDocument` the new pipeline produces.
9
+ * The indexer drains a single `IndexDocument` stream (`drainDirDocuments` × the
10
+ * dispatched adapter's `recognize`) and persists its canonical projection in
11
+ * `entries.document_json`. Every reader (`rowToIndexedEntry`
12
+ * `DbIndexedEntry.entry`) consumes that same projection, and parity tests pin
13
+ * the boundary.
16
14
  *
17
15
  * That reconstruction is lossless by construction: the akm adapter's `recognize`
18
16
  * assembles a full `IndexDocument` (P1/P2 → fold → P4) and then maps it onto the
@@ -20,8 +18,8 @@
20
18
  * onto named members, every other search-surface/provenance field onto
21
19
  * `documentJson` (the `DOCUMENT_JSON_CARRIED_FIELDS` set). This function reverses
22
20
  * that mapping exactly, so `indexDocumentToStashEntry(recognize(file))` deep-
23
- * equals the entry the legacy flat-walk pass stored for the same file (the
24
- * `tests/integration/shadow-scan-parity.test.ts` gate pins the persisted index).
21
+ * equals the current persisted document for the same file (the
22
+ * `tests/integration/shadow-scan-parity.test.ts` gate pins this boundary).
25
23
  *
26
24
  * Two deliberate non-round-trip fields:
27
25
  * - `filename` — dropped by `indexDocumentFromEntry` (no IndexDocument home),
@@ -123,7 +121,6 @@ export function indexDocumentToStashEntry(doc) {
123
121
  entry.toc = dj.toc;
124
122
  if (dj.parameters !== undefined)
125
123
  entry.parameters = dj.parameters;
126
- assignString(entry, "bodyOpening", dj.bodyOpening);
127
124
  if (dj.source !== undefined)
128
125
  entry.source = dj.source;
129
126
  assignString(entry, "category", dj.category);
@@ -14,26 +14,23 @@
14
14
  * - **Broken-workflow drop (item 3).** The live path dropped a broken workflow
15
15
  * via the renderer contributor's throw → metadata-pass skip-with-warning; the
16
16
  * `akm` adapter's synchronous `foldRecognizedMetadata` SWALLOWS the parse
17
- * error, so a broken workflow would otherwise silently index. We re-run
18
- * `parseWorkflow` on drained workflow docs and DROP
17
+ * error, so a broken workflow would otherwise silently index. We run the
18
+ * shared source-IR compiler on drained workflow docs and DROP
19
19
  * the entry with the same `Skipped workflow …` warning
20
20
  * ({@link buildMetadataSkipWarning}), so the workflow-skip summary counts it.
21
- * - **Workflow-document side-table (workflow-md only).** The valid parsed
22
- * `WorkflowDocument` is handed to the persist layer through the same
23
- * `document-cache` side channel the live renderer contributor used, keyed by
24
- * the reconstructed entry — so `takeWorkflowDocument(entry)` in the persist
25
- * loop writes the `workflow_documents` row exactly as before.
26
21
  *
27
22
  * `doc.hash` (= sha256 of the file content) is surfaced per recognized file so
28
23
  * the persist layer can populate the `content_hash` column (item 2). It is keyed
29
24
  * by the file's absolute path rather than by the entry object.
30
25
  *
31
- * Pure of DB/global state beyond the workflow-document side channel; a new leaf
32
- * (nothing imports it back), so it joins no import cycle.
26
+ * Pure of DB/global state; a new leaf (nothing imports it back), so it joins no
27
+ * import cycle.
33
28
  */
29
+ import path from "node:path";
34
30
  import { akmAdapter } from "../../core/adapter/adapters/akm-adapter.js";
35
- import { parseWorkflow } from "../../workflows/parser.js";
36
- import { cacheWorkflowDocument } from "../../workflows/runtime/document-cache.js";
31
+ import { canonicalizeWorkflowName } from "../../core/recognition-util.js";
32
+ import { resolveUniqueWorkflowSource, WorkflowSourceRejectionError, workflowNameForSourcePath, } from "../../workflows/source-files.js";
33
+ import { compileWorkflowSource } from "../../workflows/source-ir/compile.js";
37
34
  import { buildMetadataSkipWarning } from "../passes/metadata.js";
38
35
  import { buildFileContext } from "../walk/file-context.js";
39
36
  import { indexDocumentToStashEntry } from "./doc-to-entry.js";
@@ -53,7 +50,34 @@ export function drainDirDocuments(adapter, component, fileContexts) {
53
50
  const warnings = [];
54
51
  const hashByFile = new Map();
55
52
  const conceptIdByFile = new Map();
53
+ const rejectedPaths = new Set();
54
+ const rejectedConceptIds = new Set();
55
+ const workflowLookups = new Map();
56
56
  for (const file of fileContexts) {
57
+ const workflowName = workflowNameForSourcePath(component.root, adapter.id, file.absPath);
58
+ if (workflowName !== undefined) {
59
+ const canonicalName = canonicalizeWorkflowName(workflowName);
60
+ if (!workflowLookups.has(canonicalName))
61
+ workflowLookups.set(canonicalName, workflowName);
62
+ }
63
+ }
64
+ for (const [canonicalName, workflowName] of [...workflowLookups].sort(([left], [right]) => comparePaths(left, right))) {
65
+ try {
66
+ resolveUniqueWorkflowSource(component.root, adapter.id, workflowName);
67
+ }
68
+ catch (error) {
69
+ if (!(error instanceof WorkflowSourceRejectionError))
70
+ throw error;
71
+ rejectedConceptIds.add(adapter.id === "akm" ? `workflows/${canonicalName}` : canonicalName);
72
+ for (const relativePath of error.sourcePaths) {
73
+ rejectedPaths.add(path.join(component.root, relativePath));
74
+ }
75
+ warnings.push(error.message);
76
+ }
77
+ }
78
+ for (const file of fileContexts) {
79
+ if (rejectedPaths.has(file.absPath))
80
+ continue;
57
81
  const doc = adapter.recognize(component, file);
58
82
  if (doc === null)
59
83
  continue;
@@ -62,9 +86,9 @@ export function drainDirDocuments(adapter, component, fileContexts) {
62
86
  continue;
63
87
  }
64
88
  const entry = indexDocumentToStashEntry(doc);
65
- // Workflow docs: drop-with-warning if broken; otherwise cache the parsed
66
- // markdown document for the persist-time `workflow_documents` write.
67
- const dropWarning = handleWorkflowDoc(doc, entry, file);
89
+ // Workflow docs: drop-with-warning if broken; otherwise cache a lossless
90
+ // runtime projection when the current executor can represent the source.
91
+ const dropWarning = handleWorkflowDoc(doc, file, component.root);
68
92
  if (dropWarning !== null) {
69
93
  warnings.push(dropWarning);
70
94
  continue;
@@ -74,7 +98,10 @@ export function drainDirDocuments(adapter, component, fileContexts) {
74
98
  conceptIdByFile.set(file.absPath, doc.conceptId);
75
99
  entries.push(entry);
76
100
  }
77
- return { entries, warnings, hashByFile, conceptIdByFile };
101
+ return { entries, warnings, hashByFile, conceptIdByFile, rejectedPaths, rejectedConceptIds };
102
+ }
103
+ function comparePaths(left, right) {
104
+ return left < right ? -1 : left > right ? 1 : 0;
78
105
  }
79
106
  /**
80
107
  * `(stashRoot, files) → StashFile` drop-in for the deleted flat-walk matcher
@@ -98,18 +125,19 @@ export function recognizeStashEntries(stashRoot, files) {
98
125
  : { entries: drained.entries };
99
126
  }
100
127
  /**
101
- * If `doc` is a workflow, re-parse it: return a `Skipped workflow …` drop
102
- * warning when it is broken, or cache the parsed markdown `WorkflowDocument`
103
- * (workflow-md only) and return `null` when valid. Non-workflow docs return
104
- * `null` immediately.
128
+ * If `doc` is a workflow, compile it through source IR: return a
129
+ * `Skipped workflow …` drop warning when it is broken, or return `null` when
130
+ * it compiles. Non-workflow docs return `null` immediately.
105
131
  */
106
- function handleWorkflowDoc(doc, entry, file) {
107
- if (docRenderer(doc) !== WORKFLOW_MD_RENDERER)
132
+ function handleWorkflowDoc(doc, file, workspaceRoot) {
133
+ if (doc.type !== "workflow" ||
134
+ (doc.adapterId !== "akm" && doc.adapterId !== "akm-workflow") ||
135
+ (docRenderer(doc) !== WORKFLOW_MD_RENDERER && doc.adapterId !== "akm-workflow")) {
108
136
  return null;
109
- const result = parseWorkflow(file.content(), { path: file.relPath });
137
+ }
138
+ const result = compileWorkflowSource(file.content(), { path: file.relPath, workspaceRoot });
110
139
  if (!result.ok)
111
140
  return workflowDropWarning(file, result.errors);
112
- cacheWorkflowDocument(entry, result.document);
113
141
  return null;
114
142
  }
115
143
  /** The winning renderer name the `akm` adapter carries on `documentJson.renderer`, or `undefined`. */