akm-cli 0.8.0-rc1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (295) hide show
  1. package/{.github/CHANGELOG.md → CHANGELOG.md} +191 -3
  2. package/README.md +22 -6
  3. package/SECURITY.md +93 -0
  4. package/dist/cli/config-migrate.js +144 -0
  5. package/dist/cli/config-validate.js +39 -0
  6. package/dist/cli/confirm.js +73 -0
  7. package/dist/cli/parse-args.js +93 -3
  8. package/dist/cli/shared.js +129 -0
  9. package/dist/cli.js +2162 -1258
  10. package/dist/commands/add-cli.js +279 -0
  11. package/dist/commands/agent-dispatch.js +20 -12
  12. package/dist/commands/agent-support.js +11 -5
  13. package/dist/commands/completions.js +3 -0
  14. package/dist/commands/config-cli.js +129 -517
  15. package/dist/commands/consolidate.js +1533 -144
  16. package/dist/commands/curate.js +44 -3
  17. package/dist/commands/db-cli.js +23 -0
  18. package/dist/commands/distill-promotion-policy.js +5 -3
  19. package/dist/commands/distill.js +906 -100
  20. package/dist/commands/env.js +213 -0
  21. package/dist/commands/eval-cases.js +3 -0
  22. package/dist/commands/events.js +3 -0
  23. package/dist/commands/extract-cli.js +127 -0
  24. package/dist/commands/extract-prompt.js +204 -0
  25. package/dist/commands/extract.js +477 -0
  26. package/dist/commands/feedback-cli.js +331 -0
  27. package/dist/commands/graph.js +260 -5
  28. package/dist/commands/health.js +977 -51
  29. package/dist/commands/help/help-accept.md +6 -3
  30. package/dist/commands/help/help-improve.md +36 -8
  31. package/dist/commands/help/help-proposals.md +7 -4
  32. package/dist/commands/help/help-reject.md +5 -2
  33. package/dist/commands/history.js +51 -16
  34. package/dist/commands/improve-auto-accept.js +97 -0
  35. package/dist/commands/improve-cli.js +236 -0
  36. package/dist/commands/improve-profiles.js +184 -0
  37. package/dist/commands/improve-result-file.js +167 -0
  38. package/dist/commands/improve.js +1725 -332
  39. package/dist/commands/info.js +3 -0
  40. package/dist/commands/init.js +49 -1
  41. package/dist/commands/installed-stashes.js +6 -23
  42. package/dist/commands/knowledge.js +3 -0
  43. package/dist/commands/lint/agent-linter.js +3 -0
  44. package/dist/commands/lint/base-linter.js +233 -5
  45. package/dist/commands/lint/command-linter.js +3 -0
  46. package/dist/commands/lint/default-linter.js +3 -0
  47. package/dist/commands/lint/env-key-rules.js +154 -0
  48. package/dist/commands/lint/index.js +92 -3
  49. package/dist/commands/lint/knowledge-linter.js +3 -0
  50. package/dist/commands/lint/markdown-insertion.js +343 -0
  51. package/dist/commands/lint/memory-linter.js +3 -0
  52. package/dist/commands/lint/registry.js +3 -0
  53. package/dist/commands/lint/skill-linter.js +3 -0
  54. package/dist/commands/lint/task-linter.js +15 -12
  55. package/dist/commands/lint/types.js +3 -0
  56. package/dist/commands/lint/workflow-linter.js +3 -0
  57. package/dist/commands/lint.js +3 -0
  58. package/dist/commands/migration-help.js +5 -2
  59. package/dist/commands/proposal-drain-policies.js +128 -0
  60. package/dist/commands/proposal-drain.js +477 -0
  61. package/dist/commands/proposal.js +60 -6
  62. package/dist/commands/propose.js +24 -19
  63. package/dist/commands/reflect.js +1004 -94
  64. package/dist/commands/registry-cli.js +150 -0
  65. package/dist/commands/registry-search.js +3 -0
  66. package/dist/commands/remember-cli.js +257 -0
  67. package/dist/commands/remember.js +15 -6
  68. package/dist/commands/schema-repair.js +88 -15
  69. package/dist/commands/search.js +99 -14
  70. package/dist/commands/secret.js +173 -0
  71. package/dist/commands/self-update.js +3 -0
  72. package/dist/commands/show.js +32 -13
  73. package/dist/commands/source-add.js +7 -35
  74. package/dist/commands/source-clone.js +3 -0
  75. package/dist/commands/source-manage.js +3 -0
  76. package/dist/commands/tasks.js +161 -95
  77. package/dist/commands/url-checker.js +3 -0
  78. package/dist/core/action-contributors.js +3 -0
  79. package/dist/core/asset-ref.js +17 -2
  80. package/dist/core/asset-registry.js +9 -2
  81. package/dist/core/asset-serialize.js +88 -0
  82. package/dist/core/asset-spec.js +61 -5
  83. package/dist/core/common.js +93 -5
  84. package/dist/core/concurrent.js +3 -0
  85. package/dist/core/config-io.js +347 -0
  86. package/dist/core/config-migration.js +622 -0
  87. package/dist/core/config-schema.js +558 -0
  88. package/dist/core/config-sources.js +108 -0
  89. package/dist/core/config-types.js +4 -0
  90. package/dist/core/config-walker.js +337 -0
  91. package/dist/core/config.js +366 -1077
  92. package/dist/core/errors.js +42 -20
  93. package/dist/core/events.js +31 -25
  94. package/dist/core/file-lock.js +104 -0
  95. package/dist/core/frontmatter.js +75 -10
  96. package/dist/core/lesson-lint.js +3 -0
  97. package/dist/core/markdown.js +3 -0
  98. package/dist/core/memory-belief.js +62 -0
  99. package/dist/core/memory-contradiction-detect.js +274 -0
  100. package/dist/core/memory-improve.js +142 -14
  101. package/dist/core/parse.js +3 -0
  102. package/dist/core/paths.js +218 -50
  103. package/dist/core/proposal-quality-validators.js +380 -0
  104. package/dist/core/proposal-validators.js +11 -3
  105. package/dist/core/proposals.js +464 -5
  106. package/dist/core/state-db.js +349 -56
  107. package/dist/core/text-truncation.js +107 -0
  108. package/dist/core/time.js +3 -0
  109. package/dist/core/tty.js +59 -0
  110. package/dist/core/warn.js +7 -2
  111. package/dist/core/write-source.js +12 -0
  112. package/dist/indexer/db-backup.js +391 -0
  113. package/dist/indexer/db-search.js +136 -28
  114. package/dist/indexer/db.js +662 -166
  115. package/dist/indexer/ensure-index.js +3 -0
  116. package/dist/indexer/file-context.js +3 -0
  117. package/dist/indexer/graph-boost.js +162 -40
  118. package/dist/indexer/graph-db.js +241 -51
  119. package/dist/indexer/graph-dedup.js +3 -7
  120. package/dist/indexer/graph-extraction.js +242 -149
  121. package/dist/indexer/index-context.js +3 -9
  122. package/dist/indexer/indexer.js +84 -14
  123. package/dist/indexer/llm-cache.js +24 -19
  124. package/dist/indexer/manifest.js +3 -0
  125. package/dist/indexer/matchers.js +184 -11
  126. package/dist/indexer/memory-inference.js +94 -50
  127. package/dist/indexer/metadata-contributors.js +3 -0
  128. package/dist/indexer/metadata.js +114 -48
  129. package/dist/indexer/path-resolver.js +3 -0
  130. package/dist/indexer/project-context.js +192 -0
  131. package/dist/indexer/ranking-contributors.js +134 -7
  132. package/dist/indexer/ranking.js +8 -1
  133. package/dist/indexer/search-fields.js +5 -9
  134. package/dist/indexer/search-hit-enrichers.js +91 -2
  135. package/dist/indexer/search-source.js +20 -1
  136. package/dist/indexer/semantic-status.js +4 -1
  137. package/dist/indexer/staleness-detect.js +447 -0
  138. package/dist/indexer/usage-events.js +12 -9
  139. package/dist/indexer/walker.js +3 -0
  140. package/dist/integrations/agent/builders.js +135 -0
  141. package/dist/integrations/agent/config.js +121 -401
  142. package/dist/integrations/agent/detect.js +3 -0
  143. package/dist/integrations/agent/index.js +6 -14
  144. package/dist/integrations/agent/model-aliases.js +55 -0
  145. package/dist/integrations/agent/profiles.js +3 -0
  146. package/dist/integrations/agent/prompts.js +137 -8
  147. package/dist/integrations/agent/runner.js +208 -0
  148. package/dist/integrations/agent/sdk-runner.js +8 -2
  149. package/dist/integrations/agent/spawn.js +54 -14
  150. package/dist/integrations/github.js +3 -0
  151. package/dist/integrations/lockfile.js +22 -51
  152. package/dist/integrations/session-logs/index.js +4 -0
  153. package/dist/integrations/session-logs/inline-refs.js +35 -0
  154. package/dist/integrations/session-logs/pre-filter.js +152 -0
  155. package/dist/integrations/session-logs/providers/claude-code.js +226 -0
  156. package/dist/integrations/session-logs/providers/opencode.js +231 -25
  157. package/dist/integrations/session-logs/types.js +3 -0
  158. package/dist/llm/call-ai.js +14 -26
  159. package/dist/llm/client.js +16 -2
  160. package/dist/llm/embedder.js +20 -29
  161. package/dist/llm/embedders/cache.js +3 -7
  162. package/dist/llm/embedders/local.js +42 -1
  163. package/dist/llm/embedders/remote.js +20 -8
  164. package/dist/llm/embedders/types.js +3 -7
  165. package/dist/llm/feature-gate.js +92 -56
  166. package/dist/llm/graph-extract.js +401 -30
  167. package/dist/llm/index-passes.js +44 -29
  168. package/dist/llm/memory-infer.js +30 -2
  169. package/dist/llm/metadata-enhance.js +3 -7
  170. package/dist/llm/prompts/extract-session.md +80 -0
  171. package/dist/llm/prompts/graph-extract-user-prompt.md +24 -1
  172. package/dist/output/cli-hints-full.md +60 -32
  173. package/dist/output/cli-hints-short.md +10 -7
  174. package/dist/output/cli-hints.js +5 -2
  175. package/dist/output/context.js +60 -8
  176. package/dist/output/renderers.js +170 -194
  177. package/dist/output/shapes/curate.js +56 -0
  178. package/dist/output/shapes/distill.js +10 -0
  179. package/dist/output/shapes/env-list.js +19 -0
  180. package/dist/output/shapes/events.js +11 -0
  181. package/dist/output/shapes/helpers.js +424 -0
  182. package/dist/output/shapes/history.js +7 -0
  183. package/dist/output/shapes/passthrough.js +105 -0
  184. package/dist/output/shapes/proposal-accept.js +7 -0
  185. package/dist/output/shapes/proposal-diff.js +7 -0
  186. package/dist/output/shapes/proposal-list.js +7 -0
  187. package/dist/output/shapes/proposal-producer.js +11 -0
  188. package/dist/output/shapes/proposal-reject.js +7 -0
  189. package/dist/output/shapes/proposal-show.js +7 -0
  190. package/dist/output/shapes/registry-search.js +6 -0
  191. package/dist/output/shapes/registry.js +30 -0
  192. package/dist/output/shapes/search.js +6 -0
  193. package/dist/output/shapes/secret-list.js +19 -0
  194. package/dist/output/shapes/show.js +6 -0
  195. package/dist/output/shapes/vault-list.js +19 -0
  196. package/dist/output/shapes.js +51 -549
  197. package/dist/output/text/add.js +6 -0
  198. package/dist/output/text/clone.js +6 -0
  199. package/dist/output/text/config.js +6 -0
  200. package/dist/output/text/curate.js +6 -0
  201. package/dist/output/text/distill.js +7 -0
  202. package/dist/output/text/enable-disable.js +7 -0
  203. package/dist/output/text/events.js +10 -0
  204. package/dist/output/text/feedback.js +6 -0
  205. package/dist/output/text/helpers.js +1059 -0
  206. package/dist/output/text/history.js +7 -0
  207. package/dist/output/text/import.js +6 -0
  208. package/dist/output/text/index.js +6 -0
  209. package/dist/output/text/info.js +6 -0
  210. package/dist/output/text/init.js +6 -0
  211. package/dist/output/text/list.js +6 -0
  212. package/dist/output/text/proposal-producer.js +8 -0
  213. package/dist/output/text/proposal.js +12 -0
  214. package/dist/output/text/registry-commands.js +11 -0
  215. package/dist/output/text/registry.js +30 -0
  216. package/dist/output/text/remember.js +6 -0
  217. package/dist/output/text/remove.js +6 -0
  218. package/dist/output/text/save.js +6 -0
  219. package/dist/output/text/search.js +6 -0
  220. package/dist/output/text/show.js +6 -0
  221. package/dist/output/text/update.js +6 -0
  222. package/dist/output/text/upgrade.js +6 -0
  223. package/dist/output/text/vault.js +16 -0
  224. package/dist/output/text/wiki.js +15 -0
  225. package/dist/output/text/workflow.js +14 -0
  226. package/dist/output/text.js +44 -1329
  227. package/dist/registry/build-index.js +3 -0
  228. package/dist/registry/create-provider-registry.js +3 -0
  229. package/dist/registry/factory.js +4 -1
  230. package/dist/registry/origin-resolve.js +3 -0
  231. package/dist/registry/providers/index.js +3 -0
  232. package/dist/registry/providers/skills-sh.js +11 -2
  233. package/dist/registry/providers/static-index.js +10 -1
  234. package/dist/registry/providers/types.js +3 -24
  235. package/dist/registry/resolve.js +11 -16
  236. package/dist/registry/types.js +3 -0
  237. package/dist/scripts/migrate-storage.js +17767 -0
  238. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +9031 -0
  239. package/dist/scripts/migrations/v16-to-v17.js +141 -0
  240. package/dist/setup/detect.js +3 -0
  241. package/dist/setup/ripgrep-install.js +3 -0
  242. package/dist/setup/ripgrep-resolve.js +3 -0
  243. package/dist/setup/setup.js +306 -67
  244. package/dist/setup/steps.js +3 -15
  245. package/dist/sources/include.js +3 -0
  246. package/dist/sources/provider-factory.js +3 -11
  247. package/dist/sources/provider.js +3 -20
  248. package/dist/sources/providers/filesystem.js +19 -23
  249. package/dist/sources/providers/git.js +171 -21
  250. package/dist/sources/providers/index.js +3 -0
  251. package/dist/sources/providers/install-types.js +3 -13
  252. package/dist/sources/providers/npm.js +3 -4
  253. package/dist/sources/providers/provider-utils.js +3 -0
  254. package/dist/sources/providers/sync-from-ref.js +3 -11
  255. package/dist/sources/providers/tar-utils.js +3 -0
  256. package/dist/sources/providers/website.js +18 -22
  257. package/dist/sources/resolve.js +3 -0
  258. package/dist/sources/types.js +3 -0
  259. package/dist/sources/website-ingest.js +3 -0
  260. package/dist/tasks/backends/cron.js +3 -0
  261. package/dist/tasks/backends/exec-utils.js +3 -0
  262. package/dist/tasks/backends/index.js +3 -11
  263. package/dist/tasks/backends/launchd.js +3 -0
  264. package/dist/tasks/backends/schtasks.js +3 -0
  265. package/dist/tasks/parser.js +51 -38
  266. package/dist/tasks/resolveAkmBin.js +3 -0
  267. package/dist/tasks/runner.js +35 -9
  268. package/dist/tasks/schedule.js +20 -1
  269. package/dist/tasks/schema.js +5 -3
  270. package/dist/tasks/validator.js +6 -3
  271. package/dist/version.js +3 -0
  272. package/dist/wiki/wiki-templates.js +3 -0
  273. package/dist/wiki/wiki.js +3 -0
  274. package/dist/workflows/authoring.js +3 -0
  275. package/dist/workflows/cli.js +3 -0
  276. package/dist/workflows/db.js +140 -10
  277. package/dist/workflows/document-cache.js +3 -10
  278. package/dist/workflows/parser.js +3 -0
  279. package/dist/workflows/renderer.js +3 -0
  280. package/dist/workflows/runs.js +18 -1
  281. package/dist/workflows/schema.js +3 -0
  282. package/dist/workflows/scope-key.js +3 -0
  283. package/dist/workflows/validator.js +5 -9
  284. package/docs/README.md +7 -2
  285. package/docs/data-and-telemetry.md +225 -0
  286. package/docs/migration/release-notes/0.7.5.md +2 -2
  287. package/docs/migration/release-notes/0.8.0.md +57 -5
  288. package/docs/migration/v0.7-to-v0.8.md +1378 -0
  289. package/package.json +28 -11
  290. package/.github/LICENSE +0 -374
  291. package/dist/commands/install-audit.js +0 -385
  292. package/dist/commands/vault.js +0 -307
  293. package/dist/indexer/match-contributors.js +0 -141
  294. package/dist/integrations/agent/pipeline.js +0 -39
  295. package/dist/integrations/agent/runners.js +0 -31
@@ -1,28 +1,32 @@
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/.
1
4
  /**
2
- * Parse a task markdown file (frontmatter + body) into a {@link TaskDocument}.
5
+ * Parse a task YAML file into a {@link TaskDocument}.
3
6
  *
4
- * The on-disk shape is:
7
+ * The on-disk shape is a pure YAML file at `<stash>/tasks/<id>.yml`:
5
8
  *
6
- * ```markdown
7
- * ---
9
+ * ```yaml
8
10
  * schedule: "0 9 * * *"
9
11
  * # one of:
10
12
  * workflow: workflow:daily-backup
11
13
  * params:
12
14
  * region: us-east-1
13
15
  * # ...or:
14
- * prompt: inline # body is the prompt
15
- * profile: opencode # optional
16
- * # ...or:
17
16
  * prompt: agent:my-agent # asset ref
18
17
  * # ...or:
19
18
  * prompt: ./prompts/my-prompt.md # relative file path
19
+ * # ...or:
20
+ * prompt: | # inline multi-line prompt (block scalar)
21
+ * Do the thing.
22
+ * And the other thing.
23
+ * # ...or:
24
+ * command: akm improve --auto-accept=90 --limit 25
20
25
  * enabled: true # default true
26
+ * name: Daily backup
21
27
  * description: …
28
+ * when_to_use: …
22
29
  * tags: [scheduled, backup]
23
- * ---
24
- *
25
- * # Task: Daily backup (optional notes; for prompt:inline this is the prompt)
26
30
  * ```
27
31
  *
28
32
  * Validation lives in {@link validateTaskDocument}. The parser only enforces
@@ -30,29 +34,42 @@
30
34
  * checked separately so callers can choose how strictly to surface errors.
31
35
  */
32
36
  import path from "node:path";
37
+ import { parse as parseYaml } from "yaml";
33
38
  import { UsageError } from "../core/errors";
34
- import { parseFrontmatter } from "../core/frontmatter";
35
39
  import { TASK_SCHEMA_VERSION } from "./schema";
36
40
  export function parseTaskDocument(input) {
37
- const { markdown, filePath, id } = input;
38
- const fm = parseFrontmatter(markdown);
39
- const data = fm.data;
41
+ const { yaml, filePath, id } = input;
42
+ let data;
43
+ try {
44
+ const parsed = parseYaml(yaml);
45
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
46
+ throw new UsageError(`Task "${id}" YAML must be a mapping (key: value pairs). File: ${filePath}`, "INVALID_FLAG_VALUE");
47
+ }
48
+ data = parsed;
49
+ }
50
+ catch (err) {
51
+ if (err instanceof UsageError)
52
+ throw err;
53
+ throw new UsageError(`Task "${id}" has invalid YAML: ${err instanceof Error ? err.message : String(err)}. File: ${filePath}`, "INVALID_FLAG_VALUE");
54
+ }
40
55
  const schedule = readString(data.schedule, "schedule", filePath);
41
56
  if (!schedule) {
42
- throw new UsageError(`Task "${id}" is missing a schedule (frontmatter key "schedule"). File: ${filePath}`, "MISSING_REQUIRED_ARGUMENT");
57
+ throw new UsageError(`Task "${id}" is missing a schedule (YAML key "schedule"). File: ${filePath}`, "MISSING_REQUIRED_ARGUMENT");
43
58
  }
44
59
  const enabled = data.enabled === undefined ? true : data.enabled === true;
60
+ const name = readString(data.name, "name", filePath);
45
61
  const description = readString(data.description, "description", filePath);
62
+ const when_to_use = readString(data.when_to_use, "when_to_use", filePath);
46
63
  const tags = readStringArray(data.tags);
47
- const hasWorkflow = "workflow" in data && data.workflow !== "";
48
- const hasPrompt = "prompt" in data && data.prompt !== "";
49
- const hasCommand = "command" in data && data.command !== "" && data.command !== null && data.command !== undefined;
64
+ const hasWorkflow = "workflow" in data && data.workflow !== "" && data.workflow != null;
65
+ const hasPrompt = "prompt" in data && data.prompt !== "" && data.prompt != null;
66
+ const hasCommand = "command" in data && data.command !== "" && data.command != null;
50
67
  const targetCount = [hasWorkflow, hasPrompt, hasCommand].filter(Boolean).length;
51
68
  if (targetCount > 1) {
52
69
  throw new UsageError(`Task "${id}" sets more than one of \`workflow\`, \`prompt\`, \`command\`; pick exactly one. File: ${filePath}`, "INVALID_FLAG_VALUE");
53
70
  }
54
71
  if (targetCount === 0) {
55
- throw new UsageError(`Task "${id}" must set one of \`workflow\`, \`prompt\`, or \`command\` in frontmatter. File: ${filePath}`, "MISSING_REQUIRED_ARGUMENT");
72
+ throw new UsageError(`Task "${id}" must set one of \`workflow\`, \`prompt\`, or \`command\`. File: ${filePath}`, "MISSING_REQUIRED_ARGUMENT");
56
73
  }
57
74
  let target;
58
75
  if (hasWorkflow) {
@@ -78,7 +95,7 @@ export function parseTaskDocument(input) {
78
95
  const profile = readString(data.profile, "profile", filePath);
79
96
  target = {
80
97
  kind: "prompt",
81
- source: resolvePromptSource(promptRaw, fm.content, filePath, id),
98
+ source: resolvePromptSource(promptRaw, filePath, id),
82
99
  profile: profile && profile.length > 0 ? profile : undefined,
83
100
  };
84
101
  }
@@ -101,30 +118,24 @@ export function parseTaskDocument(input) {
101
118
  schedule,
102
119
  enabled,
103
120
  target,
121
+ name: name && name.length > 0 ? name : undefined,
104
122
  description: description && description.length > 0 ? description : undefined,
123
+ when_to_use: when_to_use && when_to_use.length > 0 ? when_to_use : undefined,
105
124
  tags: tags && tags.length > 0 ? tags : undefined,
106
125
  source: { path: filePath },
107
126
  timeoutMs,
108
127
  };
109
128
  }
110
129
  /**
111
- * Split `prompt:` frontmatter into one of {@link TaskPromptSource} variants.
130
+ * Resolve a `prompt:` value into a {@link TaskPromptSource} variant.
112
131
  *
113
- * • "inline" body is the prompt
114
- * • "<type>:<name>" (asset ref) asset
115
- * • "./foo.md", "../foo.md", "/abs" → file
116
- * • anything else treated as inline prompt text
117
- * (the value itself is the prompt)
132
+ * • "<type>:<name>" (asset ref) → asset
133
+ * • "./foo.md", "../foo.md", "/abs" file
134
+ * • "C:\\abs" (Windows absolute) → file
135
+ * • anything else (incl. block scalars) → inline text
118
136
  */
119
- function resolvePromptSource(raw, body, filePath, id) {
137
+ function resolvePromptSource(raw, filePath, id) {
120
138
  const trimmed = raw.trim();
121
- if (trimmed === "inline") {
122
- const text = body.trim();
123
- if (!text) {
124
- throw new UsageError(`Task "${id}" sets \`prompt: inline\` but the markdown body is empty. File: ${filePath}`, "MISSING_REQUIRED_ARGUMENT");
125
- }
126
- return { kind: "inline", text };
127
- }
128
139
  if (trimmed.startsWith("./") || trimmed.startsWith("../") || path.isAbsolute(trimmed)) {
129
140
  return { kind: "file", path: trimmed };
130
141
  }
@@ -134,7 +145,9 @@ function resolvePromptSource(raw, body, filePath, id) {
134
145
  if (/^[a-z][a-z0-9_-]*:[^\s]/i.test(trimmed)) {
135
146
  return { kind: "asset", ref: trimmed };
136
147
  }
137
- // Fallback: treat the literal value as the prompt text.
148
+ if (!trimmed) {
149
+ throw new UsageError(`Task "${id}" has empty \`prompt\`. File: ${filePath}`, "MISSING_REQUIRED_ARGUMENT");
150
+ }
138
151
  return { kind: "inline", text: trimmed };
139
152
  }
140
153
  function readString(value, key, filePath) {
@@ -144,7 +157,7 @@ function readString(value, key, filePath) {
144
157
  return value.trim();
145
158
  if (typeof value === "number" || typeof value === "boolean")
146
159
  return String(value);
147
- throw new UsageError(`Frontmatter key "${key}" must be a string. File: ${filePath}`, "INVALID_FLAG_VALUE");
160
+ throw new UsageError(`Key "${key}" must be a string. File: ${filePath}`, "INVALID_FLAG_VALUE");
148
161
  }
149
162
  function readStringArray(value) {
150
163
  if (value === undefined || value === null)
@@ -175,7 +188,7 @@ function readCommand(value, filePath, id) {
175
188
  }
176
189
  return parts;
177
190
  }
178
- throw new UsageError(`Frontmatter key "command" must be a string or array of strings. File: ${filePath}`, "INVALID_FLAG_VALUE");
191
+ throw new UsageError(`Key "command" must be a string or array of strings. File: ${filePath}`, "INVALID_FLAG_VALUE");
179
192
  }
180
193
  function readParams(value, filePath) {
181
194
  if (value === undefined || value === null)
@@ -194,5 +207,5 @@ function readParams(value, filePath) {
194
207
  // fall through
195
208
  }
196
209
  }
197
- throw new UsageError(`Frontmatter key "params" must be a mapping or a JSON object. File: ${filePath}`, "INVALID_FLAG_VALUE");
210
+ throw new UsageError(`Key "params" must be a mapping or a JSON object. File: ${filePath}`, "INVALID_FLAG_VALUE");
198
211
  }
@@ -1,3 +1,6 @@
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/.
1
4
  /**
2
5
  * Resolve the absolute invocation that the OS scheduler should run.
3
6
  *
@@ -1,3 +1,6 @@
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/.
1
4
  /**
2
5
  * `akm tasks run <id>` — what cron / launchd / schtasks invoke at the
3
6
  * scheduled moment.
@@ -24,12 +27,13 @@ import path from "node:path";
24
27
  import { parseAssetRef } from "../core/asset-ref";
25
28
  import { resolveStashDir } from "../core/common";
26
29
  import { loadConfig } from "../core/config";
27
- import { NotFoundError } from "../core/errors";
30
+ import { NotFoundError, rethrowIfTestIsolationError } from "../core/errors";
28
31
  import { getTaskLogDir } from "../core/paths";
29
32
  import { getTaskHistory, openStateDatabase, queryTaskHistory, upsertTaskHistory } from "../core/state-db";
30
33
  import { error } from "../core/warn";
31
34
  import { requireAgentProfile, runAgent } from "../integrations/agent";
32
35
  import { resolveProcessAgentProfile } from "../integrations/agent/config";
36
+ import { resolveRunner } from "../integrations/agent/runner";
33
37
  import { resolveAssetPath } from "../sources/resolve";
34
38
  import { startWorkflowRun } from "../workflows/runs";
35
39
  import { parseTaskDocument } from "./parser";
@@ -40,8 +44,8 @@ export async function runTask(id, options = {}) {
40
44
  const now = options.now ?? (() => new Date());
41
45
  const logDir = options.logDir ?? getTaskLogDir();
42
46
  const filePath = await resolveAssetPath(stashDir, "task", id);
43
- const markdown = fs.readFileSync(filePath, "utf8");
44
- const task = parseTaskDocument({ markdown, filePath, id });
47
+ const yaml = fs.readFileSync(filePath, "utf8");
48
+ const task = parseTaskDocument({ yaml, filePath, id });
45
49
  const startedAt = now();
46
50
  const startedIso = startedAt.toISOString();
47
51
  const tsSlug = startedIso.replace(/[:.]/g, "-");
@@ -91,8 +95,8 @@ export async function runTask(id, options = {}) {
91
95
  now,
92
96
  runAgentImpl,
93
97
  agentOptions: options.agentOptions,
94
- agentConfig: config.agent,
95
- agentTimeoutMs: config.agent?.timeoutMs ?? undefined,
98
+ agentConfig: config,
99
+ agentTimeoutMs: undefined,
96
100
  });
97
101
  }
98
102
  // ── command target ──────────────────────────────────────────────────────────
@@ -111,6 +115,7 @@ async function runCommandTask(input) {
111
115
  stdin: "ignore",
112
116
  stdout: "pipe",
113
117
  stderr: "pipe",
118
+ cwd: process.env.HOME ?? "/tmp",
114
119
  });
115
120
  let timer;
116
121
  let timedOut = false;
@@ -249,15 +254,35 @@ async function runPromptTask(input) {
249
254
  // Use pre-resolved agent config when available to avoid redundant loadConfig()
250
255
  // calls in batch task runs (Fix C6). Fall back to loadConfig() for callers
251
256
  // that invoke runPromptTask directly without threading config.
252
- const agentCfg = input.agentConfig !== undefined ? input.agentConfig : loadConfig().agent;
257
+ const fullConfig = loadConfig();
258
+ const agentCfg = input.agentConfig !== undefined ? input.agentConfig : fullConfig;
253
259
  // Resolve the profile for this task. When the task doc specifies a profile,
254
260
  // use it directly. Otherwise fall back to the per-process config for "task"
255
261
  // (agent.processes["task"]), which itself falls back to agent.default.
256
262
  let profile;
257
263
  let processTimeoutMs;
258
264
  if (task.target.profile) {
259
- // Task doc explicitly names a profile honour it directly.
260
- profile = requireAgentProfile(agentCfg, task.target.profile);
265
+ // v2: if profiles.agent is configured, resolve through new runner
266
+ if (fullConfig.profiles?.agent) {
267
+ const mode = task.target.mode ?? "agent";
268
+ if (mode !== "llm") {
269
+ const runnerSpec = resolveRunner(mode, task.target.profile, fullConfig);
270
+ if (runnerSpec.kind === "agent" || runnerSpec.kind === "sdk") {
271
+ profile = runnerSpec.profile;
272
+ processTimeoutMs = runnerSpec.timeoutMs;
273
+ }
274
+ else {
275
+ profile = requireAgentProfile(agentCfg, task.target.profile);
276
+ }
277
+ }
278
+ else {
279
+ profile = requireAgentProfile(agentCfg, task.target.profile);
280
+ }
281
+ }
282
+ else {
283
+ // v1: Task doc explicitly names a profile — honour it directly.
284
+ profile = requireAgentProfile(agentCfg, task.target.profile);
285
+ }
261
286
  }
262
287
  else {
263
288
  // No per-task profile: use process config for "task" as a fallback.
@@ -273,7 +298,7 @@ async function runPromptTask(input) {
273
298
  ? processTimeoutMs
274
299
  : input.agentTimeoutMs !== undefined
275
300
  ? input.agentTimeoutMs
276
- : agentCfg?.timeoutMs;
301
+ : undefined;
277
302
  const promptText = await resolvePromptText(task, stashDir);
278
303
  const result = await runAgentImpl(profile, promptText, {
279
304
  stdio: "captured",
@@ -362,6 +387,7 @@ function appendHistory(result) {
362
387
  }
363
388
  }
364
389
  catch (err) {
390
+ rethrowIfTestIsolationError(err);
365
391
  error(`[akm] task history DB write failed: ${String(err)}`);
366
392
  }
367
393
  }
@@ -1,3 +1,6 @@
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/.
1
4
  /**
2
5
  * Cross-platform schedule parsing and translation.
3
6
  *
@@ -38,7 +41,7 @@ const FIELD_LIMITS = {
38
41
  month: { min: 1, max: 12 },
39
42
  dow: { min: 0, max: 6 },
40
43
  };
41
- const SUPPORTED_HINT = "Supported subset: `*`, single integers (`5`), and step-on-star (`*/N`). " +
44
+ const SUPPORTED_HINT = "Supported subset: `*`, single integers (`5`), step-on-star (`*/N`), and comma lists (`7,37`). " +
42
45
  "Aliases: `@hourly`, `@daily`, `@weekly`, `@monthly`. " +
43
46
  "Lists, ranges, and named days/months are not supported.";
44
47
  export function parseSchedule(input, backend) {
@@ -103,6 +106,19 @@ function parseField(raw, name, limit, original) {
103
106
  }
104
107
  return { kind: "value", value };
105
108
  }
109
+ // Comma-separated list: `7,37` or `0,15,30,45`. Each element must be an
110
+ // integer within [limit.min, limit.max]. Duplicates and unsorted input
111
+ // are accepted but the parsed form is deduped + ascending so downstream
112
+ // consumers can rely on a canonical shape.
113
+ if (/^\d+(,\d+)+$/.test(raw)) {
114
+ const values = [...new Set(raw.split(",").map((s) => Number(s)))].sort((a, b) => a - b);
115
+ for (const v of values) {
116
+ if (!Number.isInteger(v) || v < limit.min || v > limit.max) {
117
+ throw new UsageError(`Invalid ${name} list value "${v}" in schedule "${original}" (allowed ${limit.min}-${limit.max}).`, "INVALID_FLAG_VALUE");
118
+ }
119
+ }
120
+ return { kind: "list", values };
121
+ }
106
122
  throw new UsageError(`Unsupported ${name} expression "${raw}" in schedule "${original}". ${SUPPORTED_HINT}`, "INVALID_FLAG_VALUE");
107
123
  }
108
124
  // ── Backend translators ─────────────────────────────────────────────────────
@@ -161,6 +177,9 @@ function rejectStepInsideCalendar(field, name, spec) {
161
177
  if (field.kind === "step") {
162
178
  throw new UsageError(`Schedule "${spec.raw}" uses step (${name} = */N) in a position macOS launchd cannot express. ${SUPPORTED_HINT}`, "INVALID_FLAG_VALUE", "Either restrict the step to the minute or hour field only, or rewrite the schedule with concrete values.");
163
179
  }
180
+ if (field.kind === "list") {
181
+ throw new UsageError(`Schedule "${spec.raw}" uses comma list (${name} = a,b,...) which macOS launchd cannot express as a single trigger. ${SUPPORTED_HINT}`, "INVALID_FLAG_VALUE", "Either install one task per list element, or rewrite the schedule with a step (`*/N`) or single value.");
182
+ }
164
183
  }
165
184
  export function translateToSchtasks(spec) {
166
185
  const f = spec.fields;
@@ -1,3 +1,6 @@
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/.
1
4
  /**
2
5
  * Task asset schema. A task pairs a cron-style schedule with exactly one of:
3
6
  *
@@ -6,8 +9,7 @@
6
9
  * agent harness (e.g. `opencode run`)
7
10
  * • a command target — invoked directly via `Bun.spawn()`, no AI agent
8
11
  *
9
- * Tasks are stored as markdown files at `<stash>/tasks/<id>.md`. The
10
- * frontmatter holds the schedule and target; for inline-prompt tasks the
11
- * markdown body is the prompt text.
12
+ * Tasks are stored as pure YAML files at `<stash>/tasks/<id>.yml`. Multi-line
13
+ * inline prompts use a YAML block scalar (`prompt: |`).
12
14
  */
13
15
  export const TASK_SCHEMA_VERSION = 1;
@@ -1,3 +1,6 @@
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/.
1
4
  /**
2
5
  * Validate a parsed {@link TaskDocument} for runnability.
3
6
  *
@@ -36,12 +39,12 @@ export async function validateTaskDocument(task, options) {
36
39
  return;
37
40
  }
38
41
  // Prompt target. Resolve the profile unconditionally — when no profile is
39
- // set on the task, requireAgentProfile falls back to config.agent.default
40
- // and throws a clear error if neither is configured. Catching this at
42
+ // set on the task, requireAgentProfile falls back to defaults.agent and
43
+ // throws a clear error if neither is configured. Catching this at
41
44
  // `tasks add` / `tasks sync` time is much more useful than failing only
42
45
  // when the OS scheduler fires.
43
46
  const config = loadConfig();
44
- requireAgentProfile(config.agent, task.target.profile);
47
+ requireAgentProfile(config, task.target.profile);
45
48
  const src = task.target.source;
46
49
  if (src.kind === "asset") {
47
50
  const stashDir = options.stashDir ?? resolveStashDir();
package/dist/version.js CHANGED
@@ -1,3 +1,6 @@
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/.
1
4
  import fs from "node:fs";
2
5
  import path from "node:path";
3
6
  // Version: prefer compile-time define, then package.json, then fallback
@@ -1,3 +1,6 @@
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/.
1
4
  import indexTemplate from "./index-template.md" with { type: "text" };
2
5
  import logTemplate from "./log-template.md" with { type: "text" };
3
6
  import schemaTemplate from "./schema-template.md" with { type: "text" };
package/dist/wiki/wiki.js CHANGED
@@ -1,3 +1,6 @@
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/.
1
4
  /**
2
5
  * Multi-wiki support for akm (issue #119).
3
6
  *
@@ -1,3 +1,6 @@
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/.
1
4
  import fs from "node:fs";
2
5
  import path from "node:path";
3
6
  import { resolveAssetPathFromName } from "../core/asset-spec";
@@ -1,3 +1,6 @@
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/.
1
4
  import { UsageError } from "../core/errors";
2
5
  export const WORKFLOW_STEP_STATES = [
3
6
  "completed",
@@ -1,7 +1,44 @@
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/.
1
4
  import { Database } from "bun:sqlite";
2
5
  import fs from "node:fs";
3
6
  import path from "node:path";
4
7
  import { getWorkflowDbPath } from "../core/paths";
8
+ /**
9
+ * workflow.db — Durable SQLite database for workflow run state.
10
+ *
11
+ * Owns the `workflow_runs` and `workflow_run_steps` tables that track active /
12
+ * completed workflow executions. Like `state.db` (and unlike `index.db`), the
13
+ * rows here are NON-REGENERABLE — losing them is data loss. Schema must evolve
14
+ * via incremental, additive migrations recorded in `schema_migrations`.
15
+ *
16
+ * ## Migration-safety contract
17
+ *
18
+ * The `schema_migrations` table records every applied migration by a stable
19
+ * string ID. `runMigrations(db)` is idempotent: new installs run every
20
+ * migration in order; upgrades run only the ones not yet applied. The
21
+ * migration framework here intentionally mirrors `src/core/state-db.ts` so
22
+ * future schema evolution follows a single proven pattern.
23
+ *
24
+ * Permitted schema evolution operations (always migration-safe in SQLite):
25
+ * - ALTER TABLE … ADD COLUMN <name> <type> DEFAULT <value>
26
+ * - CREATE INDEX IF NOT EXISTS …
27
+ * - CREATE TABLE IF NOT EXISTS … (additive new tables)
28
+ *
29
+ * ## Bootstrapping pre-versioning databases
30
+ *
31
+ * Workflow databases created before this file gained `schema_migrations`
32
+ * already have the `workflow_runs.scope_key` column applied by the previous
33
+ * ad-hoc `PRAGMA table_info` + `ALTER TABLE` code. To avoid re-running the
34
+ * migration (which would no-op but still wastes work and clutters logs), the
35
+ * runner detects this state and back-fills the `schema_migrations` row for
36
+ * the scope-key migration before evaluating the migration list. See
37
+ * `bootstrapPreVersioningDb()`.
38
+ *
39
+ * @module workflows/db
40
+ */
41
+ // ── Public API ───────────────────────────────────────────────────────────────
5
42
  export function openWorkflowDatabase(dbPath = getWorkflowDbPath()) {
6
43
  const dir = path.dirname(dbPath);
7
44
  if (!fs.existsSync(dir)) {
@@ -10,18 +47,29 @@ export function openWorkflowDatabase(dbPath = getWorkflowDbPath()) {
10
47
  const db = new Database(dbPath);
11
48
  db.exec("PRAGMA journal_mode = WAL");
12
49
  db.exec("PRAGMA foreign_keys = ON");
13
- ensureWorkflowSchema(db);
50
+ ensureBaseSchema(db);
51
+ runMigrations(db);
14
52
  return db;
15
53
  }
16
54
  export function closeWorkflowDatabase(db) {
17
55
  db.close();
18
56
  }
19
- function ensureWorkflowSchema(db) {
57
+ // ── Base schema ──────────────────────────────────────────────────────────────
58
+ /**
59
+ * Create the baseline `workflow_runs` and `workflow_run_steps` tables if they
60
+ * do not exist. These statements are idempotent: existing databases keep their
61
+ * current schema, and migrations evolve them further.
62
+ *
63
+ * NOTE: the `scope_key` column on `workflow_runs` is intentionally NOT declared
64
+ * here. It is added by migration `001-add-scope-key`. Fresh databases will run
65
+ * the migration immediately on first open; pre-versioning databases that
66
+ * already have the column are bootstrapped — see {@link runMigrations}.
67
+ */
68
+ function ensureBaseSchema(db) {
20
69
  db.exec(`
21
70
  CREATE TABLE IF NOT EXISTS workflow_runs (
22
71
  id TEXT PRIMARY KEY,
23
72
  workflow_ref TEXT NOT NULL,
24
- scope_key TEXT,
25
73
  workflow_entry_id INTEGER,
26
74
  workflow_title TEXT NOT NULL,
27
75
  status TEXT NOT NULL CHECK (status IN ('active', 'completed', 'blocked', 'failed')),
@@ -53,12 +101,94 @@ function ensureWorkflowSchema(db) {
53
101
  CREATE INDEX IF NOT EXISTS idx_workflow_run_steps_run_sequence
54
102
  ON workflow_run_steps(run_id, sequence_index);
55
103
  `);
56
- const columns = db
57
- .query("PRAGMA table_info(workflow_runs)")
58
- .all()
59
- .map((column) => column.name);
60
- if (!columns.includes("scope_key")) {
61
- db.exec("ALTER TABLE workflow_runs ADD COLUMN scope_key TEXT");
104
+ }
105
+ /**
106
+ * All workflow.db migrations in application order. New migrations are
107
+ * APPENDED — never inserted in the middle or reordered.
108
+ */
109
+ const MIGRATIONS = [
110
+ // ── Migration 001 — add scope_key column ────────────────────────────────────
111
+ //
112
+ // Adds the `scope_key` column to `workflow_runs` so runs can be partitioned
113
+ // per stash/scope. Pre-versioning databases that already have this column
114
+ // are bootstrapped before this migration runs — see runMigrations().
115
+ {
116
+ id: "001-add-scope-key",
117
+ up: `
118
+ ALTER TABLE workflow_runs ADD COLUMN scope_key TEXT;
119
+
120
+ CREATE INDEX IF NOT EXISTS idx_workflow_runs_scope_ref_status
121
+ ON workflow_runs(scope_key, workflow_ref, status);
122
+ `,
123
+ },
124
+ ];
125
+ /**
126
+ * Stable id of the scope_key migration. Exported for bootstrap detection and
127
+ * tests.
128
+ */
129
+ const SCOPE_KEY_MIGRATION_ID = "001-add-scope-key";
130
+ /**
131
+ * Create the migrations table if it does not exist. Called unconditionally on
132
+ * every open so a fresh database bootstraps correctly.
133
+ */
134
+ function ensureMigrationsTable(db) {
135
+ db.exec(`
136
+ CREATE TABLE IF NOT EXISTS schema_migrations (
137
+ id TEXT PRIMARY KEY,
138
+ applied_at TEXT NOT NULL DEFAULT (datetime('now'))
139
+ );
140
+ `);
141
+ }
142
+ /**
143
+ * Detect whether a column exists on a given table.
144
+ */
145
+ function hasColumn(db, table, column) {
146
+ const rows = db.query(`PRAGMA table_info(${table})`).all();
147
+ return rows.some((r) => r.name === column);
148
+ }
149
+ /**
150
+ * Back-fill `schema_migrations` rows for any schema state that existed before
151
+ * this file gained migration tracking.
152
+ *
153
+ * The pre-versioning code added the `scope_key` column on `workflow_runs` via
154
+ * an ad-hoc PRAGMA / ALTER TABLE pair. Those databases must not re-run the
155
+ * scope_key migration (the ALTER would fail with "duplicate column name"
156
+ * since the migration body does not use IF NOT EXISTS — SQLite does not
157
+ * support that clause on ALTER TABLE ADD COLUMN). Instead, we mark the
158
+ * migration as already applied.
159
+ *
160
+ * This function is a no-op on fresh databases: the `scope_key` column does
161
+ * not exist, so the migration runs normally and records itself.
162
+ */
163
+ function bootstrapPreVersioningDb(db) {
164
+ const alreadyRecorded = db.prepare("SELECT 1 FROM schema_migrations WHERE id = ?").get(SCOPE_KEY_MIGRATION_ID);
165
+ if (alreadyRecorded)
166
+ return;
167
+ if (hasColumn(db, "workflow_runs", "scope_key")) {
168
+ db.prepare("INSERT INTO schema_migrations (id) VALUES (?)").run(SCOPE_KEY_MIGRATION_ID);
169
+ }
170
+ }
171
+ /**
172
+ * Apply every pending migration in a single transaction per migration.
173
+ *
174
+ * Each migration is applied in its own transaction so a failure in migration N
175
+ * does not roll back already-applied migrations 1..N-1. The migration row is
176
+ * inserted AFTER the DDL succeeds — a crash mid-migration leaves no row and
177
+ * the migration is retried on next open.
178
+ *
179
+ * Called automatically by {@link openWorkflowDatabase}.
180
+ */
181
+ export function runMigrations(db) {
182
+ ensureMigrationsTable(db);
183
+ bootstrapPreVersioningDb(db);
184
+ const appliedRows = db.prepare("SELECT id FROM schema_migrations").all();
185
+ const applied = new Set(appliedRows.map((r) => r.id));
186
+ for (const migration of MIGRATIONS) {
187
+ if (applied.has(migration.id))
188
+ continue;
189
+ db.transaction(() => {
190
+ db.exec(migration.up);
191
+ db.prepare("INSERT INTO schema_migrations (id) VALUES (?)").run(migration.id);
192
+ })();
62
193
  }
63
- db.exec("CREATE INDEX IF NOT EXISTS idx_workflow_runs_scope_ref_status ON workflow_runs(scope_key, workflow_ref, status)");
64
194
  }
@@ -1,13 +1,6 @@
1
- /**
2
- * Side-channel cache that lets the workflow renderer hand a validated
3
- * `WorkflowDocument` to the indexer without persisting it through the
4
- * `entry_json` column or widening `StashEntry` with a workflow-shaped field.
5
- *
6
- * The renderer is called during metadata generation; the indexer writes the
7
- * document to `workflow_documents` after `upsertEntry` returns the row id.
8
- * A WeakMap keyed by the entry object preserves the parse work between the
9
- * two phases without leaking memory if the entry is dropped.
10
- */
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/.
11
4
  const cache = new WeakMap();
12
5
  export function cacheWorkflowDocument(entry, doc) {
13
6
  cache.set(entry, doc);
@@ -1,3 +1,6 @@
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/.
1
4
  /**
2
5
  * Workflow markdown → WorkflowDocument JSON.
3
6
  *
@@ -1,3 +1,6 @@
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/.
1
4
  /**
2
5
  * Show + indexing renderer for workflow assets.
3
6
  *