akm-cli 0.9.31 → 0.10.261002-beta

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 (270) hide show
  1. package/CHANGELOG.md +420 -0
  2. package/STABILITY.md +99 -57
  3. package/dist/api-node.mjs +20 -0
  4. package/dist/api.d.ts +23 -0
  5. package/dist/api.js +45 -0
  6. package/dist/assets/hints/cli-hints-full.md +15 -29
  7. package/dist/assets/hints/cli-hints-short.md +2 -2
  8. package/dist/assets/improve-strategies/default.json +3 -3
  9. package/dist/assets/prompts/consolidate-pair.md +9 -7
  10. package/dist/assets/stash-skeleton/README.md +1 -1
  11. package/dist/assets/tasks/improve/akm-improve-consolidate.yml +1 -1
  12. package/dist/cli/shared.js +31 -48
  13. package/dist/cli/unknown-flags.js +6 -43
  14. package/dist/cli.js +8 -34
  15. package/dist/commands/agent/contribute-cli.js +1 -1
  16. package/dist/commands/completions.js +1 -2
  17. package/dist/commands/config-cli.js +5 -28
  18. package/dist/commands/env/env-cli.js +5 -5
  19. package/dist/commands/env/secret-cli.js +2 -2
  20. package/dist/commands/feedback-cli.js +75 -49
  21. package/dist/commands/health/checks.js +41 -16
  22. package/dist/commands/health/plugin-staleness.js +52 -22
  23. package/dist/commands/improve/autonomy-gate.js +5 -3
  24. package/dist/commands/improve/consolidate/chunking.js +3 -10
  25. package/dist/commands/improve/consolidate/continuity-check.js +4 -4
  26. package/dist/commands/improve/consolidate/pair-pass.js +29 -10
  27. package/dist/commands/improve/consolidate.js +10 -65
  28. package/dist/commands/improve/distill/lesson-update.js +91 -0
  29. package/dist/commands/improve/distill-guards.js +1 -43
  30. package/dist/commands/improve/distill.js +148 -21
  31. package/dist/commands/improve/eligibility.js +14 -1
  32. package/dist/commands/improve/extract-cli.js +1 -1
  33. package/dist/commands/improve/extract-prompt.js +17 -7
  34. package/dist/commands/improve/extract.js +5 -7
  35. package/dist/commands/improve/improve-cli.js +92 -106
  36. package/dist/commands/improve/improve-strategies.js +8 -12
  37. package/dist/commands/improve/improve.js +134 -52
  38. package/dist/commands/improve/loop-stages.js +18 -5
  39. package/dist/commands/improve/planner.js +0 -1
  40. package/dist/commands/improve/preparation.js +40 -99
  41. package/dist/commands/improve/proactive-maintenance.js +2 -2
  42. package/dist/commands/improve/reflect-noise.js +3 -55
  43. package/dist/commands/improve/reflect.js +29 -34
  44. package/dist/commands/improve/retrieval-queries.js +37 -0
  45. package/dist/commands/improve/retrieval-scope.js +2 -2
  46. package/dist/commands/improve/salience.js +2 -9
  47. package/dist/commands/improve/session-asset.js +4 -1
  48. package/dist/commands/improve/stage.js +23 -12
  49. package/dist/commands/lint/index.js +5 -20
  50. package/dist/commands/observability-cli.js +3 -9
  51. package/dist/commands/proposal/drain.js +43 -6
  52. package/dist/commands/proposal/proposal-cli.js +11 -31
  53. package/dist/commands/proposal/repository.js +20 -14
  54. package/dist/commands/proposal/validators/proposal-quality-validators.js +0 -3
  55. package/dist/commands/read/knowledge.js +3 -3
  56. package/dist/commands/read/remember-cli.js +0 -13
  57. package/dist/commands/read/search-cli.js +29 -97
  58. package/dist/commands/read/search.js +0 -11
  59. package/dist/commands/read/show.js +0 -19
  60. package/dist/commands/sources/plugin-upgrade.js +164 -101
  61. package/dist/commands/sources/sources-cli.js +1 -15
  62. package/dist/commands/sources/stash-cli.js +6 -14
  63. package/dist/commands/tasks/tasks-cli.js +4 -115
  64. package/dist/commands/workflow/plan.js +11 -33
  65. package/dist/commands/workflow-cli.js +2 -7
  66. package/dist/core/adapter/adapter-ids.js +1 -2
  67. package/dist/core/adapter/adapters/agent-skills-adapter.js +0 -4
  68. package/dist/core/adapter/adapters/akm-adapter.js +9 -48
  69. package/dist/core/adapter/adapters/akm-lint.js +10 -26
  70. package/dist/core/adapter/adapters/akm-metadata.js +2 -72
  71. package/dist/core/adapter/adapters/akm-task-adapter.js +0 -5
  72. package/dist/core/adapter/adapters/akm-workflow-adapter.js +9 -28
  73. package/dist/core/adapter/adapters/dotenv-adapter.js +1 -20
  74. package/dist/core/adapter/adapters/generic-files-adapter.js +0 -10
  75. package/dist/core/adapter/adapters/llm-wiki-adapter.js +0 -4
  76. package/dist/core/adapter/adapters/tool-dir-shared.js +0 -24
  77. package/dist/core/adapter/adapters/website-snapshot-adapter.js +4 -4
  78. package/dist/core/adapter/recognize-match.js +3 -4
  79. package/dist/core/common.js +18 -2
  80. package/dist/core/config/config-schema.js +12 -15
  81. package/dist/core/config/config-types.js +6 -15
  82. package/dist/core/config/config-walker.js +6 -23
  83. package/dist/core/config/config.js +17 -80
  84. package/dist/core/config/engine-semantics.js +7 -3
  85. package/dist/core/config/schema/engines.js +7 -7
  86. package/dist/core/config/schema/improve-processes.js +10 -63
  87. package/dist/core/config/schema/improve.js +2 -21
  88. package/dist/core/config/schema/index-config.js +6 -39
  89. package/dist/core/env-secret-ref.js +2 -45
  90. package/dist/core/errors.js +1 -2
  91. package/dist/core/extra-params.js +2 -86
  92. package/dist/core/file-lock.js +2 -6
  93. package/dist/core/improve-result.js +13 -1
  94. package/dist/core/mutation-target.js +2 -2
  95. package/dist/core/paths.js +4 -0
  96. package/dist/core/recognition-util.js +4 -5
  97. package/dist/core/run-lock.js +1 -1
  98. package/dist/core/subprocess.js +49 -0
  99. package/dist/core/warn.js +21 -4
  100. package/dist/core/write-source.js +3 -3
  101. package/dist/execution/target-ref.js +1 -2
  102. package/dist/indexer/indexer.js +15 -6
  103. package/dist/indexer/passes/memory-inference.js +15 -6
  104. package/dist/indexer/search/db-search.js +49 -36
  105. package/dist/indexer/search/fts-query.js +0 -19
  106. package/dist/indexer/search/ranking.js +29 -1
  107. package/dist/indexer/usage/usage-events.js +21 -16
  108. package/dist/integrations/agent/engine-resolution.js +5 -2
  109. package/dist/integrations/agent/index.js +1 -1
  110. package/dist/integrations/agent/profiles.js +41 -98
  111. package/dist/integrations/agent/request-lowering.js +3 -3
  112. package/dist/integrations/harnesses/aider/index.js +5 -0
  113. package/dist/integrations/harnesses/amazonq/index.js +1 -0
  114. package/dist/integrations/harnesses/claude/config-import.js +2 -3
  115. package/dist/integrations/harnesses/claude/index.js +5 -0
  116. package/dist/integrations/harnesses/codex/index.js +1 -0
  117. package/dist/integrations/harnesses/copilot/index.js +1 -0
  118. package/dist/integrations/harnesses/gemini/index.js +1 -0
  119. package/dist/integrations/harnesses/index.js +18 -39
  120. package/dist/integrations/harnesses/opencode/agent-builder.js +60 -35
  121. package/dist/integrations/harnesses/opencode/config-import.js +121 -52
  122. package/dist/integrations/harnesses/opencode/index.js +22 -0
  123. package/dist/integrations/harnesses/opencode/plugin-layout.js +68 -0
  124. package/dist/integrations/harnesses/opencode/session-log-types.js +12 -0
  125. package/dist/integrations/harnesses/opencode/session-log-v1.js +72 -0
  126. package/dist/integrations/harnesses/opencode/session-log-v2.js +140 -0
  127. package/dist/integrations/harnesses/opencode/session-log.js +104 -115
  128. package/dist/integrations/harnesses/opencode/version.js +98 -0
  129. package/dist/integrations/harnesses/opencode-sdk/{harness.js → index.js} +5 -17
  130. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +105 -206
  131. package/dist/integrations/harnesses/opencode-sdk/v1-adapter.js +199 -0
  132. package/dist/integrations/harnesses/opencode-sdk/v2-adapter.js +254 -0
  133. package/dist/integrations/harnesses/opencode-sdk/wire.js +66 -0
  134. package/dist/integrations/harnesses/openhands/index.js +1 -0
  135. package/dist/integrations/harnesses/pi/index.js +1 -0
  136. package/dist/integrations/harnesses/shared.js +2 -0
  137. package/dist/integrations/harnesses/types.js +2 -0
  138. package/dist/llm/client.js +41 -11
  139. package/dist/output/cli-hints.js +1 -1
  140. package/dist/output/context.js +10 -19
  141. package/dist/output/format-exempt.js +5 -8
  142. package/dist/output/shapes/helpers.js +1 -22
  143. package/dist/output/shapes/passthrough.js +0 -1
  144. package/dist/output/shapes.js +3 -31
  145. package/dist/output/stdout.js +5 -1
  146. package/dist/output/text/command-format.js +6 -1
  147. package/dist/output/text/workflow-format.js +4 -22
  148. package/dist/scripts/akm-migrate-node.js +15745 -16711
  149. package/dist/scripts/akm-migrate.js +16318 -17280
  150. package/dist/setup/detect.js +10 -4
  151. package/dist/setup/harness-config-import.js +5 -14
  152. package/dist/setup/setup.js +1 -1
  153. package/dist/setup/steps/tasks.js +12 -13
  154. package/dist/sources/providers/git-stash.js +113 -16
  155. package/dist/storage/repositories/index-utility-repository.js +2 -32
  156. package/dist/storage/repositories/index-vec-repository.js +4 -0
  157. package/dist/storage/repositories/task-history-repository.js +0 -5
  158. package/dist/storage/repositories/workflow-runs-repository.js +5 -15
  159. package/dist/tasks/embedded.js +2 -1
  160. package/dist/tasks/run/run-command-task.js +9 -1
  161. package/dist/tasks/run/run-workflow-task.js +4 -3
  162. package/dist/tasks/run/task-history.js +0 -2
  163. package/dist/tasks/run/task-log.js +38 -1
  164. package/dist/tasks/schedule-gates.js +39 -0
  165. package/dist/tasks/scheduler-binding.js +0 -16
  166. package/dist/tasks/scheduler-sync.js +3 -97
  167. package/dist/tasks/source/bounded-document.js +1 -1
  168. package/dist/tasks/source/task-source-v4.js +2 -12
  169. package/dist/tasks/task-run-reserved-flags.js +2 -31
  170. package/dist/workflows/authoring/authoring.js +6 -6
  171. package/dist/workflows/compile.js +8 -65
  172. package/dist/workflows/exec/child-workflow.js +22 -3
  173. package/dist/workflows/exec/native-executor.js +12 -147
  174. package/dist/workflows/exec/run-workflow.js +31 -103
  175. package/dist/workflows/exec/step-work.js +9 -178
  176. package/dist/workflows/exec/unit-dispatch.js +0 -10
  177. package/dist/workflows/freeze/environment.js +3 -44
  178. package/dist/workflows/freeze/freeze.js +8 -41
  179. package/dist/workflows/freeze/targets/child-workflow.js +27 -63
  180. package/dist/workflows/freeze/targets/command.js +2 -16
  181. package/dist/workflows/freeze/targets/shell.js +0 -2
  182. package/dist/workflows/freeze/task-bindings.js +2 -28
  183. package/dist/workflows/ir/params.js +12 -2
  184. package/dist/workflows/parser.js +82 -232
  185. package/dist/workflows/plan.js +1 -1
  186. package/dist/workflows/renderer.js +10 -19
  187. package/dist/workflows/runtime/run-plan.js +11 -53
  188. package/dist/workflows/runtime/runs.js +4 -27
  189. package/dist/workflows/source-files.js +12 -16
  190. package/dist/workflows/source-semantics.js +1 -93
  191. package/docs/README.md +1 -1
  192. package/docs/agents/README.md +3 -3
  193. package/docs/agents/agent-install.md +1 -1
  194. package/docs/architecture/0.10-process-rules.md +827 -0
  195. package/docs/architecture/adapters.md +17 -31
  196. package/docs/architecture/architecture.md +4 -5
  197. package/docs/architecture/improvement.md +115 -76
  198. package/docs/architecture/internals/improve-workflow.md +22 -16
  199. package/docs/architecture/internals/indexing.md +1 -1
  200. package/docs/architecture/internals/search.md +1 -2
  201. package/docs/architecture/internals/storage-locations.md +6 -6
  202. package/docs/architecture/persisted-data-compat.md +13 -4
  203. package/docs/architecture/specs/0.9.0-decisions.md +6 -2
  204. package/docs/architecture/specs/improve-collapse-churn-detector-design.md +2 -2
  205. package/docs/architecture/specs/workflow-format-unification.md +1 -6
  206. package/docs/architecture/testing/manual-testing-checklist.md +21 -30
  207. package/docs/architecture/testing/task-workflow-manual-tests.md +106 -203
  208. package/docs/architecture/testing/testing-workflow.md +1 -1
  209. package/docs/architecture/workflow-engine.md +5 -56
  210. package/docs/guides/author-workflows.md +39 -69
  211. package/docs/guides/bundles.md +3 -1
  212. package/docs/guides/capture-knowledge.md +1 -1
  213. package/docs/guides/claude-code-vs-akm-workflows.md +1 -2
  214. package/docs/guides/discover-and-load.md +4 -4
  215. package/docs/guides/getting-started.md +1 -1
  216. package/docs/guides/improve-the-library.md +10 -9
  217. package/docs/guides/recipes/headless-install.md +2 -2
  218. package/docs/guides/run-workflows.md +1 -1
  219. package/docs/guides/scheduling.md +9 -1
  220. package/docs/guides/use-with-any-agent.md +49 -4
  221. package/docs/integration/bundling-akm.md +7 -6
  222. package/docs/maintainers/curate-workmap.md +1 -2
  223. package/docs/maintainers/release-checklist.md +33 -1
  224. package/docs/migration/README.md +2 -0
  225. package/docs/migration/release-notes/0.10.0.md +201 -0
  226. package/docs/migration/release-notes/README.md +1 -0
  227. package/docs/migration/v0.9-to-v0.10.md +507 -0
  228. package/docs/posts/akm-command-tour.md +3 -3
  229. package/docs/product-surface.md +1 -1
  230. package/docs/reference/README.md +1 -0
  231. package/docs/reference/api.md +65 -0
  232. package/docs/reference/asset-types.md +3 -5
  233. package/docs/reference/bundle-types.md +2 -4
  234. package/docs/reference/cli.md +160 -188
  235. package/docs/reference/configuration.md +101 -20
  236. package/docs/reference/data-and-telemetry.md +2 -2
  237. package/docs/reference/env-and-secrets.md +2 -2
  238. package/docs/reference/supported-formats.md +3 -4
  239. package/docs/reference/tasks.md +12 -19
  240. package/docs/reference/website-sources.md +1 -1
  241. package/docs/reference/workflow-schema.md +89 -317
  242. package/docs/reference/workflows.md +20 -48
  243. package/package.json +12 -2
  244. package/schemas/akm-config.json +2 -243
  245. package/schemas/akm-task.json +2 -3
  246. package/schemas/akm-workflow.json +47 -84
  247. package/dist/assets/improve-strategies/catchup.json +0 -41
  248. package/dist/assets/improve-strategies/proactive-maintenance.json +0 -24
  249. package/dist/assets/improve-strategies/quick.json +0 -17
  250. package/dist/assets/improve-strategies/reflect-distill.json +0 -29
  251. package/dist/assets/improve-strategies/thorough.json +0 -49
  252. package/dist/assets/prompts/retrieval-relevance-judge.md +0 -6
  253. package/dist/assets/tasks/improve/akm-improve-catchup.yml +0 -6
  254. package/dist/assets/tasks/improve/akm-improve-frequent.yml +0 -4
  255. package/dist/assets/tasks/improve/akm-improve-nightly.yml +0 -4
  256. package/dist/cli/retired-commands.js +0 -118
  257. package/dist/commands/improve/retrieval-gate.js +0 -132
  258. package/dist/commands/tasks/validate.js +0 -176
  259. package/dist/core/config/legacy-source-shape-shim.js +0 -117
  260. package/dist/indexer/feedback/utility-policy.js +0 -75
  261. package/dist/integrations/harnesses/ids.js +0 -28
  262. package/dist/tasks/source/task-source-v3-frozen.js +0 -424
  263. package/dist/tasks/source/task-to-v4.js +0 -863
  264. package/dist/workflows/exec/worktree.js +0 -372
  265. package/dist/workflows/freeze/child-output-references.js +0 -83
  266. package/dist/workflows/freeze/identity.js +0 -22
  267. package/dist/workflows/freeze/targets/script.js +0 -50
  268. package/dist/workflows/freeze/targets/task.js +0 -124
  269. package/dist/workflows/github-yaml.js +0 -506
  270. package/dist/workflows/runtime/run-outputs.js +0 -80
@@ -14,6 +14,7 @@ import { runManagedSubprocess } from "../core/subprocess.js";
14
14
  import { defaultWhich } from "../integrations/agent/detect.js";
15
15
  import { getBuiltinAgentProfile, OPENCODE_SDK_SERVER_BIN } from "../integrations/agent/profiles.js";
16
16
  import { AGENT_DISPATCH_HARNESSES, SESSION_LOG_HARNESSES } from "../integrations/harnesses/index.js";
17
+ import { detectOpencodeMajor, resolveOpencodeBin } from "../integrations/harnesses/opencode/version.js";
17
18
  import { detectHarnessConfigs } from "./harness-config-import.js";
18
19
  let detectOverrides;
19
20
  /** TEST-ONLY. Swap the network/host probes; pass undefined to restore. */
@@ -342,7 +343,8 @@ function findGitRepoRoot(start) {
342
343
  * 3. none.
343
344
  *
344
345
  * The SDK import resolving is NOT evidence the SDK path can run: `@opencode-ai/sdk`
345
- * is a hard dependency of akm-cli itself, so `import()` always succeeds and an
346
+ * (OpenCode 1) and `@opencode/client` (OpenCode 2; the binary's detected major
347
+ * selects) are hard dependencies of akm-cli itself, so `import()` always succeeds and an
346
348
  * import-only check would report `opencode-sdk` on every machine — including
347
349
  * ones where the first agentic command then dies with spawn ENOENT. The SDK
348
350
  * runner spawns `opencode serve` (see `harnesses/opencode-sdk/sdk-runner.ts`),
@@ -355,10 +357,14 @@ function findGitRepoRoot(start) {
355
357
  export async function detectHarness(whichFn = defaultWhich) {
356
358
  // Probed once; reused below so the `opencode` CLI fallback doesn't repeat
357
359
  // the identical full-PATH walk this just performed.
358
- const opencodePath = whichFn(OPENCODE_SDK_SERVER_BIN);
360
+ const opencodePath = whichFn(resolveOpencodeBin(undefined, whichFn));
359
361
  if (opencodePath) {
360
362
  try {
361
- await import("@opencode-ai/sdk");
363
+ // The client package of the major the binary reports.
364
+ if (detectOpencodeMajor(opencodePath).major === 1)
365
+ await import("@opencode-ai/sdk");
366
+ else
367
+ await import("@opencode/client");
362
368
  return "opencode-sdk";
363
369
  }
364
370
  catch {
@@ -368,7 +374,7 @@ export async function detectHarness(whichFn = defaultWhich) {
368
374
  }
369
375
  for (const harness of AGENT_DISPATCH_HARNESSES) {
370
376
  // The SDK harness dispatches without a CLI profile and was decided above;
371
- // an explicit skip so a future BUILTINS entry for it can't silently
377
+ // an explicit skip so a future `profile` for it can't silently
372
378
  // reintroduce it here after the import guard already rejected it.
373
379
  if (harness.id === "opencode-sdk")
374
380
  continue;
@@ -8,8 +8,8 @@
8
8
  * no network) and, if so, reads its config to extract LLM connection details.
9
9
  * API key VALUES are never stored — only the env var names that hold them.
10
10
  *
11
- * To add a new harness: implement {@link HarnessConfigImporter} and append it
12
- * to {@link HARNESS_CONFIG_IMPORTERS}.
11
+ * To add a new harness importer: declare `configImporter` on the harness
12
+ * descriptor (`integrations/harnesses/<id>/index.ts`).
13
13
  *
14
14
  * NOTE: The `detect()` method in each importer overlaps intentionally with
15
15
  * `detectAgentPlatforms()` in `detect.ts`. That function scans for harness
@@ -17,18 +17,9 @@
17
17
  * further by reading and parsing the harness config. They serve different
18
18
  * purposes and should not be deduplicated.
19
19
  */
20
- import { claudeCodeImporter } from "../integrations/harnesses/claude/config-import.js";
21
- import { openCodeImporter } from "../integrations/harnesses/opencode/config-import.js";
22
- // The Claude Code importer was migrated to its harness directory in #563
23
- // (`harnesses/claude/config-import.ts`) and the OpenCode importer in #564
24
- // (`harnesses/opencode/config-import.ts`). Both are imported back into
25
- // HARNESS_CONFIG_IMPORTERS below so detection order is unchanged.
26
- // ── Registry ─────────────────────────────────────────────────────────────────
27
- /**
28
- * Registry of all supported harness config importers.
29
- * To add a new harness: implement {@link HarnessConfigImporter} and append here.
30
- */
31
- export const HARNESS_CONFIG_IMPORTERS = [claudeCodeImporter, openCodeImporter];
20
+ import { HARNESS_REGISTRY } from "../integrations/harnesses/index.js";
21
+ /** Importers declared by the registered harnesses, in registry order. */
22
+ export const HARNESS_CONFIG_IMPORTERS = HARNESS_REGISTRY.flatMap((h) => h.configImporter ? [h.configImporter] : []);
32
23
  /**
33
24
  * Run all importers whose `detect()` returns `true` and collect their configs.
34
25
  *
@@ -625,7 +625,7 @@ export async function runSetupWizard(opts) {
625
625
  }
626
626
  }
627
627
  p.outro(`Configuration saved to ${configPath}\n` +
628
- 'Next: `akm bundle add <source>`, `akm index`, `akm search "<query>"`, `akm help agents`');
628
+ 'Next: `akm bundle add <source>`, `akm index`, `akm search "<query>"`, `akm hints`');
629
629
  }
630
630
  // ── Non-interactive / scripting entry points ─────────────────────────────────
631
631
  /**
@@ -19,13 +19,12 @@ import { parseSchedule } from "../../tasks/schedule.js";
19
19
  import { parseTaskSource } from "../../tasks/source/parse-task-source.js";
20
20
  import { prompt } from "../prompt.js";
21
21
  /**
22
- * A scheduled server-only nightly full sweep exists among the embedded
23
- * improve-schedule templates (folded in from the retired
24
- * `registerDefaultTasks`/`akm tasks init` path in 0.9, S6). This id is
25
- * preselected in the review multiselect on a detected server install — the
26
- * "battery heuristic" {@link detectServerDefault} preserved from that path.
22
+ * The nightly improve template (`core/improve`). This id is preselected in the
23
+ * review multiselect on a detected server install — the "battery heuristic"
24
+ * {@link detectServerDefault} preserved from the retired
25
+ * `registerDefaultTasks`/`akm tasks init` path (0.9, S6).
27
26
  */
28
- const SERVER_SUGGESTED_TASK_ID = "akm-improve-nightly";
27
+ const SERVER_SUGGESTED_TASK_ID = "improve";
29
28
  let scheduledTasksEnvOverrides;
30
29
  /** TEST-ONLY. Swap the CI/server-detection probes; pass undefined to restore. */
31
30
  export function _setScheduledTasksEnvForTests(fakes) {
@@ -66,7 +65,7 @@ export function detectServerDefault() {
66
65
  }
67
66
  catch {
68
67
  // If we cannot read power-supply info, prefer the safe server default on
69
- // Linux (the nightly sweep is low-impact and re-runnable).
68
+ // Linux (the nightly improve run is low-impact and re-runnable).
70
69
  return true;
71
70
  }
72
71
  }
@@ -200,11 +199,11 @@ export async function stepScheduledTasks(deps = DEFAULT_SCHEDULED_TASKS_DEPS, op
200
199
  p.log.info("Non-interactive setup leaves task files and scheduler state unchanged. Run `akm setup` interactively to review tasks.");
201
200
  return;
202
201
  }
203
- // ALL templates are offered, including ships-disabled ones (e.g. the
204
- // manual-recovery catchup task): an unselected template is still PREPARED,
205
- // so its YAML exists for `akm task run <id>` while its ref remains absent
206
- // from local scheduler activation. Filtering on `task.enabled`
207
- // here would make ships-disabled templates invisible and unpreparable.
202
+ // ALL templates are offered, including ships-disabled ones: an unselected
203
+ // template is still PREPARED, so its YAML exists for `akm task run <id>`
204
+ // while its ref remains absent from local scheduler activation. Filtering on
205
+ // `task.enabled` here would make ships-disabled templates invisible and
206
+ // unpreparable.
208
207
  const embedded = listEmbeddedTasks();
209
208
  if (embedded.length === 0)
210
209
  return;
@@ -228,7 +227,7 @@ export async function stepScheduledTasks(deps = DEFAULT_SCHEDULED_TASKS_DEPS, op
228
227
  byId.set(normaliseTaskIdForMatch(task.id), task);
229
228
  const preChecked = embedded.filter((task) => byId.get(task.id)?.enabled === true).map((task) => task.id);
230
229
  // Battery heuristic preserved from the retired `registerDefaultTasks` path
231
- // (S6): suggest the nightly full sweep on a detected server install, same
230
+ // (S6): suggest the nightly improve task on a detected server install, same
232
231
  // as every other embedded template, still gated behind the confirmation
233
232
  // below.
234
233
  if (!byId.has(SERVER_SUGGESTED_TASK_ID) &&
@@ -257,8 +257,11 @@ export function saveGitStash(name, message, writableOverride, options) {
257
257
  if (statusResult.error || statusResult.status !== 0) {
258
258
  throw new Error(`git status failed: ${statusResult.error?.message || statusResult.stderr?.trim() || "unknown error"}`);
259
259
  }
260
+ const hasRemote = readHasRemote(repoDir);
261
+ // `--no-push`, a read-only bundle and a bundle without a remote never touch the network.
262
+ const syncWithRemote = hasRemote && writable && allowPush;
260
263
  if (!statusResult.stdout.trim() && options?.paths === undefined) {
261
- return { committed: false, pushed: false, skipped: false, output: "nothing to commit, working tree clean" };
264
+ return nothingToCommit(repoDir, "nothing to commit, working tree clean", syncWithRemote);
262
265
  }
263
266
  // Scoped staging (#476 + the auto-sync incident): NEVER refuse akm's commit
264
267
  // because unrelated non-akm files exist in the working tree. When the stash
@@ -277,22 +280,14 @@ export function saveGitStash(name, message, writableOverride, options) {
277
280
  : "";
278
281
  const changedPaths = listGitChangedPaths(repoDir);
279
282
  const requestedPaths = normalizeExactPaths(repoDir, options?.paths ??
280
- (managedRelativeRoot && managedRelativeRoot !== ".." && !managedRelativeRoot.startsWith("../")
283
+ withoutIgnoredPaths(repoDir, managedRelativeRoot && managedRelativeRoot !== ".." && !managedRelativeRoot.startsWith("../")
281
284
  ? changedPaths.filter((changedPath) => changedPath === managedRelativeRoot || changedPath.startsWith(`${managedRelativeRoot}/`))
282
285
  : managedFallbackPaths(repoDir, changedPaths)));
283
286
  if (requestedPaths.length === 0) {
284
- return { committed: false, pushed: false, skipped: false, output: "nothing to commit" };
287
+ return nothingToCommit(repoDir, "nothing to commit", syncWithRemote);
285
288
  }
286
289
  assertNoIgnoredExactPaths(repoDir, requestedPaths);
287
290
  const baseHead = readOptionalHead(repoDir);
288
- const remoteResult = runGit(["-C", repoDir, "remote"]);
289
- if (remoteResult.status !== 0) {
290
- throw new Error(`git remote failed: ${remoteResult.stderr?.trim() || "unknown error"}`);
291
- }
292
- const hasRemote = remoteResult.stdout.trim().length > 0;
293
- // A branch that cannot be pushed still gets its commit: only the push is skipped, and the result says why.
294
- const upstream = hasRemote && writable && allowPush ? readActualUpstream(repoDir, baseHead) : undefined;
295
- const pushTarget = typeof upstream === "object" ? upstream : undefined;
296
291
  const exactCommit = createExactPathCommit(repoDir, {
297
292
  baseHead,
298
293
  commitMessage,
@@ -301,8 +296,17 @@ export function saveGitStash(name, message, writableOverride, options) {
301
296
  expectedSnapshots: options?.expectedSnapshots,
302
297
  });
303
298
  if (!exactCommit) {
304
- return { committed: false, pushed: false, skipped: false, output: "nothing to commit" };
305
- }
299
+ return nothingToCommit(repoDir, "nothing to commit", syncWithRemote);
300
+ }
301
+ // Bring in whatever another host pushed, then push. A branch that cannot be pushed still keeps its
302
+ // commit: only the push is skipped, and the result says why.
303
+ const integration = syncWithRemote ? integrateUpstream(repoDir) : undefined;
304
+ const upstream = integration && "blocked" in integration
305
+ ? integration.blocked
306
+ : syncWithRemote
307
+ ? readActualUpstream(repoDir, readOptionalHead(repoDir))
308
+ : undefined;
309
+ const pushTarget = typeof upstream === "object" ? upstream : undefined;
306
310
  if (!pushTarget) {
307
311
  return {
308
312
  committed: true,
@@ -319,7 +323,7 @@ export function saveGitStash(name, message, writableOverride, options) {
319
323
  "push",
320
324
  `--force-with-lease=${pushTarget.mergeRef}:${pushTarget.upstreamHead}`,
321
325
  pushTarget.remote,
322
- `${exactCommit}:${pushTarget.mergeRef}`,
326
+ `${readOptionalHead(repoDir) ?? exactCommit}:${pushTarget.mergeRef}`,
323
327
  ], { timeout: GIT_PUSH_TIMEOUT_MS });
324
328
  if (pushResult.status !== 0) {
325
329
  throw new GitStashPushError(`git push failed: ${pushResult.stderr?.trim() || "unknown error"}`, exactCommit);
@@ -332,11 +336,98 @@ export function saveGitStash(name, message, writableOverride, options) {
332
336
  commit: exactCommit,
333
337
  };
334
338
  }
339
+ function readHasRemote(repoDir) {
340
+ const remoteResult = runGit(["-C", repoDir, "remote"]);
341
+ if (remoteResult.status !== 0) {
342
+ throw new Error(`git remote failed: ${remoteResult.stderr?.trim() || "unknown error"}`);
343
+ }
344
+ return remoteResult.stdout.trim().length > 0;
345
+ }
346
+ /** Nothing to commit still brings in the upstream, so a host with no changes of its own keeps up. */
347
+ function nothingToCommit(repoDir, output, syncWithRemote) {
348
+ const integration = syncWithRemote ? integrateUpstream(repoDir) : undefined;
349
+ if (integration && "blocked" in integration) {
350
+ return { committed: false, pushed: false, skipped: false, reason: `not pushed: ${integration.blocked}`, output };
351
+ }
352
+ return { committed: false, pushed: false, skipped: false, output: integration?.integrated ?? output };
353
+ }
354
+ function readUpstreamConfig(repoDir) {
355
+ const branchRef = runGit(["-C", repoDir, "symbolic-ref", "--quiet", "HEAD"]);
356
+ if (branchRef.status !== 0 || !branchRef.stdout.trim())
357
+ return undefined;
358
+ const branch = branchRef.stdout.trim().replace(/^refs\/heads\//, "");
359
+ const remote = runGit(["-C", repoDir, "config", "--get", `branch.${branch}.remote`]);
360
+ const merge = runGit(["-C", repoDir, "config", "--get", `branch.${branch}.merge`]);
361
+ if (remote.status !== 0 || merge.status !== 0)
362
+ return undefined;
363
+ return { remote: remote.stdout.trim(), mergeRef: merge.stdout.trim() };
364
+ }
365
+ function isAncestor(repoDir, ancestor, descendant) {
366
+ return runGit(["-C", repoDir, "merge-base", "--is-ancestor", ancestor, descendant]).status === 0;
367
+ }
368
+ /**
369
+ * Fetch the branch's remote and bring its upstream into HEAD: nothing to do when HEAD already has it,
370
+ * a fast-forward when HEAD is behind, otherwise git's default merge. A merge that conflicts is aborted
371
+ * so the working tree and the local commits stay exactly as they were. `blocked` says why the branch
372
+ * could not be brought up to date (and therefore must not be pushed); `integrated` describes what moved.
373
+ */
374
+ function integrateUpstream(repoDir) {
375
+ const config = readUpstreamConfig(repoDir);
376
+ if (!config)
377
+ return {};
378
+ const fetch = runGit(["-C", repoDir, "fetch", config.remote], { timeout: GIT_PUSH_TIMEOUT_MS });
379
+ if (fetch.status !== 0) {
380
+ return { blocked: `could not fetch ${config.remote}: ${fetch.stderr?.trim() || "unknown error"}` };
381
+ }
382
+ const upstream = runGit(["-C", repoDir, "rev-parse", "--verify", "@{u}"]);
383
+ const head = readOptionalHead(repoDir);
384
+ if (upstream.status !== 0 || !head)
385
+ return {};
386
+ const upstreamHead = upstream.stdout.trim();
387
+ if (isAncestor(repoDir, upstreamHead, head))
388
+ return {};
389
+ const fastForward = isAncestor(repoDir, head, upstreamHead);
390
+ const merge = runGit([
391
+ "-C",
392
+ repoDir,
393
+ "-c",
394
+ "user.name=akm",
395
+ "-c",
396
+ "user.email=akm@local",
397
+ "merge",
398
+ ...(fastForward ? ["--ff-only"] : ["--no-edit", "-m", `akm sync: merge ${config.remote} upstream`]),
399
+ upstreamHead,
400
+ ]);
401
+ if (merge.status === 0)
402
+ return { integrated: fastForward ? "fast-forwarded to upstream" : "merged upstream" };
403
+ const conflicted = runGit(["-C", repoDir, "diff", "--name-only", "--diff-filter=U"])
404
+ .stdout.split("\n")
405
+ .filter(Boolean);
406
+ // Not every refusal leaves a merge in progress (e.g. local changes in the way); abort only if one is.
407
+ if (runGit(["-C", repoDir, "rev-parse", "--verify", "--quiet", "MERGE_HEAD"]).status === 0) {
408
+ runGit(["-C", repoDir, "merge", "--abort"]);
409
+ }
410
+ return {
411
+ blocked: conflicted.length > 0
412
+ ? `merging upstream conflicts in ${conflicted.join(", ")}; the merge was aborted and the local commit kept`
413
+ : `could not merge upstream (${merge.stderr?.trim() || merge.stdout?.trim() || "unknown error"}); the local commit was kept`,
414
+ };
415
+ }
335
416
  function managedFallbackPaths(repoDir, changedPaths) {
336
417
  const ownedDirs = akmAdapter.directoryList?.({ id: "akm", adapter: "akm", root: repoDir, writable: false }) ?? stashDirNames();
337
418
  const prefixes = [...ownedDirs, ".akm"];
338
419
  return changedPaths.filter((changedPath) => prefixes.some((prefix) => changedPath === prefix || changedPath.startsWith(`${prefix}/`)));
339
420
  }
421
+ /**
422
+ * Drop paths a `.gitignore` rule matches from the paths sync picks for itself. Ignoring means "do
423
+ * not publish", also for a file committed before the rule existed (`.akm/evals/runs/latest` in a
424
+ * stash whose eval runs are ignored): git refuses to stage such a file, which would fail the whole
425
+ * sync. Only an explicit `paths` request still reaches {@link assertNoIgnoredExactPaths}.
426
+ */
427
+ function withoutIgnoredPaths(repoDir, paths) {
428
+ const ignored = new Set(listIgnoredExactPaths(repoDir, paths, { includeTracked: true }));
429
+ return ignored.size === 0 ? paths : paths.filter((candidate) => !ignored.has(candidate));
430
+ }
340
431
  function normalizeExactPaths(repoDir, paths) {
341
432
  const normalized = [...new Set(paths.map((value) => value.replaceAll(path.sep, "/")))];
342
433
  for (const filePath of normalized) {
@@ -363,11 +454,17 @@ function runExactPathChunks(repoDir, command, paths, options = {}) {
363
454
  }
364
455
  return results;
365
456
  }
366
- export function listIgnoredExactPaths(repoDir, paths) {
457
+ /**
458
+ * The subset of `paths` a `.gitignore` rule matches. By default a tracked file never counts (git's
459
+ * own `check-ignore` skips it); `includeTracked` asks about the rule alone, as `git add` does.
460
+ */
461
+ export function listIgnoredExactPaths(repoDir, paths, options = {}) {
367
462
  if (paths.length === 0)
368
463
  return [];
369
464
  const input = `${paths.join("\0")}\0`;
370
- const result = runGit(["-C", repoDir, "check-ignore", "-z", "--stdin"], { input });
465
+ const result = runGit(["-C", repoDir, "check-ignore", "-z", "--stdin", ...(options.includeTracked ? ["--no-index"] : [])], {
466
+ input,
467
+ });
371
468
  if (result.status === 1)
372
469
  return [];
373
470
  if (result.status !== 0) {
@@ -5,11 +5,10 @@
5
5
  * `index.db` utility-score + retrieval-count repository (MemRL signals).
6
6
  *
7
7
  * Owns the raw SQL for `utility_scores` and the retrieval-frequency counting
8
- * over `usage_events`. The bounded-step EMA policy itself lives in
9
- * `indexer/feedback/utility-policy`; this repo only reads/writes.
8
+ * over `usage_events`. The utility formula itself lives in
9
+ * `recomputeUtilityScores` (`indexer/indexer.ts`); this repo only reads/writes.
10
10
  */
11
11
  import { conceptIdFromTypeName, parseRefInput } from "../../core/asset/resolve-ref.js";
12
- import { computeNextUtility } from "../../indexer/feedback/utility-policy.js";
13
12
  import { SQLITE_CHUNK_SIZE } from "./index-sql.js";
14
13
  /**
15
14
  * Get the utility score for an entry, or undefined if none exists.
@@ -242,35 +241,6 @@ function getSourceScopedRetrievalCounts(indexDb, stateDb, refs, options) {
242
241
  }
243
242
  return result;
244
243
  }
245
- /**
246
- * Apply accumulated feedback counts to the utility score of an entry, persisting
247
- * the result. The bounded-step EMA policy itself (MemRL, F-5 / #386,
248
- * arXiv:2601.03192) lives in {@link computeNextUtility} (feedback/utility-policy);
249
- * this function only reads the current utility, applies the policy, and writes
250
- * the new value.
251
- *
252
- * A new entry starts at 0.5 (neutral midpoint) before the EMA step is applied.
253
- * When there is no feedback (both counts zero) the score is left untouched — no
254
- * DB write. Returns a {@link FeedbackUtilityResult} so the caller can detect a
255
- * previously high-utility asset crossing below the review threshold and escalate.
256
- */
257
- export function applyFeedbackToUtilityScore(db, entryId, positiveCount, negativeCount) {
258
- const existing = getUtilityScore(db, entryId);
259
- const previousUtility = existing?.utility ?? 0.5;
260
- const result = computeNextUtility(previousUtility, positiveCount, negativeCount);
261
- if (positiveCount === 0 && negativeCount === 0) {
262
- return result;
263
- }
264
- const now = new Date().toISOString();
265
- db.prepare(`
266
- INSERT INTO utility_scores (entry_id, utility, show_count, search_count, select_rate, last_used_at, updated_at)
267
- VALUES (?, ?, 0, 0, 0, ?, ?)
268
- ON CONFLICT(entry_id) DO UPDATE SET
269
- utility = ?,
270
- updated_at = ?
271
- `).run(entryId, result.nextUtility, now, now, result.nextUtility, now);
272
- return result;
273
- }
274
244
  /**
275
245
  * Every indexed entry with its `utility_scores` row (when it has one), for
276
246
  * `akm metrics`. `utility_scores` is keyed by the unstable `entry_id`, so the
@@ -140,6 +140,10 @@ export function getNeighborsByEntryId(db, id, k, scope) {
140
140
  return [];
141
141
  return searchVec(db, new Float32Array(row.embedding.slice().buffer), k, scope);
142
142
  }
143
+ /** Whether the entry has a stored vector (what {@link getNeighborsByEntryId} reads). */
144
+ export function hasStoredEmbedding(db, id) {
145
+ return db.prepare("SELECT 1 FROM embeddings WHERE id = ?").get(id) != null;
146
+ }
143
147
  /**
144
148
  * Every entry that has no embedding row for `model` (any row when no model is
145
149
  * given), with its embedding input derived from the stored document
@@ -67,10 +67,6 @@ export function decodeTaskHistoryMetadata(input) {
67
67
  // A malformed `engine` (wrong type) is dropped like any other unrecognized
68
68
  // field rather than rejecting the whole row over a non-essential value.
69
69
  const engine = parsed.engine === null || typeof parsed.engine === "string" ? parsed.engine : undefined;
70
- if (parsed.targetVocab !== undefined && parsed.targetVocab !== 2) {
71
- warnOnce(`task-history-target-vocab:${String(parsed.targetVocab)}`, `task_history row has targetVocab ${String(parsed.targetVocab)}, newer than this akm's 2 — falling back to ` +
72
- "the legacy target_kind mapping rather than rejecting the row.");
73
- }
74
70
  validateDetail(detail);
75
71
  const cleanDetail = detail
76
72
  ? {
@@ -85,7 +81,6 @@ export function decodeTaskHistoryMetadata(input) {
85
81
  durationMs: parsed.durationMs,
86
82
  detail: cleanDetail,
87
83
  ...(engine !== undefined ? { engine } : {}),
88
- ...(parsed.targetVocab === 2 ? { targetVocab: 2 } : {}),
89
84
  };
90
85
  }
91
86
  /**
@@ -353,15 +353,6 @@ export class WorkflowRunsRepository {
353
353
  return db.prepare("SELECT * FROM workflow_runs WHERE id = ?").get(input.run.id);
354
354
  });
355
355
  }
356
- /**
357
- * Persist a run's resolved declared `outputs:` (migration 024, P3b §4.3).
358
- * Called from INSIDE `completeWorkflowStep`'s own write transaction — this
359
- * method opens none of its own, matching `updateStepCompletion` /
360
- * `updateRunState` immediately above.
361
- */
362
- setRunOutputs(runId, outputsJson) {
363
- this.db.prepare("UPDATE workflow_runs SET outputs_json = ? WHERE id = ?").run(outputsJson, runId);
364
- }
365
356
  /** Every child run published under `parentRunId`, oldest first. `[]` when none. */
366
357
  childRunsOf(parentRunId) {
367
358
  return this.db
@@ -432,13 +423,13 @@ export class WorkflowRunsRepository {
432
423
  const dispatchId = randomUUID();
433
424
  db.prepare(`INSERT INTO workflow_run_unit_attempts (
434
425
  run_id, unit_id, attempt, dispatch_id, step_id, node_id, phase,
435
- runner, engine, model, input_hash, status, worktree_path, started_at,
426
+ runner, engine, model, input_hash, status, started_at,
436
427
  claim_holder, claim_expires_at
437
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'running', ?, ?, ?, ?)`).run(input.runId, input.unitId, attemptNumber, dispatchId, input.stepId, input.nodeId, input.phase, input.runner, input.engine, input.model, input.inputHash, input.worktreePath ?? null, input.now, holder, input.now);
428
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'running', ?, ?, ?)`).run(input.runId, input.unitId, attemptNumber, dispatchId, input.stepId, input.nodeId, input.phase, input.runner, input.engine, input.model, input.inputHash, input.now, holder, input.now);
438
429
  db.prepare(`INSERT INTO workflow_run_units (
439
430
  run_id, unit_id, step_id, node_id, parent_unit_id, phase, runner, engine, model,
440
- status, input_hash, worktree_path, started_at, claim_holder, claim_expires_at, attempts
441
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'running', ?, ?, ?, ?, ?, ?)
431
+ status, input_hash, started_at, claim_holder, claim_expires_at, attempts
432
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'running', ?, ?, ?, ?, ?)
442
433
  ON CONFLICT(run_id, unit_id) DO UPDATE SET
443
434
  step_id = excluded.step_id,
444
435
  node_id = excluded.node_id,
@@ -449,7 +440,6 @@ export class WorkflowRunsRepository {
449
440
  model = excluded.model,
450
441
  status = 'running',
451
442
  input_hash = excluded.input_hash,
452
- worktree_path = excluded.worktree_path,
453
443
  started_at = excluded.started_at,
454
444
  claim_holder = excluded.claim_holder,
455
445
  claim_expires_at = excluded.claim_expires_at,
@@ -458,7 +448,7 @@ export class WorkflowRunsRepository {
458
448
  failure_reason = NULL,
459
449
  session_id = NULL,
460
450
  finished_at = NULL,
461
- attempts = excluded.attempts`).run(input.runId, input.unitId, input.stepId, input.nodeId, input.parentUnitId ?? null, input.phase, input.runner, input.engine, input.model, input.inputHash, input.worktreePath ?? null, input.now, holder, input.now, attemptNumber);
451
+ attempts = excluded.attempts`).run(input.runId, input.unitId, input.stepId, input.nodeId, input.parentUnitId ?? null, input.phase, input.runner, input.engine, input.model, input.inputHash, input.now, holder, input.now, attemptNumber);
462
452
  insertEventStrict(db, {
463
453
  eventType: "workflow_unit_started",
464
454
  ts: input.now,
@@ -25,7 +25,8 @@ import { getDirname } from "../runtime.js";
25
25
  import { parseTaskSource } from "./source/parse-task-source.js";
26
26
  /** Directory holding the bundled task template categories. */
27
27
  const TASKS_ASSETS_DIR = path.join(getDirname(import.meta.url), "../assets/tasks");
28
- const DEFAULT_DISABLED_TASKS = new Set(["improve/akm-improve-catchup"]);
28
+ /** Templates setup offers but ships disabled (`category/id`). None ship disabled since catchup was removed (#1130). */
29
+ const DEFAULT_DISABLED_TASKS = new Set();
29
30
  /**
30
31
  * Enumerate the embedded task templates from every category subdirectory of
31
32
  * the bundled assets directory. Sorted by category then id for deterministic
@@ -3,6 +3,7 @@
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
4
  import { dispatchPreparedCommandInvocation } from "../../commands/command/command-execution.js";
5
5
  import { finishAttempt } from "./attempt-lifecycle.js";
6
+ import { DEFAULT_SCHEDULED_TASK_TIMEOUT_MS } from "./run-workflow-task.js";
6
7
  import { appendHistory } from "./task-history.js";
7
8
  import { persistRunLog, scrubTaskOutput, streamLines } from "./task-log.js";
8
9
  export async function runPreparedCommandTask(input) {
@@ -15,7 +16,14 @@ export async function runPreparedCommandTask(input) {
15
16
  // into the dispatched engine's child env and the recorded usage events.
16
17
  eventSource: provenance.eventSource,
17
18
  };
18
- const result = await dispatchPreparedCommandInvocation(task.invocation, dispatchOptions);
19
+ // Unset anywhere (task `timeout:`, command asset, engine) → the unattended
20
+ // default; an explicit value or `null` (opt-out) already sits on the request.
21
+ const { request, runner } = task.invocation;
22
+ const unset = !Object.hasOwn(request.runtime, "timeoutMs") && runner.timeoutMs === undefined;
23
+ const invocation = unset
24
+ ? { ...task.invocation, runner: { ...runner, timeoutMs: DEFAULT_SCHEDULED_TASK_TIMEOUT_MS } }
25
+ : task.invocation;
26
+ const result = await dispatchPreparedCommandInvocation(invocation, dispatchOptions);
19
27
  const engineName = result.engine;
20
28
  const finishedAt = finishAttempt(startedAt, now());
21
29
  const log = renderPromptLog({ task, engineName, result, notices: result.notices, warnings: result.warnings });
@@ -50,11 +50,12 @@ import { persistRunLog, scrubTaskOutput } from "./task-log.js";
50
50
  */
51
51
  export const DEFAULT_WORKFLOW_TASK_TIMEOUT_MS = 6 * 60 * 60 * 1000;
52
52
  /**
53
- * The same unattended default for command and prompt tasks.
53
+ * The same unattended default for the other scheduled task kinds: `uses: commands/…`
54
+ * command tasks and native shell/script tasks.
54
55
  *
55
56
  * The reasoning above is about SCHEDULED runs, not about workflows: nobody is
56
- * watching, and one wedged run silently stops the schedule. Command tasks
57
- * defaulted to `null` (no kill timer) and prompt tasks inherited
57
+ * watching, and one wedged run silently stops the schedule. Native shell/script
58
+ * tasks defaulted to `null` (no kill timer) and command tasks inherited
58
59
  * DEFAULT_AGENT_TIMEOUT_MS, also null — so a hung `curl`, a prompting agent
59
60
  * waiting on stdin, or a stuck engine wedged the task forever while the
60
61
  * workflow arm was protected. Same value, same opt-out: an explicit
@@ -49,8 +49,6 @@ export function appendHistory(result, historyReserved = false) {
49
49
  metadataVersion: 2,
50
50
  durationMs: result.durationMs,
51
51
  detail: result.detail ?? null,
52
- // D8 (spec §5.3): every NEW row carries the vocabulary marker.
53
- targetVocab: 2,
54
52
  ...(result.target.kind === "command" ? { engine: result.target.engine } : {}),
55
53
  }),
56
54
  };
@@ -20,7 +20,7 @@ import path from "node:path";
20
20
  import { loadConfig } from "../../core/config/config.js";
21
21
  import { rethrowIfTestIsolationError } from "../../core/errors.js";
22
22
  import { buildTaskRunId, insertTaskLogLines, openLogsDatabase, } from "../../core/logs-db.js";
23
- import { getTaskLogDir } from "../../core/paths.js";
23
+ import { getRunLogDir, getTaskLogDir } from "../../core/paths.js";
24
24
  import { redactCredentialPatterns, redactSensitiveText } from "../../core/redaction.js";
25
25
  import { collectTaskLogSensitiveValues } from "../log-redaction.js";
26
26
  function taskLogPath(logDir, taskId, startedAtIso) {
@@ -95,6 +95,43 @@ export function purgeOldTaskLogFiles(logDir, retentionDays = 90) {
95
95
  }
96
96
  return deleted;
97
97
  }
98
+ /**
99
+ * Delete the flat per-run `akm improve` and `akm index` log files
100
+ * (`$CACHE/logs/{improve,index}/<ISO>.log`) older than `retentionDays`. Same
101
+ * best-effort contract as {@link purgeOldTaskLogFiles}; `.log` files directly
102
+ * inside each directory only.
103
+ */
104
+ export function purgeOldRunLogFiles(retentionDays = 90) {
105
+ if (!Number.isFinite(retentionDays) || retentionDays <= 0)
106
+ return 0;
107
+ const cutoffMs = Date.now() - retentionDays * 86_400_000;
108
+ let deleted = 0;
109
+ for (const dir of [getRunLogDir("improve"), getRunLogDir("index")]) {
110
+ let entries;
111
+ try {
112
+ entries = fs.readdirSync(dir, { withFileTypes: true });
113
+ }
114
+ catch (error) {
115
+ rethrowIfTestIsolationError(error);
116
+ continue;
117
+ }
118
+ for (const entry of entries) {
119
+ if (!entry.isFile() || !entry.name.endsWith(".log"))
120
+ continue;
121
+ const filePath = path.join(dir, entry.name);
122
+ try {
123
+ if (fs.statSync(filePath).mtimeMs < cutoffMs) {
124
+ fs.unlinkSync(filePath);
125
+ deleted++;
126
+ }
127
+ }
128
+ catch (error) {
129
+ rethrowIfTestIsolationError(error);
130
+ }
131
+ }
132
+ }
133
+ return deleted;
134
+ }
98
135
  /**
99
136
  * Redact logs.db rows against the SAME contiguous text the file sink sees.
100
137
  *
@@ -0,0 +1,39 @@
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
+ * The two per-source schedule gates `akm task sync` runs before it installs a
6
+ * schedule, kept in a leaf module so `akm lint` can run them without importing
7
+ * the scheduler's task-preparation graph (which reaches the adapter registry
8
+ * that lint itself is part of).
9
+ */
10
+ import { UsageError } from "../core/errors.js";
11
+ import { applyInputDefaults, validateInputs } from "../execution/input-contract.js";
12
+ import { parseSchedule } from "./schedule.js";
13
+ /**
14
+ * Validate every v4 `schedule:` entry's `inputs` against the task's own
15
+ * declared contract with defaults applied — the exact set of values a
16
+ * compiled invocation delivers — so a violation is reported at sync rather
17
+ * than when the scheduler fires. Shared with `akm lint`.
18
+ */
19
+ export function assertTaskScheduleInputsSatisfyContract(v4, refLabel) {
20
+ const contract = v4.inputs ?? {};
21
+ for (const scheduleEntry of v4.schedule) {
22
+ const defaultedInputs = applyInputDefaults(contract, { ...scheduleEntry.inputs });
23
+ const errors = validateInputs(contract, defaultedInputs);
24
+ if (errors.length > 0) {
25
+ throw new UsageError(`Task ${JSON.stringify(refLabel)} schedule[${scheduleEntry.ordinal}].inputs does not satisfy ` +
26
+ `its declared inputs once defaults are applied: ${errors.join("; ")}`, "TASK_SOURCE_INVALID");
27
+ }
28
+ }
29
+ }
30
+ /**
31
+ * Validate every v4 `schedule:` entry's `cron` against the active backend's
32
+ * dialect: cron is the most permissive of the three, so a task authored on
33
+ * Linux can carry an expression launchd/schtasks cannot translate. Shared
34
+ * with `akm lint`.
35
+ */
36
+ export function assertTaskScheduleCronValid(v4, backend) {
37
+ for (const scheduleEntry of v4.schedule)
38
+ parseSchedule(scheduleEntry.cron, backend);
39
+ }
@@ -62,22 +62,6 @@ function schedulerInputFlagValueText(value) {
62
62
  return String(value);
63
63
  return canonicalInputJson(value);
64
64
  }
65
- export function compileWorkflowSchedulerBindings(input) {
66
- const ref = assertQualifiedRef(input.qualifiedRef, "workflow");
67
- return Object.freeze(input.schedules.map((schedule) => {
68
- const bindingId = digestBindingId("workflow", ref, schedule.ordinal);
69
- return freezeBinding({
70
- id: bindingId,
71
- nativeId: bindingId,
72
- logicalSource: { kind: "workflow", ref },
73
- cron: schedule.cron,
74
- source: schedule.source,
75
- ordinal: schedule.ordinal,
76
- enabled: true,
77
- invocation: ["workflow", "run", ref],
78
- });
79
- }));
80
- }
81
65
  /**
82
66
  * Map a logical binding id to the flat portable token persisted by native
83
67
  * schedulers. Flat ids are byte-for-byte stable; only nested standalone