akm-cli 0.8.1 → 0.9.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (318) hide show
  1. package/CHANGELOG.md +258 -0
  2. package/dist/assets/help/help-proposals.md +1 -2
  3. package/dist/assets/hints/cli-hints-full.md +34 -19
  4. package/dist/assets/hints/cli-hints-short.md +1 -1
  5. package/dist/assets/profiles/catchup.json +13 -0
  6. package/dist/assets/profiles/consolidate.json +13 -0
  7. package/dist/assets/profiles/frequent.json +13 -0
  8. package/dist/assets/stash-skeleton/README.md +76 -0
  9. package/dist/assets/tasks/core/backup.yml +4 -0
  10. package/dist/assets/tasks/core/extract.yml +4 -0
  11. package/dist/assets/tasks/core/improve.yml +4 -0
  12. package/dist/assets/tasks/core/index-refresh.yml +4 -0
  13. package/dist/assets/tasks/core/sync.yml +4 -0
  14. package/dist/assets/tasks/core/update-stashes.yml +4 -0
  15. package/dist/assets/tasks/core/version-check.yml +4 -0
  16. package/dist/cli/config-migrate.js +6 -6
  17. package/dist/cli/config-validate.js +4 -4
  18. package/dist/cli/confirm.js +3 -3
  19. package/dist/cli/parse-args.js +1 -1
  20. package/dist/cli/shared.js +51 -14
  21. package/dist/cli-node.mjs +26 -0
  22. package/dist/cli.js +171 -3857
  23. package/dist/commands/{agent-dispatch.js → agent/agent-dispatch.js} +6 -6
  24. package/dist/commands/{agent-support.js → agent/agent-support.js} +2 -2
  25. package/dist/commands/agent/contribute-cli.js +200 -0
  26. package/dist/commands/completions.js +1 -1
  27. package/dist/commands/config-cli.js +240 -3
  28. package/dist/commands/config-edit.js +344 -0
  29. package/dist/commands/db-cli.js +2 -2
  30. package/dist/commands/env/env-cli.js +529 -0
  31. package/dist/commands/env/env.js +410 -0
  32. package/dist/commands/env/secret-cli.js +259 -0
  33. package/dist/commands/{secret.js → env/secret.js} +6 -47
  34. package/dist/commands/events.js +4 -4
  35. package/dist/commands/feedback-cli.js +18 -34
  36. package/dist/commands/graph/graph-cli.js +132 -0
  37. package/dist/commands/{graph.js → graph/graph.js} +22 -16
  38. package/dist/commands/health/checks.js +279 -0
  39. package/dist/commands/health.js +101 -249
  40. package/dist/commands/{consolidate.js → improve/consolidate.js} +52 -40
  41. package/dist/commands/{distill-promotion-policy.js → improve/distill-promotion-policy.js} +3 -3
  42. package/dist/commands/{distill.js → improve/distill.js} +39 -18
  43. package/dist/commands/{eval-cases.js → improve/eval-cases.js} +1 -1
  44. package/dist/commands/{extract-cli.js → improve/extract-cli.js} +4 -4
  45. package/dist/commands/{extract-prompt.js → improve/extract-prompt.js} +2 -2
  46. package/dist/commands/{extract.js → improve/extract.js} +185 -26
  47. package/dist/commands/{improve-auto-accept.js → improve/improve-auto-accept.js} +4 -4
  48. package/dist/commands/{improve-cli.js → improve/improve-cli.js} +45 -23
  49. package/dist/commands/{improve-profiles.js → improve/improve-profiles.js} +13 -7
  50. package/dist/commands/{improve-result-file.js → improve/improve-result-file.js} +10 -5
  51. package/dist/commands/{improve.js → improve/improve.js} +536 -248
  52. package/dist/{core → commands/improve/memory}/memory-belief.js +2 -2
  53. package/dist/{core → commands/improve/memory}/memory-contradiction-detect.js +5 -5
  54. package/dist/{core → commands/improve/memory}/memory-improve.js +4 -4
  55. package/dist/commands/{reflect.js → improve/reflect.js} +33 -28
  56. package/dist/commands/improve/session-asset.js +248 -0
  57. package/dist/commands/lint/agent-linter.js +1 -1
  58. package/dist/commands/lint/base-linter.js +55 -37
  59. package/dist/commands/lint/command-linter.js +1 -1
  60. package/dist/commands/lint/default-linter.js +1 -1
  61. package/dist/commands/lint/env-key-rules.js +1 -1
  62. package/dist/commands/lint/index.js +19 -25
  63. package/dist/commands/lint/knowledge-linter.js +1 -1
  64. package/dist/commands/lint/memory-linter.js +1 -1
  65. package/dist/commands/lint/registry.js +8 -8
  66. package/dist/commands/lint/skill-linter.js +1 -1
  67. package/dist/commands/lint/task-linter.js +1 -1
  68. package/dist/commands/lint/workflow-linter.js +1 -1
  69. package/dist/commands/lint.js +1 -1
  70. package/dist/commands/observability-cli.js +244 -0
  71. package/dist/commands/{proposal-drain-policies.js → proposal/drain-policies.js} +3 -3
  72. package/dist/commands/{proposal-drain.js → proposal/drain.js} +15 -10
  73. package/dist/commands/proposal/proposal-cli.js +478 -0
  74. package/dist/commands/{proposal.js → proposal/proposal.js} +5 -5
  75. package/dist/commands/{propose.js → proposal/propose.js} +11 -11
  76. package/dist/{core → commands/proposal/validators}/proposal-quality-validators.js +8 -3
  77. package/dist/{core → commands/proposal/validators}/proposal-validators.js +5 -5
  78. package/dist/{core → commands/proposal/validators}/proposals.js +13 -7
  79. package/dist/commands/{curate.js → read/curate.js} +7 -7
  80. package/dist/commands/{knowledge.js → read/knowledge.js} +22 -9
  81. package/dist/commands/{registry-search.js → read/registry-search.js} +5 -5
  82. package/dist/commands/{remember-cli.js → read/remember-cli.js} +15 -7
  83. package/dist/commands/read/search-cli.js +207 -0
  84. package/dist/commands/{search.js → read/search.js} +22 -27
  85. package/dist/commands/{show.js → read/show.js} +77 -44
  86. package/dist/commands/registry-cli.js +8 -8
  87. package/dist/commands/remember.js +8 -8
  88. package/dist/commands/sources/add-cli.js +293 -0
  89. package/dist/commands/{history.js → sources/history.js} +27 -25
  90. package/dist/commands/{info.js → sources/info.js} +6 -6
  91. package/dist/commands/{init.js → sources/init.js} +10 -5
  92. package/dist/commands/{installed-stashes.js → sources/installed-stashes.js} +12 -12
  93. package/dist/commands/{migration-help.js → sources/migration-help.js} +3 -2
  94. package/dist/commands/{schema-repair.js → sources/schema-repair.js} +8 -8
  95. package/dist/commands/{self-update.js → sources/self-update.js} +10 -9
  96. package/dist/commands/{source-add.js → sources/source-add.js} +10 -10
  97. package/dist/commands/{source-clone.js → sources/source-clone.js} +7 -7
  98. package/dist/commands/{source-manage.js → sources/source-manage.js} +4 -4
  99. package/dist/commands/sources/sources-cli.js +305 -0
  100. package/dist/commands/sources/stash-cli.js +219 -0
  101. package/dist/commands/sources/stash-skeleton.js +79 -0
  102. package/dist/commands/tasks/default-tasks.js +173 -0
  103. package/dist/commands/tasks/tasks-cli.js +210 -0
  104. package/dist/commands/{tasks.js → tasks/tasks.js} +14 -14
  105. package/dist/commands/wiki-cli.js +307 -0
  106. package/dist/commands/workflow-cli.js +329 -0
  107. package/dist/core/action-contributors.js +1 -1
  108. package/dist/core/assert.js +40 -0
  109. package/dist/core/asset/asset-create.js +54 -0
  110. package/dist/core/{asset-ref.js → asset/asset-ref.js} +21 -4
  111. package/dist/core/{asset-registry.js → asset/asset-registry.js} +3 -3
  112. package/dist/core/{asset-spec.js → asset/asset-spec.js} +17 -31
  113. package/dist/core/{markdown.js → asset/markdown.js} +1 -1
  114. package/dist/core/asset/stash-meta.js +110 -0
  115. package/dist/core/best-effort.js +64 -0
  116. package/dist/core/common.js +32 -18
  117. package/dist/core/{config-io.js → config/config-io.js} +29 -19
  118. package/dist/core/{config-migration.js → config/config-migration.js} +11 -9
  119. package/dist/core/{config-schema.js → config/config-schema.js} +45 -1
  120. package/dist/core/config/config-types.js +16 -0
  121. package/dist/core/{config-walker.js → config/config-walker.js} +2 -2
  122. package/dist/core/{config.js → config/config.js} +10 -8
  123. package/dist/core/env-secret-ref.js +90 -0
  124. package/dist/core/errors.js +13 -3
  125. package/dist/core/events.js +27 -4
  126. package/dist/core/file-lock.js +1 -1
  127. package/dist/core/improve-types.js +48 -0
  128. package/dist/core/lesson-lint.js +2 -2
  129. package/dist/core/paths.js +2 -2
  130. package/dist/{setup/ripgrep-install.js → core/ripgrep/install.js} +2 -2
  131. package/dist/{setup/ripgrep-resolve.js → core/ripgrep/resolve.js} +2 -2
  132. package/dist/core/state-db.js +88 -46
  133. package/dist/core/text-truncation.js +148 -0
  134. package/dist/core/time.js +1 -1
  135. package/dist/core/write-source.js +98 -85
  136. package/dist/indexer/{db-backup.js → db/db-backup.js} +9 -24
  137. package/dist/indexer/{db.js → db/db.js} +126 -116
  138. package/dist/indexer/{graph-db.js → db/graph-db.js} +9 -4
  139. package/dist/indexer/{llm-cache.js → db/llm-cache.js} +15 -12
  140. package/dist/indexer/ensure-index.js +4 -4
  141. package/dist/indexer/{graph-boost.js → graph/graph-boost.js} +1 -1
  142. package/dist/indexer/{graph-extraction.js → graph/graph-extraction.js} +55 -13
  143. package/dist/indexer/indexer.js +37 -30
  144. package/dist/indexer/init.js +54 -0
  145. package/dist/indexer/manifest.js +10 -10
  146. package/dist/indexer/{memory-inference.js → passes/memory-inference.js} +92 -23
  147. package/dist/indexer/{metadata-contributors.js → passes/metadata-contributors.js} +10 -8
  148. package/dist/indexer/{metadata.js → passes/metadata.js} +15 -19
  149. package/dist/indexer/{staleness-detect.js → passes/staleness-detect.js} +53 -12
  150. package/dist/indexer/{db-search.js → search/db-search.js} +28 -16
  151. package/dist/indexer/{ranking-contributors.js → search/ranking-contributors.js} +1 -1
  152. package/dist/indexer/{ranking.js → search/ranking.js} +2 -2
  153. package/dist/indexer/{search-hit-enrichers.js → search/search-hit-enrichers.js} +3 -3
  154. package/dist/indexer/{search-source.js → search/search-source.js} +8 -8
  155. package/dist/indexer/{semantic-status.js → search/semantic-status.js} +3 -3
  156. package/dist/indexer/usage/unmigrated-vaults-guard.js +94 -0
  157. package/dist/indexer/{usage-events.js → usage/usage-events.js} +32 -0
  158. package/dist/indexer/{file-context.js → walk/file-context.js} +10 -15
  159. package/dist/indexer/{matchers.js → walk/matchers.js} +13 -9
  160. package/dist/indexer/{path-resolver.js → walk/path-resolver.js} +6 -6
  161. package/dist/indexer/{project-context.js → walk/project-context.js} +1 -1
  162. package/dist/indexer/{walker.js → walk/walker.js} +4 -3
  163. package/dist/integrations/agent/builder-shared.js +39 -0
  164. package/dist/integrations/agent/builders.js +14 -81
  165. package/dist/integrations/agent/config.js +6 -4
  166. package/dist/integrations/agent/detect.js +1 -1
  167. package/dist/integrations/agent/index.js +23 -8
  168. package/dist/integrations/agent/prompts.js +2 -3
  169. package/dist/integrations/agent/runner.js +22 -3
  170. package/dist/integrations/agent/spawn.js +9 -10
  171. package/dist/integrations/harnesses/claude/agent-builder.js +48 -0
  172. package/dist/integrations/harnesses/claude/config-import.js +70 -0
  173. package/dist/integrations/harnesses/claude/index.js +64 -0
  174. package/dist/integrations/{session-logs/providers/claude-code.js → harnesses/claude/session-log.js} +16 -1
  175. package/dist/integrations/harnesses/index.js +144 -0
  176. package/dist/integrations/harnesses/opencode/agent-builder.js +43 -0
  177. package/dist/integrations/harnesses/opencode/config-import.js +82 -0
  178. package/dist/integrations/harnesses/opencode/index.js +59 -0
  179. package/dist/integrations/{session-logs/providers/opencode.js → harnesses/opencode/session-log.js} +1 -1
  180. package/dist/integrations/harnesses/opencode-sdk/index.js +49 -0
  181. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +234 -0
  182. package/dist/integrations/harnesses/types.js +43 -0
  183. package/dist/integrations/lockfile.js +7 -16
  184. package/dist/integrations/session-logs/index.js +82 -9
  185. package/dist/llm/call-ai.js +4 -4
  186. package/dist/llm/client.js +131 -6
  187. package/dist/llm/embedder.js +6 -6
  188. package/dist/llm/embedders/local.js +9 -22
  189. package/dist/llm/embedders/remote.js +2 -2
  190. package/dist/llm/embedders/types.js +1 -1
  191. package/dist/llm/graph-extract.js +31 -12
  192. package/dist/llm/index-passes.js +1 -1
  193. package/dist/llm/memory-infer.js +12 -5
  194. package/dist/llm/metadata-enhance.js +2 -2
  195. package/dist/output/context.js +6 -44
  196. package/dist/output/renderers.js +88 -58
  197. package/dist/output/shapes/curate.js +7 -3
  198. package/dist/output/shapes/distill.js +7 -3
  199. package/dist/output/shapes/env-list.js +18 -16
  200. package/dist/output/shapes/events.js +5 -4
  201. package/dist/output/shapes/helpers.js +2 -4
  202. package/dist/output/shapes/history.js +7 -3
  203. package/dist/output/shapes/passthrough.js +8 -11
  204. package/dist/output/shapes/{proposal-accept.js → proposal/accept.js} +7 -3
  205. package/dist/output/shapes/{proposal-diff.js → proposal/diff.js} +7 -3
  206. package/dist/output/shapes/{proposal-list.js → proposal/list.js} +7 -3
  207. package/dist/output/shapes/{proposal-producer.js → proposal/producer.js} +5 -4
  208. package/dist/output/shapes/{proposal-reject.js → proposal/reject.js} +7 -3
  209. package/dist/output/shapes/{proposal-show.js → proposal/show.js} +7 -3
  210. package/dist/output/shapes/registry-search.js +7 -3
  211. package/dist/output/shapes/registry.js +12 -0
  212. package/dist/output/shapes/search.js +7 -3
  213. package/dist/output/shapes/secret-list.js +18 -16
  214. package/dist/output/shapes/show.js +7 -3
  215. package/dist/output/shapes.js +55 -30
  216. package/dist/output/text/add.js +2 -3
  217. package/dist/output/text/clone.js +2 -3
  218. package/dist/output/text/config.js +2 -3
  219. package/dist/output/text/curate.js +4 -3
  220. package/dist/output/text/distill.js +2 -3
  221. package/dist/output/text/enable-disable.js +5 -4
  222. package/dist/output/text/env.js +13 -0
  223. package/dist/output/text/events.js +5 -4
  224. package/dist/output/text/feedback.js +4 -3
  225. package/dist/output/text/helpers.js +54 -39
  226. package/dist/output/text/history.js +2 -3
  227. package/dist/output/text/import.js +2 -3
  228. package/dist/output/text/index.js +2 -3
  229. package/dist/output/text/info.js +2 -3
  230. package/dist/output/text/init.js +2 -3
  231. package/dist/output/text/list.js +2 -3
  232. package/dist/output/text/proposal/producer.js +9 -0
  233. package/dist/output/text/proposal/proposal.js +13 -0
  234. package/dist/output/text/registry-commands.js +8 -7
  235. package/dist/output/text/registry.js +12 -0
  236. package/dist/output/text/remember.js +4 -3
  237. package/dist/output/text/remove.js +2 -3
  238. package/dist/output/text/save.js +2 -3
  239. package/dist/output/text/search.js +4 -3
  240. package/dist/output/text/show.js +4 -3
  241. package/dist/output/text/update.js +2 -3
  242. package/dist/output/text/upgrade.js +2 -3
  243. package/dist/output/text/wiki.js +12 -11
  244. package/dist/output/text/workflow.js +12 -10
  245. package/dist/output/text.js +66 -32
  246. package/dist/registry/build-index.js +11 -10
  247. package/dist/registry/factory.js +1 -1
  248. package/dist/registry/origin-resolve.js +1 -1
  249. package/dist/registry/providers/index.js +2 -2
  250. package/dist/registry/providers/skills-sh.js +91 -72
  251. package/dist/registry/providers/static-index.js +75 -52
  252. package/dist/registry/resolve.js +3 -3
  253. package/dist/runtime.js +242 -0
  254. package/dist/scripts/migrate-storage.js +1594 -673
  255. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +240 -166
  256. package/dist/setup/detect.js +338 -9
  257. package/dist/setup/harness-config-import.js +56 -0
  258. package/dist/setup/registry-stash-loader.js +99 -0
  259. package/dist/setup/setup.js +664 -96
  260. package/dist/sources/include.js +1 -1
  261. package/dist/sources/provider-factory.js +2 -2
  262. package/dist/sources/providers/filesystem.js +3 -3
  263. package/dist/sources/providers/git.js +9 -9
  264. package/dist/sources/providers/index.js +4 -4
  265. package/dist/sources/providers/npm.js +6 -6
  266. package/dist/sources/providers/provider-utils.js +13 -20
  267. package/dist/sources/providers/sync-from-ref.js +5 -5
  268. package/dist/sources/providers/tar-utils.js +2 -2
  269. package/dist/sources/providers/website.js +2 -2
  270. package/dist/sources/resolve.js +5 -5
  271. package/dist/sources/website-ingest.js +5 -5
  272. package/dist/storage/database.js +102 -0
  273. package/dist/storage/engines/sqlite-migrations.js +42 -0
  274. package/dist/storage/locations.js +25 -0
  275. package/dist/storage/repositories/index-db.js +43 -0
  276. package/dist/storage/repositories/workflow-runs-repository.js +141 -0
  277. package/dist/tasks/backends/cron.js +4 -4
  278. package/dist/tasks/backends/exec-utils.js +32 -0
  279. package/dist/tasks/backends/index.js +3 -3
  280. package/dist/tasks/backends/launchd.js +7 -14
  281. package/dist/tasks/backends/schtasks.js +7 -16
  282. package/dist/tasks/embedded.js +71 -0
  283. package/dist/tasks/parser.js +2 -2
  284. package/dist/tasks/resolveAkmBin.js +1 -1
  285. package/dist/tasks/runner.js +28 -15
  286. package/dist/tasks/schedule.js +1 -1
  287. package/dist/tasks/validator.js +7 -7
  288. package/dist/text-import-hook.mjs +51 -0
  289. package/dist/version.js +2 -1
  290. package/dist/wiki/wiki.js +7 -7
  291. package/dist/workflows/{authoring.js → authoring/authoring.js} +6 -6
  292. package/dist/workflows/{scope-key.js → authoring/scope-key.js} +1 -1
  293. package/dist/workflows/cli.js +1 -1
  294. package/dist/workflows/db.js +50 -32
  295. package/dist/workflows/parser.js +4 -4
  296. package/dist/workflows/renderer.js +5 -5
  297. package/dist/workflows/runtime/agent-identity.js +56 -0
  298. package/dist/workflows/runtime/checkin.js +57 -0
  299. package/dist/workflows/{runs.js → runtime/runs.js} +197 -101
  300. package/dist/workflows/validate-summary.js +82 -0
  301. package/docs/README.md +1 -1
  302. package/docs/data-and-telemetry.md +6 -6
  303. package/package.json +16 -8
  304. package/dist/commands/add-cli.js +0 -279
  305. package/dist/commands/env.js +0 -213
  306. package/dist/integrations/agent/sdk-runner.js +0 -126
  307. package/dist/output/shapes/vault-list.js +0 -19
  308. package/dist/output/text/proposal-producer.js +0 -8
  309. package/dist/output/text/proposal.js +0 -12
  310. package/dist/output/text/vault.js +0 -16
  311. /package/dist/core/{asset-serialize.js → asset/asset-serialize.js} +0 -0
  312. /package/dist/core/{frontmatter.js → asset/frontmatter.js} +0 -0
  313. /package/dist/core/{config-sources.js → config/config-sources.js} +0 -0
  314. /package/dist/indexer/{graph-dedup.js → graph/graph-dedup.js} +0 -0
  315. /package/dist/{core/config-types.js → indexer/passes/pass-context.js} +0 -0
  316. /package/dist/indexer/{search-fields.js → search/search-fields.js} +0 -0
  317. /package/dist/indexer/{index-context.js → walk/index-context.js} +0 -0
  318. /package/dist/workflows/{document-cache.js → runtime/document-cache.js} +0 -0
@@ -28,8 +28,8 @@
28
28
  * - MemOS (arXiv:2507.03724) — formal archive/merge/transition with shared state model
29
29
  */
30
30
  import fs from "node:fs";
31
- import { assembleAsset } from "./asset-serialize";
32
- import { parseFrontmatter } from "./frontmatter";
31
+ import { assembleAsset } from "../../../core/asset/asset-serialize.js";
32
+ import { parseFrontmatter } from "../../../core/asset/frontmatter.js";
33
33
  // ── Contradiction edge writer ─────────────────────────────────────────────────
34
34
  /**
35
35
  * Write `contradictedBy` and `beliefState: contradicted` edges to a memory
@@ -35,11 +35,11 @@
35
35
  */
36
36
  import fs from "node:fs";
37
37
  import path from "node:path";
38
- import { chatCompletion, parseEmbeddedJsonResponse } from "../llm/client";
39
- import { tryLlmFeature } from "../llm/feature-gate";
40
- import { assembleAsset } from "./asset-serialize";
41
- import { getDefaultLlmConfig } from "./config";
42
- import { parseFrontmatter } from "./frontmatter";
38
+ import { assembleAsset } from "../../../core/asset/asset-serialize.js";
39
+ import { parseFrontmatter } from "../../../core/asset/frontmatter.js";
40
+ import { getDefaultLlmConfig } from "../../../core/config/config.js";
41
+ import { chatCompletion, parseEmbeddedJsonResponse } from "../../../llm/client.js";
42
+ import { tryLlmFeature } from "../../../llm/feature-gate.js";
43
43
  // ── Constants ────────────────────────────────────────────────────────────────
44
44
  /**
45
45
  * Maximum family size for pairwise contradiction checking. Families larger
@@ -3,10 +3,10 @@
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
4
  import fs from "node:fs";
5
5
  import path from "node:path";
6
- import { makeAssetRef, parseAssetRef } from "./asset-ref";
7
- import { assembleAsset } from "./asset-serialize";
8
- import { firstString, groupBy, stringArray } from "./common";
9
- import { parseFrontmatter } from "./frontmatter";
6
+ import { makeAssetRef, parseAssetRef } from "../../../core/asset/asset-ref.js";
7
+ import { assembleAsset } from "../../../core/asset/asset-serialize.js";
8
+ import { parseFrontmatter } from "../../../core/asset/frontmatter.js";
9
+ import { firstString, groupBy, stringArray } from "../../../core/common.js";
10
10
  const DERIVED_SUFFIX = ".derived";
11
11
  export function analyzeMemoryCleanup(stashDir, options = {}) {
12
12
  const records = collectDerivedMemories(stashDir, options.parentRef);
@@ -24,27 +24,28 @@
24
24
  import fs from "node:fs";
25
25
  import os from "node:os";
26
26
  import path from "node:path";
27
- import { parseAssetRef } from "../core/asset-ref";
28
- import { assembleAssetFromString, serializeFrontmatter } from "../core/asset-serialize";
29
- import { resolveStashDir } from "../core/common";
30
- import { loadConfig } from "../core/config";
31
- import { ConfigError, UsageError } from "../core/errors";
32
- import { appendEvent, readEvents } from "../core/events";
33
- import { parseFrontmatter } from "../core/frontmatter";
34
- import { lintLessonContent } from "../core/lesson-lint";
35
- import { stripMarkdownFences } from "../core/markdown";
36
- import { checkReflectSize } from "../core/proposal-quality-validators";
37
- import { createProposal, isProposalSkipped, listProposals, } from "../core/proposals";
38
- import { lookup } from "../indexer/indexer";
39
- import { runAgent, } from "../integrations/agent";
40
- import { resolveProcessAgentProfile } from "../integrations/agent/config";
41
- import { buildReflectPrompt, extractDraftConfidence, parseAgentProposalPayload, } from "../integrations/agent/prompts";
42
- import { resolveImproveProcessRunnerFromProfile } from "../integrations/agent/runner";
43
- import { runOpencodeSdk } from "../integrations/agent/sdk-runner";
44
- import { chatCompletion } from "../llm/client";
45
- import { isLlmFeatureEnabled } from "../llm/feature-gate";
46
- import { baseFailureFields, enoentHintMessage, isEnoentFailure, loadAgentConfigFromDisk, resolveAgentProfile, } from "./agent-support";
47
- import { deriveLessonRef, runLessonQualityJudge } from "./distill";
27
+ import { assertNever } from "../../core/assert.js";
28
+ import { parseAssetRef } from "../../core/asset/asset-ref.js";
29
+ import { assembleAssetFromString, serializeFrontmatter } from "../../core/asset/asset-serialize.js";
30
+ import { parseFrontmatter } from "../../core/asset/frontmatter.js";
31
+ import { stripMarkdownFences } from "../../core/asset/markdown.js";
32
+ import { resolveStashDir } from "../../core/common.js";
33
+ import { loadConfig } from "../../core/config/config.js";
34
+ import { ConfigError, UsageError } from "../../core/errors.js";
35
+ import { appendEvent, readEvents } from "../../core/events.js";
36
+ import { lintLessonContent } from "../../core/lesson-lint.js";
37
+ import { lookup } from "../../indexer/indexer.js";
38
+ import { runAgent, } from "../../integrations/agent/index.js";
39
+ import { resolveProcessAgentProfile } from "../../integrations/agent/config.js";
40
+ import { buildReflectPrompt, extractDraftConfidence, parseAgentProposalPayload, } from "../../integrations/agent/prompts.js";
41
+ import { resolveImproveProcessRunnerFromProfile, runnerIsLlm, runnerSupportsFileWrite, } from "../../integrations/agent/runner.js";
42
+ import { runOpencodeSdk } from "../../integrations/harnesses/opencode-sdk/index.js";
43
+ import { chatCompletion } from "../../llm/client.js";
44
+ import { isLlmFeatureEnabled } from "../../llm/feature-gate.js";
45
+ import { baseFailureFields, enoentHintMessage, isEnoentFailure, loadAgentConfigFromDisk, resolveAgentProfile, } from "../agent/agent-support.js";
46
+ import { checkReflectSize } from "../proposal/validators/proposal-quality-validators.js";
47
+ import { createProposal, isProposalSkipped, listProposals, } from "../proposal/validators/proposals.js";
48
+ import { deriveLessonRef, runLessonQualityJudge } from "./distill.js";
48
49
  const MAX_FEEDBACK_LINES = 10;
49
50
  const MAX_GLOBAL_FEEDBACK_LINES = 20;
50
51
  /**
@@ -76,7 +77,7 @@ const MAX_REJECTED_PROPOSALS = 3;
76
77
  * Asset types that reflect is allowed to operate on.
77
78
  *
78
79
  * Reflect's canonical output shape is `frontmatter + markdown body`. Running it
79
- * against types whose on-disk form is NOT markdown (executable scripts, vault
80
+ * against types whose on-disk form is NOT markdown (executable scripts, env files
80
81
  * env files, YAML tasks) blindly prepends `---\n…\n---\n` to the asset and
81
82
  * breaks the runtime contract — for example a `.ts` script with a YAML preamble
82
83
  * is a TypeScript syntax error.
@@ -614,7 +615,7 @@ export async function akmReflect(options = {}) {
614
615
  parsedRef = parseAssetRef(options.ref);
615
616
  // 2a. Type guard — reflect only operates on asset types whose canonical
616
617
  // shape is `frontmatter + markdown body`. Refuse non-markdown types
617
- // (script / vault / task) up-front so reflect never prepends YAML to a
618
+ // (script / env / task) up-front so reflect never prepends YAML to a
618
619
  // `.ts` file or rewrites a `.env` blob as prose. See REFLECT_ALLOWED_TYPES.
619
620
  if (!REFLECT_ALLOWED_TYPES.has(parsedRef.type)) {
620
621
  // Deterministic type-guard rejection — the LLM is never invoked. Emit
@@ -707,10 +708,10 @@ export async function akmReflect(options = {}) {
707
708
  }
708
709
  // Derive a display name for logging — either from the resolved profile or the runnerSpec.
709
710
  const resolvedProfileName = profile?.name ??
710
- (runnerSpec?.kind === "llm"
711
+ (runnerSpec && runnerIsLlm(runnerSpec)
711
712
  ? `llm:${runnerSpec.connection.model}`
712
- : runnerSpec?.kind !== undefined
713
- ? `${runnerSpec.kind}:${runnerSpec.profile?.name ?? "unknown"}`
713
+ : runnerSpec
714
+ ? `${runnerSpec.kind}:${runnerSpec.profile.name ?? "unknown"}`
714
715
  : "unknown");
715
716
  // 4. Build the shared prompt inputs — feedback, hints, lessons, rejected
716
717
  // proposals. These are stable across refinement iterations; only the
@@ -739,7 +740,7 @@ export async function akmReflect(options = {}) {
739
740
  // `profile.sdkMode` fallback also runs the SDK so it counts as file-writable.
740
741
  // Test seams (`options.runAgentOptions.spawn`) emulate agent CLI behaviour so
741
742
  // they participate as well — tests opt out by simply not writing the file.
742
- const runnerSupportsFileWrite = runnerSpec ? runnerSpec.kind !== "llm" : true;
743
+ const canRunnerWriteFile = runnerSpec ? runnerSupportsFileWrite(runnerSpec) : true;
743
744
  // Initialized to a sentinel; always overwritten in the first loop iteration
744
745
  // (maxRefineIters is clamped to >= 1 above). TypeScript cannot prove a
745
746
  // for-loop always runs at least once, so we use a type assertion here.
@@ -775,7 +776,7 @@ export async function akmReflect(options = {}) {
775
776
  for (let iter = 0; iter < maxRefineIters; iter++) {
776
777
  // Synthesize a fresh tmp path per iteration so refinement passes never
777
778
  // clobber an earlier draft (and so reading back is unambiguous).
778
- const iterDraftPath = runnerSupportsFileWrite ? synthesizeReflectDraftPath(options.ref) : undefined;
779
+ const iterDraftPath = canRunnerWriteFile ? synthesizeReflectDraftPath(options.ref) : undefined;
779
780
  if (iterDraftPath) {
780
781
  draftPathsToCleanup.push(iterDraftPath);
781
782
  lastDraftPath = iterDraftPath;
@@ -853,6 +854,10 @@ export async function akmReflect(options = {}) {
853
854
  ...(runnerSpec.timeoutMs !== undefined ? { timeoutMs: runnerSpec.timeoutMs } : {}),
854
855
  });
855
856
  break;
857
+ default:
858
+ // Exhaustiveness arm (H1): a 4th RunnerSpec kind becomes a compile
859
+ // error here instead of leaving `iterResult` unassigned at runtime.
860
+ assertNever(runnerSpec);
856
861
  }
857
862
  }
858
863
  else {
@@ -0,0 +1,248 @@
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+ /**
5
+ * Session asset generation for the `extract` pass (#561).
6
+ *
7
+ * After the extractor distills memory proposals from a session, it ALSO writes
8
+ * the session itself to the stash as a first-class `session` asset so any agent
9
+ * — on any harness — can discover prior work via `akm search` / `akm curate`.
10
+ *
11
+ * Design constraints (see #561):
12
+ * - ADDITIVE + FAIL-OPEN + CONFIG-GATED. Disabled (or no LLM provider) →
13
+ * extract behaves EXACTLY as before. Nothing is written.
14
+ * - The LLM summary call routes through the injectable {@link SessionSummaryGenerator}
15
+ * seam so tests never touch a real provider, and so production wraps the
16
+ * call in the existing `tryLlmFeature` fail-open pattern.
17
+ * - The `log_path` + `access` frontmatter fields are the durable correlation
18
+ * key — they survive index rebuilds (the body is re-derived from disk).
19
+ *
20
+ * The asset is written to `sessions/<harness>/<session-id>.md`; the registered
21
+ * `session` asset type (see `asset-spec.ts`) makes the normal index pass pick it
22
+ * up for FTS + vector search with no special-casing.
23
+ */
24
+ import fs from "node:fs";
25
+ import path from "node:path";
26
+ import { assembleAsset } from "../../core/asset/asset-serialize.js";
27
+ import { TYPE_DIRS } from "../../core/asset/asset-spec.js";
28
+ import { normalizeHarnessId } from "../../integrations/harnesses/index.js";
29
+ /**
30
+ * JSON Schema for the session-summary LLM call. Strict so providers that
31
+ * support schema enforcement constrain the output upstream; the parser only
32
+ * has to handle the happy path. `additionalProperties: false` drops any
33
+ * hallucinated keys before parsing.
34
+ */
35
+ export const SESSION_SUMMARY_JSON_SCHEMA = {
36
+ type: "object",
37
+ required: ["summary", "key_topics"],
38
+ additionalProperties: false,
39
+ properties: {
40
+ summary: { type: "string" },
41
+ key_topics: { type: "array", items: { type: "string" } },
42
+ tags: { type: "array", items: { type: "string" } },
43
+ },
44
+ };
45
+ /**
46
+ * Render a compact transcript snippet from session events for the summary
47
+ * prompt. Mirrors the extract transcript format but caps total length so the
48
+ * summary prompt stays bounded regardless of session size.
49
+ */
50
+ function renderTranscriptForSummary(events, maxChars = 12_000) {
51
+ if (events.length === 0)
52
+ return "(empty — no events)";
53
+ const lines = [];
54
+ let total = 0;
55
+ for (const e of events) {
56
+ const role = e.role ?? "unknown";
57
+ const text = e.text.trim();
58
+ if (!text)
59
+ continue;
60
+ const line = `[${role}] ${text}`;
61
+ if (total + line.length > maxChars)
62
+ break;
63
+ lines.push(line);
64
+ total += line.length + 2;
65
+ }
66
+ return lines.join("\n\n") || "(empty — no textual events)";
67
+ }
68
+ /**
69
+ * Build the user prompt for the session-summary LLM call. Pure — no IO. The
70
+ * model is asked for a dense 2–4 sentence summary plus key topics, optimised
71
+ * for semantic search recall.
72
+ */
73
+ export function buildSessionSummaryPrompt(data) {
74
+ const ref = data.ref;
75
+ const startedAt = isoOrUndefined(ref.startedAt) ?? "unknown";
76
+ const endedAt = isoOrUndefined(ref.endedAt) ?? "unknown";
77
+ return [
78
+ "You are summarizing an agent coding session so it can be found later via semantic search.",
79
+ "Write a DENSE 2–4 sentence summary of what was worked on, the key decisions made, and the outcomes.",
80
+ "Then list the concrete entities touched: files, GitHub issues/PRs, commands, concepts, and people.",
81
+ "Optimise the summary for recall — include the specific nouns an agent would search for.",
82
+ "",
83
+ `Harness: ${ref.harness}`,
84
+ `Project: ${ref.projectHint ?? "(unknown)"}`,
85
+ `Started: ${startedAt} Ended: ${endedAt}`,
86
+ `Title: ${ref.title ?? "(none)"}`,
87
+ "",
88
+ "Transcript:",
89
+ renderTranscriptForSummary(data.events),
90
+ "",
91
+ 'Respond as JSON: {"summary": string, "key_topics": string[], "tags"?: string[]}.',
92
+ ].join("\n");
93
+ }
94
+ /**
95
+ * Parse the session-summary LLM response into a {@link SessionSummaryResult}.
96
+ * Defensive: tolerates prose preamble/postamble around the JSON, and returns
97
+ * `undefined` when nothing usable parses (fail-open: no asset is written).
98
+ */
99
+ export function parseSessionSummary(raw) {
100
+ if (!raw || raw.trim().length === 0)
101
+ return undefined;
102
+ let parsed;
103
+ try {
104
+ parsed = JSON.parse(raw);
105
+ }
106
+ catch {
107
+ const start = raw.indexOf("{");
108
+ const end = raw.lastIndexOf("}");
109
+ if (start === -1 || end <= start)
110
+ return undefined;
111
+ try {
112
+ parsed = JSON.parse(raw.slice(start, end + 1));
113
+ }
114
+ catch {
115
+ return undefined;
116
+ }
117
+ }
118
+ if (!parsed || typeof parsed !== "object")
119
+ return undefined;
120
+ const obj = parsed;
121
+ const summary = typeof obj.summary === "string" ? obj.summary.trim() : "";
122
+ if (summary.length === 0)
123
+ return undefined;
124
+ const keyTopics = Array.isArray(obj.key_topics)
125
+ ? obj.key_topics.filter((t) => typeof t === "string" && t.trim().length > 0)
126
+ : [];
127
+ const tags = Array.isArray(obj.tags)
128
+ ? obj.tags.filter((t) => typeof t === "string" && t.trim().length > 0)
129
+ : undefined;
130
+ return { summary, keyTopics, ...(tags && tags.length > 0 ? { tags } : {}) };
131
+ }
132
+ /**
133
+ * Decide whether a session is long enough to index. `minDurationMinutes <= 0`
134
+ * disables the gate. When either timestamp is missing we DON'T gate it out —
135
+ * fail-open toward indexing, since a missing timestamp is not evidence of a
136
+ * trivial session.
137
+ */
138
+ export function sessionMeetsDurationGate(data, minDurationMinutes) {
139
+ if (!Number.isFinite(minDurationMinutes) || minDurationMinutes <= 0)
140
+ return true;
141
+ const { startedAt, endedAt } = data.ref;
142
+ if (typeof startedAt !== "number" || typeof endedAt !== "number")
143
+ return true;
144
+ const durationMinutes = (endedAt - startedAt) / 60_000;
145
+ return durationMinutes >= minDurationMinutes;
146
+ }
147
+ /**
148
+ * Build per-harness `access` instructions for reading the raw session log.
149
+ *
150
+ * Documented convention (#561, checklist item "Document `access` field
151
+ * convention per harness"): the string tells a downstream agent exactly how to
152
+ * read and parse the log at `log_path`. New harnesses fall back to a generic
153
+ * `cat <log_path>` hint, which is always correct for a file-backed log.
154
+ */
155
+ export function buildSessionAccessInstructions(harness, logPath) {
156
+ const canonical = normalizeHarnessId(harness);
157
+ if (canonical === "claude") {
158
+ return [
159
+ `Read with: cat ${logPath}`,
160
+ `Parse messages: jq -r 'select(.type=="message") | .message.content[]? | select(.type=="text") | .text' ${logPath}`,
161
+ ].join("\n");
162
+ }
163
+ if (canonical === "opencode") {
164
+ return [
165
+ `Read with: cat ${logPath}`,
166
+ `The log is opencode session storage (JSON). Inspect with: jq '.' ${logPath}`,
167
+ ].join("\n");
168
+ }
169
+ // Generic fallback — file-backed logs are always readable with cat.
170
+ return `Read with: cat ${logPath}`;
171
+ }
172
+ /** ISO-8601 (UTC) from a ms-epoch, or undefined when absent/non-finite. */
173
+ function isoOrUndefined(ms) {
174
+ return typeof ms === "number" && Number.isFinite(ms) ? new Date(ms).toISOString() : undefined;
175
+ }
176
+ /** Default session-name slug: `<harness>-session-<yyyy-mm-dd>-<shortId>`. */
177
+ export function buildSessionAssetName(harness, sessionId, startedAtMs) {
178
+ const canonical = normalizeHarnessId(harness);
179
+ const datePart = isoOrUndefined(startedAtMs)?.slice(0, 10) ?? "unknown-date";
180
+ const shortId = sessionId.slice(0, 8);
181
+ return `${canonical}-session-${datePart}-${shortId}`;
182
+ }
183
+ /**
184
+ * Assemble the full session asset (frontmatter + `## Summary` / `## Key topics`).
185
+ * Pure — no IO. Returns the serialized markdown string.
186
+ */
187
+ export function buildSessionAssetContent(data, summary) {
188
+ const ref = data.ref;
189
+ const harness = ref.harness;
190
+ const startedAt = isoOrUndefined(ref.startedAt);
191
+ const endedAt = isoOrUndefined(ref.endedAt);
192
+ const name = buildSessionAssetName(harness, ref.sessionId, ref.startedAt);
193
+ const logPath = ref.filePath;
194
+ const baseTags = ["session", normalizeHarnessId(harness)];
195
+ const extraTags = (summary.tags ?? []).filter((t) => typeof t === "string" && t.trim().length > 0);
196
+ const tags = Array.from(new Set([...baseTags, ...extraTags]));
197
+ const frontmatter = {
198
+ name,
199
+ type: "session",
200
+ harness,
201
+ session_id: ref.sessionId,
202
+ ...(startedAt ? { started_at: startedAt } : {}),
203
+ ...(endedAt ? { ended_at: endedAt } : {}),
204
+ ...(ref.projectHint ? { project: ref.projectHint } : {}),
205
+ log_path: logPath,
206
+ access: buildSessionAccessInstructions(harness, logPath),
207
+ tags,
208
+ };
209
+ const topics = summary.keyTopics
210
+ .filter((t) => typeof t === "string" && t.trim().length > 0)
211
+ .map((t) => `- ${t.trim()}`)
212
+ .join("\n");
213
+ const body = `## Summary\n\n${summary.summary.trim()}\n\n## Key topics\n\n${topics || "- (none extracted)"}\n`;
214
+ // `description` is duplicated into frontmatter so the metadata pass surfaces
215
+ // it without re-reading the body — matches how other content types behave.
216
+ const content = assembleAsset({ ...frontmatter, description: summary.summary.trim() }, body);
217
+ return { name, frontmatter, content };
218
+ }
219
+ /** Resolve `<stash>/sessions/<harness>/<session-id>.md`. */
220
+ export function resolveSessionAssetPath(stashDir, harness, sessionId) {
221
+ const dir = TYPE_DIRS.session ?? "sessions";
222
+ return path.join(stashDir, dir, normalizeHarnessId(harness), `${sessionId}.md`);
223
+ }
224
+ /**
225
+ * Generate (via the injected summarizer) and write a session asset to the stash.
226
+ *
227
+ * FAIL-OPEN: when the summarizer returns `undefined` (disabled / no LLM /
228
+ * error), NOTHING is written and `{ written: false }` is returned. Any write
229
+ * error is swallowed by the caller — session indexing must NEVER break extract.
230
+ */
231
+ export async function writeSessionAsset(data, stashDir, generate) {
232
+ const summary = await generate(data);
233
+ if (!summary?.summary || summary.summary.trim().length === 0) {
234
+ return { written: false };
235
+ }
236
+ const { content } = buildSessionAssetContent(data, summary);
237
+ const harness = data.ref.harness;
238
+ const sessionId = data.ref.sessionId;
239
+ const filePath = resolveSessionAssetPath(stashDir, harness, sessionId);
240
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
241
+ fs.writeFileSync(filePath, content, "utf8");
242
+ return {
243
+ written: true,
244
+ filePath,
245
+ ref: `session:${normalizeHarnessId(harness)}/${sessionId}`,
246
+ logPath: data.ref.filePath,
247
+ };
248
+ }
@@ -2,7 +2,7 @@
2
2
  // License, v. 2.0. If a copy of the MPL was not distributed with this
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
4
  import path from "node:path";
5
- import { BaseLinter } from "./base-linter";
5
+ import { BaseLinter } from "./base-linter.js";
6
6
  /**
7
7
  * Linter for `agents/` assets.
8
8
  *
@@ -18,18 +18,28 @@
18
18
  // will fail.
19
19
  //
20
20
  // Cases the contract covers (see fixture in the contract test):
21
- // - existing memory / knowledge / agent / workflow / skill / vault refs
21
+ // - existing memory / knowledge / agent / workflow / skill refs
22
22
  // - knowledge subdirectory layout (knowledge/<category>/<slug>.md)
23
23
  // - skill multi-file layout (skills/<slug>/SKILL.md)
24
24
  // - memory `.derived.md` sibling
25
- // - vault default vs named (.env vs <name>.env)
26
25
  // - namespaced slugs containing `/`
26
+ // - env (`env/.env`, `env/<name>.env`) and secret (`secrets/<name>`) refs
27
27
  // - non-existent refs
28
28
  // - script type (unresolvable by design — both must return false)
29
+ //
30
+ // As of 0.9 the type alternation in `REF_RE` and the path mapping in
31
+ // `refToRelPath` are DERIVED FROM THE ASSET REGISTRY (`getAssetTypes()` /
32
+ // `resolveAssetPathFromName` in `src/core/asset/asset-spec.ts`) rather than
33
+ // hand-encoded, so they can no longer drift from the registry. The previously
34
+ // hand-listed `vault` type was removed from the registry in 0.9 (replaced by
35
+ // `env`); `vault:` refs are therefore no longer matched here. `env:`/`secret:`
36
+ // refs are now matched and path-resolved. `script` stays unresolvable and
37
+ // `task` keeps its legacy `.md` resolution (see refToRelPath for both).
29
38
  // ----------------------------------------------------------------------------
30
39
  import fs from "node:fs";
31
40
  import path from "node:path";
32
- import { findSafeInsertionPoint } from "./markdown-insertion";
41
+ import { getAssetTypes, resolveAssetPathFromName, TYPE_DIRS } from "../../core/asset/asset-spec.js";
42
+ import { findSafeInsertionPoint } from "./markdown-insertion.js";
33
43
  // ── Helpers ───────────────────────────────────────────────────────────────────
34
44
  function formatDate(d) {
35
45
  const y = d.getFullYear();
@@ -85,45 +95,53 @@ function checkStalePath(body) {
85
95
  return null;
86
96
  }
87
97
  // ── missing-ref helpers ───────────────────────────────────────────────────────
88
- const REF_RE = /(?:^|[\s`"'(])((agent|command|knowledge|memory|script|skill|workflow|lesson|task|wiki|vault):[^\s"'`)\]>,\n]+)/gm;
89
98
  /**
90
- * Map from ref type to relative path pattern within stashRoot. Returns null to skip.
99
+ * Type alternation for {@link REF_RE}, derived from the asset registry at
100
+ * module load so it can never drift from `ASSET_SPECS`. Longest-first ordering
101
+ * is defensive (no built-in type is a prefix of another, but a future custom
102
+ * `registerAssetType` one might be) so the alternation prefers the longest
103
+ * match. Regex metacharacters are escaped in case a custom type introduces one.
104
+ */
105
+ function buildRefTypeAlternation() {
106
+ const types = [...getAssetTypes()].sort((a, b) => b.length - a.length);
107
+ return types.map((t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
108
+ }
109
+ // Only the TYPE alternation is registry-derived; the surrounding grammar
110
+ // (boundary prefix, capture group, slug charset) is byte-identical to the
111
+ // legacy hand-written pattern. Deriving the types from `getAssetTypes()` means
112
+ // `env`/`secret` (added in 0.9) are now matched, and the removed `vault` type
113
+ // is not — both follow the registry automatically.
114
+ const REF_RE = new RegExp(`(?:^|[\\s\`"'(])((${buildRefTypeAlternation()}):[^\\s"'\`)\\]>,\\n]+)`, "gm");
115
+ /**
116
+ * Map from ref type to relative path pattern within stashRoot. Returns null to
117
+ * skip (type is unresolvable by the slug walker).
118
+ *
119
+ * Path layout is owned by the asset registry: we resolve through
120
+ * `resolveAssetPathFromName(type, TYPE_DIRS[type], name)` so the linter and the
121
+ * rest of the CLI agree on where an asset lives. Two legacy carve-outs are
122
+ * preserved to keep pre-0.9 behaviour byte-identical:
123
+ * - `script`: returns null (scripts live in nested dirs with arbitrary
124
+ * extensions — unresolvable by the slug-based walker, as the contract pins).
125
+ * - `task`: the registry stores tasks as `<id>.yml`, but the missing-ref
126
+ * linter has always resolved `task:` refs against `tasks/<id>.md`; that
127
+ * behaviour is held constant here (non-env/secret behaviour is unchanged).
91
128
  *
92
129
  * Exported for contract testing — see header CONTRACT block.
93
130
  */
94
131
  export function refToRelPath(refType, refName) {
95
- switch (refType) {
96
- case "agent":
97
- return path.join("agents", `${refName}.md`);
98
- case "command":
99
- return path.join("commands", `${refName}.md`);
100
- case "knowledge":
101
- return path.join("knowledge", `${refName}.md`);
102
- case "memory":
103
- return path.join("memories", `${refName}.md`);
104
- case "script":
105
- return null; // scripts live in nested dirs skip
106
- case "skill":
107
- return path.join("skills", refName, "SKILL.md");
108
- case "workflow":
109
- return path.join("workflows", `${refName}.md`);
110
- case "lesson":
111
- return path.join("lessons", `${refName}.md`);
112
- case "task":
113
- return path.join("tasks", `${refName}.md`);
114
- case "wiki":
115
- return path.join("wikis", `${refName}.md`);
116
- case "vault":
117
- // Vaults are .env files. The canonical name "default" (or empty) maps to
118
- // ".env"; any other name maps to "<name>.env". This mirrors the vault
119
- // asset-spec toAssetPath logic in src/core/asset-spec.ts.
120
- if (!refName || refName === "default") {
121
- return path.join("vaults", ".env");
122
- }
123
- return path.join("vaults", `${refName}.env`);
124
- default:
125
- return null;
126
- }
132
+ // script is intentionally unresolvable (contract-pinned).
133
+ if (refType === "script")
134
+ return null;
135
+ // Preserve the legacy `.md` resolution for tasks.
136
+ if (refType === "task")
137
+ return path.join(TYPE_DIRS.task ?? "tasks", `${refName}.md`);
138
+ const typeDir = TYPE_DIRS[refType];
139
+ if (!typeDir)
140
+ return null; // unknown type — skip
141
+ // resolveAssetPathFromName returns a path rooted at the type dir we pass in,
142
+ // i.e. "<typeDir>/<...>" exactly the stash-relative path this helper has
143
+ // always returned.
144
+ return resolveAssetPathFromName(refType, typeDir, refName);
127
145
  }
128
146
  /**
129
147
  * Returns true if `relPath` resolves to a real file (or multi-file directory
@@ -2,7 +2,7 @@
2
2
  // License, v. 2.0. If a copy of the MPL was not distributed with this
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
4
  import path from "node:path";
5
- import { BaseLinter } from "./base-linter";
5
+ import { BaseLinter } from "./base-linter.js";
6
6
  /**
7
7
  * Linter for `commands/` assets.
8
8
  *
@@ -1,7 +1,7 @@
1
1
  // This Source Code Form is subject to the terms of the Mozilla Public
2
2
  // License, v. 2.0. If a copy of the MPL was not distributed with this
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
- import { BaseLinter } from "./base-linter";
4
+ import { BaseLinter } from "./base-linter.js";
5
5
  /**
6
6
  * Default linter for asset types that have no type-specific rules beyond the
7
7
  * base checks (`unquoted-colon`, `missing-updated`).
@@ -24,7 +24,7 @@
24
24
  * where the operator legitimately wants to set their editor — accept the
25
25
  * FP and bypass with `--allow-insecure` after review.
26
26
  */
27
- import { listKeys } from "../env";
27
+ import { listKeys } from "../env/env.js";
28
28
  // ── Dangerous key set ─────────────────────────────────────────────────────────
29
29
  export const DANGEROUS_VAULT_KEYS = new Set([
30
30
  // Dynamic linker hijacking (Linux glibc ld.so)
@@ -4,12 +4,12 @@
4
4
  import fs from "node:fs";
5
5
  import path from "node:path";
6
6
  import { parse as parseYaml } from "yaml";
7
- import { resolveStashDir } from "../../core/common";
8
- import { loadConfig } from "../../core/config";
9
- import { parseFrontmatter } from "../../core/frontmatter";
10
- import { resolveSourceEntries } from "../../indexer/search-source";
11
- import { checkVaultForDangerousKeys } from "./env-key-rules";
12
- import { getLinterForType } from "./registry";
7
+ import { parseFrontmatter } from "../../core/asset/frontmatter.js";
8
+ import { resolveStashDir } from "../../core/common.js";
9
+ import { loadConfig } from "../../core/config/config.js";
10
+ import { resolveSourceEntries } from "../../indexer/search/search-source.js";
11
+ import { checkEnvForDangerousKeys } from "./env-key-rules.js";
12
+ import { getLinterForType } from "./registry.js";
13
13
  // ── Constants ─────────────────────────────────────────────────────────────────
14
14
  const STASH_SUBDIRS = [
15
15
  "agents",
@@ -154,27 +154,21 @@ export function akmLint(options = {}) {
154
154
  }
155
155
  }
156
156
  // ── Env dangerous-key pass ─────────────────────────────────────────────────
157
- // Scan every `.env` file under <stashRoot>/env/ (and the deprecated
158
- // <stashRoot>/vaults/) across all stash roots for keys that are known to
159
- // enable process-execution hijacking. Warn-only — findings go into `flagged`,
160
- // never `fixed`.
157
+ // Scan every `.env` file under <stashRoot>/env/ across all stash roots for
158
+ // keys that are known to enable process-execution hijacking. Warn-only
159
+ // findings go into `flagged`, never `fixed`.
161
160
  const envRoots = [stashRoot, ...extraStashRoots];
162
161
  for (const root of envRoots) {
163
- for (const [subdir, prefix] of [
164
- ["env", "env"],
165
- ["vaults", "vault"],
166
- ]) {
167
- const dir = path.join(root, subdir);
168
- if (!fs.existsSync(dir))
169
- continue;
170
- for (const envPath of collectEnvFiles(dir)) {
171
- const baseName = path.basename(envPath, ".env");
172
- // "default" (or empty) maps to ".env" → <prefix>:default
173
- const ref = baseName === "" ? `${prefix}:default` : `${prefix}:${baseName}`;
174
- const relPath = path.relative(root, envPath);
175
- for (const issue of checkVaultForDangerousKeys(envPath, relPath, ref)) {
176
- flagged.push(issue);
177
- }
162
+ const dir = path.join(root, "env");
163
+ if (!fs.existsSync(dir))
164
+ continue;
165
+ for (const envPath of collectEnvFiles(dir)) {
166
+ const baseName = path.basename(envPath, ".env");
167
+ // "default" (or empty) maps to ".env" → env:default
168
+ const ref = baseName === "" ? "env:default" : `env:${baseName}`;
169
+ const relPath = path.relative(root, envPath);
170
+ for (const issue of checkEnvForDangerousKeys(envPath, relPath, ref)) {
171
+ flagged.push(issue);
178
172
  }
179
173
  }
180
174
  }
@@ -1,7 +1,7 @@
1
1
  // This Source Code Form is subject to the terms of the Mozilla Public
2
2
  // License, v. 2.0. If a copy of the MPL was not distributed with this
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
- import { BaseLinter } from "./base-linter";
4
+ import { BaseLinter } from "./base-linter.js";
5
5
  /**
6
6
  * Linter for `knowledge/` assets.
7
7
  *