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
@@ -8,6 +8,7 @@
8
8
  */
9
9
  import { z } from "zod";
10
10
  import { isBundleSlug } from "../../asset/asset-ref.js";
11
+ import { hasRegistryUrlCredentials, REGISTRY_CREDENTIALS_UNSUPPORTED } from "../../registry-url.js";
11
12
  import { httpUrl, nonEmptyString, positiveInt } from "./primitives.js";
12
13
  // ── Sources / registries / installed ────────────────────────────────────────
13
14
  const SourceConfigEntryOptionsSchema = z.record(z.unknown());
@@ -49,7 +50,11 @@ export const SourceConfigEntrySchema = z
49
50
  });
50
51
  export const RegistryConfigEntrySchema = z
51
52
  .object({
52
- url: httpUrl,
53
+ url: httpUrl.superRefine((value, ctx) => {
54
+ if (hasRegistryUrlCredentials(value)) {
55
+ ctx.addIssue({ code: z.ZodIssueCode.custom, message: REGISTRY_CREDENTIALS_UNSUPPORTED });
56
+ }
57
+ }),
53
58
  name: z.string().min(1).optional(),
54
59
  enabled: z.boolean().optional(),
55
60
  provider: z.string().min(1).optional(),
@@ -58,9 +63,8 @@ export const RegistryConfigEntrySchema = z
58
63
  .passthrough();
59
64
  // ── Bundles (0.9.0 config-shape cutover, spec §10.1 / D-R5) ─────────────────
60
65
  //
61
- // `bundles` + `defaultBundle` are the 0.9.0 desired-configuration shape that
62
- // supersedes the pre-cutover `stashDir` / `sources[]` / `installed[]` trio. Each
63
- // bundle entry carries ONE source descriptor (`path` | `git` | `website` | `npm`
66
+ // `bundles` + `defaultBundle` are the desired-configuration shape. Each bundle
67
+ // entry carries ONE source descriptor (`path` | `git` | `website` | `npm`
64
68
  // — mirroring today's source types), an optional `writable`, an optional
65
69
  // `registryId` locator (the original registry install id, preserved verbatim so
66
70
  // a non-slug-legal id like `github:owner/repo` is not lost when its slug-legal
@@ -69,10 +73,7 @@ export const RegistryConfigEntrySchema = z
69
73
  // machinery). The map KEY is the workspace bundle slug (spec §11.1 charset: no
70
74
  // `/`, `:`, `.`, `#`, whitespace), validated with {@link isBundleSlug}.
71
75
  //
72
- // The config migrator ({@link migrateConfigSourcesToBundles}) emits these keyed
73
- // by exactly what `deriveInstallations` derives today (D-R5). `bindings` (spec
74
- // §10.1) is Tier B and is NEVER accepted here (the top-level superRefine rejects
75
- // it) — it is not part of the 0.9.0 config-shape cutover.
76
+ // `bindings` is not accepted here; the top-level schema rejects it.
76
77
  /** Website source descriptor for a bundle entry (spec §10.1). */
77
78
  const BundleWebsiteDescriptorSchema = z
78
79
  .object({
@@ -110,15 +111,11 @@ export const BundleConfigEntrySchema = z
110
111
  npm: z.string().min(1).optional(),
111
112
  writable: z.boolean().optional(),
112
113
  // Opt a bundle out of indexing, search, refresh, and write targeting
113
- // without deleting it. Carried over from the pre-cutover `sources[].enabled`
114
- // flag, which the runtime still honors on the derived source entry
115
- // (`write-source.ts`, `search-source.ts`); without it here, migrating a
116
- // disabled source would silently reactivate it.
114
+ // without deleting it. The runtime honors the derived value in write and
115
+ // search source selection.
117
116
  enabled: z.boolean().optional(),
118
- // The original registry install id when the bundle KEY was slug-derived from
119
- // it (e.g. registryId `github:owner/repo` → key `repo`). Preserved so the
120
- // source locator survives the config-shape migration (D-R5). Absent when the
121
- // bundle key already equals the source's stable id.
117
+ // The registry install id when the bundle key was slug-derived from it
118
+ // (e.g. registryId `github:owner/repo` → key `repo`).
122
119
  registryId: z.string().min(1).optional(),
123
120
  components: z.record(z.string().min(1), BundleComponentConfigSchema).optional(),
124
121
  })
@@ -10,12 +10,14 @@ const CONFIG_HINTS = {
10
10
  STASH_DIR_NOT_A_DIRECTORY: "The configured default bundle path exists but isn't a directory. Update it to point at a folder.",
11
11
  STASH_DIR_UNREADABLE: "Check the path exists and your user has read permission, or update the default bundle path.",
12
12
  DATA_DIR_UNREADABLE: "The data directory is not readable by the user running akm. Check its owner and mode, or point AKM_DATA_DIR / XDG_DATA_HOME somewhere this user owns.",
13
+ INDEX_SCHEMA_INCOMPATIBLE: "Run `akm index --full` to rebuild the derived index from the currently materialized sources.",
13
14
  EMBEDDING_NOT_CONFIGURED: 'Run `akm config set embedding \'{"endpoint":"...","model":"..."}\'` to enable embeddings.',
14
15
  LLM_NOT_CONFIGURED: 'Run `akm setup` or configure an `engines` entry with `kind: "llm"`, then select it with `defaults.llmEngine`.',
15
16
  TEST_ISOLATION_MISSING: "Under bun test, when AKM_BUNDLE_DIR is set you MUST also set XDG_DATA_HOME (or AKM_DATA_DIR) and XDG_STATE_HOME (or AKM_STATE_DIR) to temp directories so the test does not touch the developer's real ~/.local/share/akm or ~/.local/state/akm.",
16
17
  SETUP_TMP_STASH_REFUSED: "Use a persistent directory, or set AKM_FORCE_SETUP_TMP_STASH=1 to opt in to a sandboxed setup (setup also pre-sets AKM_BUNDLE_DIR so config and cache writes auto-isolate into $stashDir/.akm/ — host config is preserved).",
17
18
  UNSAFE_STASH_DIR: "Choose a path inside your home directory (e.g. ~/akm) or another empty workspace. The bundle directory cannot be the filesystem root, your home directory itself, or a sensitive system path like /etc, /var, ~/.config, or ~/.ssh.",
18
19
  UNKNOWN_IMPROVE_STRATEGY: "Pass one of the listed strategy names to `--strategy`, or define it under `improve.strategies`. Names are case-sensitive.",
20
+ EXECUTION_NOT_AUTHORIZED: "Change the selected tools or update the machine/user execution policy, then retry.",
19
21
  };
20
22
  /** Default hint for each UsageError code. */
21
23
  const USAGE_HINTS = {
@@ -1,10 +1,34 @@
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
+ /**
5
+ * Append-only events stream — backed by state.db (#204, Phase 3).
6
+ *
7
+ * Every mutating CLI verb funnels through `appendEvent` so external
8
+ * observers (sync, replication, audit, dashboards) can react to stash
9
+ * changes. Events are stored in the `events` table in `state.db`
10
+ * (SQLite, WAL mode) instead of a flat `events.jsonl` file.
11
+ *
12
+ * The helper is the only thing in akm that writes to the events table. It
13
+ * accepts an injectable `dbPath` (via `EventsContext`) so tests can pin a
14
+ * tmpdir without any global mutation.
15
+ *
16
+ * Format (each EventEnvelope):
17
+ * { "schemaVersion": 1, "id": <number>, "ts": "<ISO>",
18
+ * "eventType": "<verb>", "ref"?: "<asset-ref>", ... }
19
+ *
20
+ * - `id` is a monotonic SQLite AUTOINCREMENT rowid. Callers can persist it
21
+ * as a durable cursor for `--since` resumption. The public API surfaces this
22
+ * as the opaque `nextOffset` number.
23
+ * - `ts` is ISO-8601 (UTC, millisecond precision).
24
+ */
25
+ import fs from "node:fs";
26
+ import { openDatabase } from "../storage/database.js";
4
27
  import { insertEvent, readStateEvents } from "../storage/repositories/events-repository.js";
5
28
  import { rethrowIfTestIsolationError } from "./errors.js";
6
29
  import { getStateDbPath, openStateDatabase, withStateDb } from "./state-db.js";
7
30
  import { borrowScopedStateDb } from "./state-db-scope.js";
31
+ import { isReadOnlyFilesystemError } from "./system-error.js";
8
32
  import { error } from "./warn.js";
9
33
  /**
10
34
  * Resolve the state.db path from context:
@@ -39,6 +63,8 @@ function resolveNow(ctx) {
39
63
  * connection.
40
64
  */
41
65
  export function appendEvent(input, ctx) {
66
+ if (ctx?.readOnly)
67
+ return;
42
68
  const now = resolveNow(ctx);
43
69
  const ts = new Date(now()).toISOString();
44
70
  const row = { eventType: input.eventType, ts, ref: input.ref, metadata: input.metadata };
@@ -68,31 +94,40 @@ export function appendEvent(input, ctx) {
68
94
  catch (err) {
69
95
  // Never mask the bun-test isolation guard as a silent "events failed".
70
96
  rethrowIfTestIsolationError(err);
97
+ // Read-only sandboxes can serve an existing index but cannot create the
98
+ // maintenance lock or state DB used by best-effort usage tracking. That is
99
+ // expected for a read verb; unrelated storage failures remain visible.
100
+ if (isReadOnlyFilesystemError(err))
101
+ return;
71
102
  // Best-effort: events stream failures must not break the mutating verb.
72
103
  // Surface once to stderr so operators can diagnose.
73
104
  error(`akm: appendEvent failed: ${String(err)}`);
74
105
  }
75
106
  }
76
- /**
77
- * 0.9.0 breaking change (owner ruling 12): `akm sync` used to persist
78
- * `eventType: "save"`; it now writes `"sync"` instead (matching the command
79
- * name). Existing `state.db` rows — and any user script running
80
- * `akm log --type save` — still carry the old spelling. Rather than
81
- * rewriting historical rows (a migration users never asked for, on data we
82
- * don't get to touch at rest), reads treat the two names as synonyms: asking
83
- * for either "save" or "sync" returns rows written under both names. Only
84
- * the WRITE path (sources-cli.ts's `runSyncBody`) changed.
85
- */
86
- const SAVE_SYNC_EVENT_TYPE_ALIASES = new Set(["save", "sync"]);
87
107
  /**
88
108
  * Read all events matching the filter. Returns a `nextOffset` that callers
89
109
  * can persist between processes for monotonic resumption.
90
110
  */
91
111
  export function readEvents(options = {}, ctx) {
112
+ if (ctx?.readOnlySnapshotUnavailable)
113
+ return { events: [], nextOffset: 0 };
92
114
  const dbPath = resolveDbPath(ctx);
93
115
  let db;
116
+ let ownsDb = false;
94
117
  try {
95
- db = openStateDatabase(dbPath);
118
+ if (ctx?.db) {
119
+ db = ctx.db;
120
+ }
121
+ else if (ctx?.readOnly) {
122
+ if (!fs.existsSync(dbPath))
123
+ return { events: [], nextOffset: 0 };
124
+ db = openDatabase(dbPath, { readonly: true, create: false });
125
+ ownsDb = true;
126
+ }
127
+ else {
128
+ db = openStateDatabase(dbPath);
129
+ ownsDb = true;
130
+ }
96
131
  }
97
132
  catch (err) {
98
133
  // Never mask the bun-test isolation guard as "no events".
@@ -101,12 +136,7 @@ export function readEvents(options = {}, ctx) {
101
136
  return { events: [], nextOffset: 0 };
102
137
  }
103
138
  try {
104
- // A "save"/"sync" query can't be expressed as a single SQL `event_type =
105
- // ?` match (see SAVE_SYNC_EVENT_TYPE_ALIASES above), so widen the SQL
106
- // filter to "no type filter" for that one case and apply the alias match
107
- // client-side alongside the existing tag post-filter below.
108
- const typeIsAliased = options.type !== undefined && SAVE_SYNC_EVENT_TYPE_ALIASES.has(options.type);
109
- // D-38: a JS-side post-filter (the type alias above, or the tag filters
139
+ // D-38: a JS-side post-filter (the tag filters
110
140
  // below) runs AFTER the SQL read, so a SQL-level LIMIT applied before it
111
141
  // could drop rows the post-filter would have kept out anyway, silently
112
142
  // returning fewer than `limit` (or the wrong — oldest-in-the-SQL-window —
@@ -114,21 +144,26 @@ export function readEvents(options = {}, ctx) {
114
144
  // downstream can shrink the result further; otherwise read unbounded (the
115
145
  // pre-existing behavior) and apply `limit` ourselves, below, AFTER the
116
146
  // post-filter runs.
117
- const needsPostFilter = typeIsAliased ||
118
- (options.excludeTags?.length ?? 0) > 0 ||
119
- (options.includeTags?.length ?? 0) > 0 ||
120
- options.runId !== undefined;
147
+ const needsPostFilter = (options.excludeTags?.length ?? 0) > 0 || (options.includeTags?.length ?? 0) > 0 || options.runId !== undefined;
121
148
  const pushLimitToSql = options.limit !== undefined && !needsPostFilter;
122
- const { events: rawEvents, nextId } = readStateEvents(db, {
123
- sinceId: options.sinceOffset,
124
- since: options.since,
125
- type: typeIsAliased ? undefined : options.type,
126
- ref: options.ref,
127
- ...(pushLimitToSql ? { limit: options.limit } : {}),
128
- });
149
+ let rawEvents;
150
+ let nextId;
151
+ try {
152
+ ({ events: rawEvents, nextId } = readStateEvents(db, {
153
+ sinceId: options.sinceOffset,
154
+ since: options.since,
155
+ type: options.type,
156
+ ref: options.ref,
157
+ ...(pushLimitToSql ? { limit: options.limit } : {}),
158
+ }));
159
+ }
160
+ catch (error) {
161
+ if (ctx?.readOnly && error instanceof Error && /no such table:/i.test(error.message)) {
162
+ return { events: [], nextOffset: options.sinceOffset ?? 0 };
163
+ }
164
+ throw error;
165
+ }
129
166
  const filtered = rawEvents.filter((envelope) => {
130
- if (typeIsAliased && !SAVE_SYNC_EVENT_TYPE_ALIASES.has(envelope.eventType))
131
- return false;
132
167
  if (options.runId !== undefined && envelope.metadata?.runId !== options.runId)
133
168
  return false;
134
169
  // Apply tag filters after the indexed state.db read.
@@ -148,6 +183,7 @@ export function readEvents(options = {}, ctx) {
148
183
  return { events, nextOffset: nextId };
149
184
  }
150
185
  finally {
151
- db.close();
186
+ if (ownsDb)
187
+ db.close();
152
188
  }
153
189
  }
@@ -10,6 +10,7 @@ export const EXTRA_PARAMS_PROTECTED_TOP_LEVEL_KEYS = [
10
10
  "stream",
11
11
  "streamoptions",
12
12
  "enablethinking",
13
+ "reasoningeffort",
13
14
  "chattemplatekwargs",
14
15
  ];
15
16
  export const EXTRA_PARAMS_CREDENTIAL_KEYS = [
@@ -1,17 +1,20 @@
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 { cloneExecutionJsonObject } from "../execution/json.js";
4
5
  const COMMON_FIELDS = [
5
6
  "schemaVersion",
6
7
  "ok",
7
8
  "scope",
8
9
  "dryRun",
10
+ "notices",
9
11
  "skipped",
10
12
  "guidance",
11
13
  "memorySummary",
12
14
  "memoryCleanup",
13
15
  "cyclesRun",
14
16
  "plannedRefs",
17
+ "plan",
15
18
  "actions",
16
19
  "distillSkipped",
17
20
  "validationFailures",
@@ -55,6 +58,308 @@ function requireExactFields(value, allowed) {
55
58
  if (unknown.length > 0)
56
59
  fail(`unknown field${unknown.length === 1 ? "" : "s"}: ${unknown.sort().join(", ")}`);
57
60
  }
61
+ function requireNumber(value, field, path) {
62
+ if (typeof value[field] !== "number" || !Number.isFinite(value[field]))
63
+ fail(`${path}.${field} must be a number`);
64
+ }
65
+ function requireCount(value, field, path) {
66
+ requireNumber(value, field, path);
67
+ if (!Number.isSafeInteger(value[field]) || value[field] < 0) {
68
+ fail(`${path}.${field} must be a non-negative integer`);
69
+ }
70
+ }
71
+ function validateLoweringNotices(value) {
72
+ if (!Array.isArray(value))
73
+ fail("notices must be an array");
74
+ for (let index = 0; index < value.length; index += 1) {
75
+ const notice = value[index];
76
+ if (!isRecord(notice))
77
+ fail(`notices[${index}] must be an object`);
78
+ requireExactFields(notice, new Set(["code", "severity", "adapter", "field", "message", "details"]));
79
+ for (const field of ["code", "adapter", "message"]) {
80
+ if (typeof notice[field] !== "string")
81
+ fail(`notices[${index}].${field} must be a string`);
82
+ }
83
+ if (notice.severity !== "info" && notice.severity !== "warning") {
84
+ fail(`notices[${index}].severity is invalid`);
85
+ }
86
+ if (notice.field !== undefined && notice.field !== null && typeof notice.field !== "string") {
87
+ fail(`notices[${index}].field must be a string or null`);
88
+ }
89
+ if (notice.details !== undefined && notice.details !== null) {
90
+ try {
91
+ cloneExecutionJsonObject(notice.details, `notices[${index}].details`);
92
+ }
93
+ catch {
94
+ fail(`notices[${index}].details must be a JSON object or null`);
95
+ }
96
+ }
97
+ }
98
+ }
99
+ function validateConsolidationPlan(value) {
100
+ if (!isRecord(value))
101
+ fail("plan.consolidation must be an object");
102
+ requireExactFields(value, new Set([
103
+ "configured",
104
+ "effective",
105
+ "poolSize",
106
+ "candidatePoolSize",
107
+ "gates",
108
+ "wouldRun",
109
+ "reason",
110
+ "estimatedChunks",
111
+ ]));
112
+ if (!isRecord(value.configured))
113
+ fail("plan.consolidation.configured must be an object");
114
+ requireExactFields(value.configured, new Set(["enabled", "minPoolSize", "limit", "maxChunkSize", "incrementalSince"]));
115
+ if (value.configured.enabled !== undefined && typeof value.configured.enabled !== "boolean") {
116
+ fail("plan.consolidation.configured.enabled must be a boolean");
117
+ }
118
+ for (const field of ["minPoolSize", "limit", "maxChunkSize"]) {
119
+ if (value.configured[field] !== undefined && typeof value.configured[field] !== "number") {
120
+ fail(`plan.consolidation.configured.${field} must be a number`);
121
+ }
122
+ }
123
+ if (value.configured.incrementalSince !== undefined && typeof value.configured.incrementalSince !== "string") {
124
+ fail("plan.consolidation.configured.incrementalSince must be a string");
125
+ }
126
+ if (!isRecord(value.effective))
127
+ fail("plan.consolidation.effective must be an object");
128
+ requireExactFields(value.effective, new Set(["enabled", "minPoolSize", "limit", "chunkSize"]));
129
+ if (typeof value.effective.enabled !== "boolean")
130
+ fail("plan.consolidation.effective.enabled must be a boolean");
131
+ requireCount(value.effective, "minPoolSize", "plan.consolidation.effective");
132
+ requireCount(value.effective, "chunkSize", "plan.consolidation.effective");
133
+ if (value.effective.limit !== undefined && typeof value.effective.limit !== "number") {
134
+ fail("plan.consolidation.effective.limit must be a number");
135
+ }
136
+ requireCount(value, "poolSize", "plan.consolidation");
137
+ requireCount(value, "candidatePoolSize", "plan.consolidation");
138
+ requireCount(value, "estimatedChunks", "plan.consolidation");
139
+ if (typeof value.wouldRun !== "boolean")
140
+ fail("plan.consolidation.wouldRun must be a boolean");
141
+ if (typeof value.reason !== "string")
142
+ fail("plan.consolidation.reason must be a string");
143
+ if (!isRecord(value.gates))
144
+ fail("plan.consolidation.gates must be an object");
145
+ requireExactFields(value.gates, new Set(["profile", "minimumPool", "delta"]));
146
+ for (const gateName of ["profile", "minimumPool", "delta"]) {
147
+ const gate = value.gates[gateName];
148
+ if (!isRecord(gate))
149
+ fail(`plan.consolidation.gates.${gateName} must be an object`);
150
+ requireExactFields(gate, new Set(["passed", "reason"]));
151
+ if (typeof gate.passed !== "boolean" || typeof gate.reason !== "string") {
152
+ fail(`plan.consolidation.gates.${gateName} must contain boolean passed and string reason`);
153
+ }
154
+ }
155
+ }
156
+ function validateProactivePlan(value) {
157
+ if (!isRecord(value))
158
+ fail("plan.proactive must be an object");
159
+ requireExactFields(value, new Set(["configured", "effective", "candidatePool", "dueTotal", "neverReflected", "selected", "selectedRefs"]));
160
+ if (!isRecord(value.configured))
161
+ fail("plan.proactive.configured must be an object");
162
+ requireExactFields(value.configured, new Set(["dueDays", "maxPerRun", "limit"]));
163
+ for (const field of ["dueDays", "maxPerRun", "limit"]) {
164
+ if (value.configured[field] !== undefined && typeof value.configured[field] !== "number") {
165
+ fail(`plan.proactive.configured.${field} must be a number`);
166
+ }
167
+ }
168
+ if (!isRecord(value.effective))
169
+ fail("plan.proactive.effective must be an object");
170
+ requireExactFields(value.effective, new Set(["dueDays", "maxPerRun"]));
171
+ requireNumber(value.effective, "dueDays", "plan.proactive.effective");
172
+ requireNumber(value.effective, "maxPerRun", "plan.proactive.effective");
173
+ for (const field of ["candidatePool", "dueTotal", "neverReflected", "selected"]) {
174
+ requireCount(value, field, "plan.proactive");
175
+ }
176
+ if (!Array.isArray(value.selectedRefs) || value.selectedRefs.some((ref) => typeof ref !== "string")) {
177
+ fail("plan.proactive.selectedRefs must be an array of strings");
178
+ }
179
+ if (value.selected !== value.selectedRefs.length) {
180
+ fail("plan.proactive.selected must equal plan.proactive.selectedRefs.length");
181
+ }
182
+ }
183
+ function validateImprovePlan(value, dryRun, plannedRefNames) {
184
+ if (!isRecord(value))
185
+ fail("plan must be an object");
186
+ requireExactFields(value, new Set([
187
+ "mode",
188
+ "dispatch",
189
+ "snapshot",
190
+ "candidates",
191
+ "limits",
192
+ "gates",
193
+ "effectiveRefs",
194
+ "proactive",
195
+ "consolidation",
196
+ "stages",
197
+ "triage",
198
+ ]));
199
+ if (value.mode !== "estimate" && value.mode !== "execution")
200
+ fail('plan.mode must be "estimate" or "execution"');
201
+ if (typeof value.dispatch !== "boolean")
202
+ fail("plan.dispatch must be a boolean");
203
+ if ((value.mode === "estimate" && value.dispatch) || (value.mode === "execution" && !value.dispatch)) {
204
+ fail("plan.dispatch must be false for estimate mode and true for execution mode");
205
+ }
206
+ if (dryRun && (value.mode !== "estimate" || value.dispatch !== false)) {
207
+ fail("dryRun=true requires estimate mode with dispatch=false");
208
+ }
209
+ if (!dryRun && (value.mode !== "execution" || value.dispatch !== true)) {
210
+ fail("dryRun=false requires execution mode with dispatch=true");
211
+ }
212
+ if (!isRecord(value.snapshot))
213
+ fail("plan.snapshot must be an object");
214
+ requireExactFields(value.snapshot, new Set(["status", "reason"]));
215
+ if (typeof value.snapshot.status !== "string" ||
216
+ !new Set(["ready", "missing", "incompatible", "unknown"]).has(value.snapshot.status)) {
217
+ fail("plan.snapshot.status is invalid");
218
+ }
219
+ if (typeof value.snapshot.reason !== "string")
220
+ fail("plan.snapshot.reason must be a string");
221
+ if (!isRecord(value.candidates))
222
+ fail("plan.candidates must be an object");
223
+ requireExactFields(value.candidates, new Set(["rawInScope", "selected", "effective"]));
224
+ for (const field of ["rawInScope", "selected", "effective"]) {
225
+ requireCount(value.candidates, field, "plan.candidates");
226
+ }
227
+ if (value.candidates.rawInScope < value.candidates.selected) {
228
+ fail("plan.candidates.rawInScope cannot be less than plan.candidates.selected");
229
+ }
230
+ if (value.candidates.selected < value.candidates.effective) {
231
+ fail("plan.candidates.selected cannot be less than plan.candidates.effective");
232
+ }
233
+ if (!isRecord(value.limits))
234
+ fail("plan.limits must be an object");
235
+ requireExactFields(value.limits, new Set(["configured", "effective", "additiveReplayAllowance", "totalCeiling"]));
236
+ if (!isRecord(value.limits.configured))
237
+ fail("plan.limits.configured must be an object");
238
+ requireExactFields(value.limits.configured, new Set(["cli", "profile", "reflect"]));
239
+ for (const field of ["cli", "profile", "reflect"]) {
240
+ if (value.limits.configured[field] !== undefined)
241
+ requireCount(value.limits.configured, field, "plan.limits.configured");
242
+ }
243
+ if (value.limits.effective !== undefined)
244
+ requireCount(value.limits, "effective", "plan.limits");
245
+ requireCount(value.limits, "additiveReplayAllowance", "plan.limits");
246
+ if (value.limits.totalCeiling !== undefined)
247
+ requireCount(value.limits, "totalCeiling", "plan.limits");
248
+ if (value.limits.effective === undefined && value.limits.totalCeiling !== undefined) {
249
+ fail("plan.limits.totalCeiling must be omitted when plan.limits.effective is unbounded");
250
+ }
251
+ if (value.limits.effective !== undefined &&
252
+ value.limits.totalCeiling !== value.limits.effective + value.limits.additiveReplayAllowance) {
253
+ fail("plan.limits.totalCeiling must equal plan.limits.effective + plan.limits.additiveReplayAllowance");
254
+ }
255
+ const gateNames = new Set(["profile", "cleanup", "validation", "signal", "disk", "limit"]);
256
+ if (!Array.isArray(value.gates))
257
+ fail("plan.gates must be an array");
258
+ const gateRemovedByName = new Map();
259
+ for (const gate of value.gates) {
260
+ if (!isRecord(gate))
261
+ fail("plan.gates entries must be objects");
262
+ requireExactFields(gate, new Set(["name", "removed", "reason"]));
263
+ if (typeof gate.name !== "string" || !gateNames.has(gate.name))
264
+ fail("plan.gates.name is invalid");
265
+ requireCount(gate, "removed", "plan.gates entry");
266
+ if (typeof gate.reason !== "string")
267
+ fail("plan.gates.reason must be a string");
268
+ if (gateRemovedByName.has(gate.name))
269
+ fail(`plan.gates must contain exactly one ${gate.name} gate`);
270
+ gateRemovedByName.set(gate.name, gate.removed);
271
+ }
272
+ for (const gateName of gateNames) {
273
+ if (!gateRemovedByName.has(gateName))
274
+ fail(`plan.gates must contain exactly one ${gateName} gate`);
275
+ }
276
+ const preLimitRemoved = [...gateRemovedByName]
277
+ .filter(([gateName]) => gateName !== "limit")
278
+ .reduce((total, [, removed]) => total + removed, 0);
279
+ if (preLimitRemoved !== value.candidates.rawInScope - value.candidates.selected) {
280
+ fail("plan pre-limit gate removals must equal plan.candidates.rawInScope - plan.candidates.selected");
281
+ }
282
+ if (gateRemovedByName.get("limit") !==
283
+ value.candidates.selected - value.candidates.effective) {
284
+ fail("plan limit removal must equal plan.candidates.selected - plan.candidates.effective");
285
+ }
286
+ const lanes = new Set([
287
+ "scope",
288
+ "signal-delta",
289
+ "proactive",
290
+ "high-salience",
291
+ "distill-only",
292
+ "forgetting-safety",
293
+ "replay",
294
+ "unknown",
295
+ ]);
296
+ const reasons = new Set(["scope-ref", "scope-type", "memory-cleanup", "strategy_filtered_all_passes"]);
297
+ if (!Array.isArray(value.effectiveRefs))
298
+ fail("plan.effectiveRefs must be an array");
299
+ for (const entry of value.effectiveRefs) {
300
+ if (!isRecord(entry))
301
+ fail("plan.effectiveRefs entries must be objects");
302
+ requireExactFields(entry, new Set(["ref", "lane", "reason"]));
303
+ if (typeof entry.ref !== "string")
304
+ fail("plan.effectiveRefs.ref must be a string");
305
+ if (typeof entry.lane !== "string" || !lanes.has(entry.lane))
306
+ fail("plan.effectiveRefs.lane is invalid");
307
+ if (typeof entry.reason !== "string" || !reasons.has(entry.reason))
308
+ fail("plan.effectiveRefs.reason is invalid");
309
+ }
310
+ if (value.candidates.effective !== value.effectiveRefs.length) {
311
+ fail("plan.candidates.effective must equal plan.effectiveRefs.length");
312
+ }
313
+ const effectiveRefNames = value.effectiveRefs.map((entry) => entry.ref);
314
+ if (new Set(effectiveRefNames).size !== effectiveRefNames.length) {
315
+ fail("plan.effectiveRefs must not contain duplicate refs");
316
+ }
317
+ if (plannedRefNames.length !== effectiveRefNames.length ||
318
+ plannedRefNames.some((ref, index) => ref !== effectiveRefNames[index])) {
319
+ fail("plannedRefs must contain the same refs in the same order as plan.effectiveRefs");
320
+ }
321
+ const replayCount = value.effectiveRefs.filter((entry) => entry.lane === "replay").length;
322
+ const ordinaryCount = value.effectiveRefs.length - replayCount;
323
+ if (replayCount > value.limits.additiveReplayAllowance) {
324
+ fail("plan replay refs cannot exceed plan.limits.additiveReplayAllowance");
325
+ }
326
+ if (value.limits.effective !== undefined && ordinaryCount > value.limits.effective) {
327
+ fail("plan ordinary refs cannot exceed plan.limits.effective");
328
+ }
329
+ if (value.limits.totalCeiling !== undefined && value.effectiveRefs.length > value.limits.totalCeiling) {
330
+ fail("plan.effectiveRefs cannot exceed plan.limits.totalCeiling");
331
+ }
332
+ if (value.proactive !== undefined)
333
+ validateProactivePlan(value.proactive);
334
+ validateConsolidationPlan(value.consolidation);
335
+ const stageNames = new Set(["consolidation", "extract", "graph-extraction", "memory-inference"]);
336
+ if (!Array.isArray(value.stages))
337
+ fail("plan.stages must be an array");
338
+ for (const stage of value.stages) {
339
+ if (!isRecord(stage))
340
+ fail("plan.stages entries must be objects");
341
+ requireExactFields(stage, new Set(["name", "wouldRun", "reason"]));
342
+ if (typeof stage.name !== "string" || !stageNames.has(stage.name))
343
+ fail("plan.stages.name is invalid");
344
+ if (typeof stage.wouldRun !== "boolean" || typeof stage.reason !== "string") {
345
+ fail("plan.stages entries must contain boolean wouldRun and string reason");
346
+ }
347
+ }
348
+ if (!isRecord(value.triage))
349
+ fail("plan.triage must be an object");
350
+ requireExactFields(value.triage, new Set(["enabled", "configuredMode", "mode", "maxAcceptsPerRun", "maxDiffLines"]));
351
+ if (typeof value.triage.enabled !== "boolean")
352
+ fail("plan.triage.enabled must be a boolean");
353
+ for (const field of ["configuredMode", "mode"]) {
354
+ if (value.triage[field] !== "queue" && value.triage[field] !== "promote") {
355
+ fail(`plan.triage.${field} must be queue or promote`);
356
+ }
357
+ }
358
+ requireCount(value.triage, "maxAcceptsPerRun", "plan.triage");
359
+ if (value.triage.maxDiffLines !== undefined && typeof value.triage.maxDiffLines !== "number") {
360
+ fail("plan.triage.maxDiffLines must be a number");
361
+ }
362
+ }
58
363
  function validateCommon(value) {
59
364
  if (typeof value.ok !== "boolean")
60
365
  fail("ok must be a boolean");
@@ -62,6 +367,15 @@ function validateCommon(value) {
62
367
  fail("dryRun must be a boolean");
63
368
  if (!Array.isArray(value.plannedRefs))
64
369
  fail("plannedRefs must be an array");
370
+ if (value.notices !== undefined)
371
+ validateLoweringNotices(value.notices);
372
+ const plannedRefNames = value.plannedRefs.map((entry, index) => {
373
+ if (!isRecord(entry) || typeof entry.ref !== "string")
374
+ fail(`plannedRefs[${index}].ref must be a string`);
375
+ return entry.ref;
376
+ });
377
+ if (value.plan !== undefined)
378
+ validateImprovePlan(value.plan, value.dryRun, plannedRefNames);
65
379
  if (!isRecord(value.scope))
66
380
  fail("scope must be an object");
67
381
  requireExactFields(value.scope, new Set(["mode", "value"]));
@@ -127,6 +441,7 @@ function validateCommon(value) {
127
441
  "cycleMetrics",
128
442
  "sync",
129
443
  "terminated",
444
+ "plan",
130
445
  ]) {
131
446
  if (value[field] !== undefined && !isRecord(value[field]))
132
447
  fail(`${field} must be an object`);
@@ -20,7 +20,6 @@
20
20
  * lets it run from any code path (CLI, proposal-accept, asset-spec tests)
21
21
  * without dragging in the rest of the runtime.
22
22
  */
23
- import fs from "node:fs";
24
23
  import { parseFrontmatter } from "./asset/frontmatter.js";
25
24
  function isNonEmptyString(value) {
26
25
  return typeof value === "string" && value.trim().length > 0;
@@ -66,8 +65,3 @@ export function lintLessonContent(raw, pathForMessages) {
66
65
  }
67
66
  return { path: pathForMessages, findings };
68
67
  }
69
- /** Lint a lesson file on disk. Throws if the file cannot be read. */
70
- export function lintLessonFile(filePath) {
71
- const raw = fs.readFileSync(filePath, "utf8");
72
- return lintLessonContent(raw, filePath);
73
- }
@@ -10,9 +10,9 @@ import { createLockPayload, probeLock, reclaimStaleLock, releaseLock, tryAcquire
10
10
  import { getMaintenanceBarrierPath } from "./paths.js";
11
11
  const heldBarrierContext = new AsyncLocalStorage();
12
12
  /**
13
- * Serialize restore with the short critical section that creates every
14
- * long-lived AKM lock or workflow lease. The long operation keeps its own
15
- * lock/lease; this barrier is released immediately after acquisition.
13
+ * Serialize the short critical section that creates each long-lived AKM lock,
14
+ * lease, or state activity. The operation keeps its own ownership record; this
15
+ * barrier is released immediately after acquisition.
16
16
  */
17
17
  export function tryAcquireMaintenanceBarrier() {
18
18
  const lockPath = getMaintenanceBarrierPath();
@@ -107,7 +107,7 @@ function withMaintenanceStartBarrierSyncWait(run) {
107
107
  release();
108
108
  }
109
109
  }
110
- /** Register a long-lived operation atomically with restore's blocker scan. */
110
+ /** Register a long-lived state operation atomically with other start acquisitions. */
111
111
  export async function acquireMaintenanceActivity(name) {
112
112
  return withMaintenanceStartBarrierAsync(async () => {
113
113
  const directory = path.join(path.dirname(getMaintenanceBarrierPath()), "maintenance-activities");