akm-cli 0.7.4 → 0.8.0-rc.10

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 (300) hide show
  1. package/CHANGELOG.md +224 -1
  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 +133 -0
  8. package/dist/cli/shared.js +129 -0
  9. package/dist/cli.js +2631 -1440
  10. package/dist/commands/add-cli.js +279 -0
  11. package/dist/commands/agent-dispatch.js +110 -0
  12. package/dist/commands/agent-support.js +68 -0
  13. package/dist/commands/completions.js +3 -0
  14. package/dist/commands/config-cli.js +130 -534
  15. package/dist/commands/consolidate.js +2122 -0
  16. package/dist/commands/curate.js +45 -3
  17. package/dist/commands/db-cli.js +23 -0
  18. package/dist/commands/distill-promotion-policy.js +660 -0
  19. package/dist/commands/distill.js +1081 -73
  20. package/dist/commands/env.js +213 -0
  21. package/dist/commands/eval-cases.js +43 -0
  22. package/dist/commands/events.js +15 -24
  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 +477 -0
  28. package/dist/commands/health.js +1302 -0
  29. package/dist/commands/help/help-accept.md +12 -0
  30. package/dist/commands/help/help-improve.md +69 -0
  31. package/dist/commands/help/help-proposals.md +18 -0
  32. package/dist/commands/help/help-propose.md +17 -0
  33. package/dist/commands/help/help-reject.md +11 -0
  34. package/dist/commands/history.js +54 -46
  35. package/dist/commands/improve-auto-accept.js +97 -0
  36. package/dist/commands/improve-cli.js +217 -0
  37. package/dist/commands/improve-profiles.js +166 -0
  38. package/dist/commands/improve-result-file.js +167 -0
  39. package/dist/commands/improve.js +2373 -0
  40. package/dist/commands/info.js +5 -2
  41. package/dist/commands/init.js +50 -2
  42. package/dist/commands/installed-stashes.js +102 -139
  43. package/dist/commands/knowledge.js +136 -0
  44. package/dist/commands/lint/agent-linter.js +49 -0
  45. package/dist/commands/lint/base-linter.js +479 -0
  46. package/dist/commands/lint/command-linter.js +49 -0
  47. package/dist/commands/lint/default-linter.js +16 -0
  48. package/dist/commands/lint/env-key-rules.js +154 -0
  49. package/dist/commands/lint/index.js +196 -0
  50. package/dist/commands/lint/knowledge-linter.js +16 -0
  51. package/dist/commands/lint/markdown-insertion.js +343 -0
  52. package/dist/commands/lint/memory-linter.js +61 -0
  53. package/dist/commands/lint/registry.js +36 -0
  54. package/dist/commands/lint/skill-linter.js +45 -0
  55. package/dist/commands/lint/task-linter.js +50 -0
  56. package/dist/commands/lint/types.js +4 -0
  57. package/dist/commands/lint/workflow-linter.js +56 -0
  58. package/dist/commands/lint.js +4 -0
  59. package/dist/commands/migration-help.js +3 -0
  60. package/dist/commands/proposal.js +67 -12
  61. package/dist/commands/propose.js +120 -45
  62. package/dist/commands/reflect.js +1104 -60
  63. package/dist/commands/registry-cli.js +150 -0
  64. package/dist/commands/registry-search.js +5 -2
  65. package/dist/commands/remember-cli.js +257 -0
  66. package/dist/commands/remember.js +70 -7
  67. package/dist/commands/schema-repair.js +203 -0
  68. package/dist/commands/search.js +115 -14
  69. package/dist/commands/secret.js +173 -0
  70. package/dist/commands/self-update.js +3 -0
  71. package/dist/commands/show.js +158 -60
  72. package/dist/commands/source-add.js +17 -45
  73. package/dist/commands/source-clone.js +3 -0
  74. package/dist/commands/source-manage.js +14 -19
  75. package/dist/commands/tasks.js +437 -0
  76. package/dist/commands/url-checker.js +42 -0
  77. package/dist/core/action-contributors.js +28 -0
  78. package/dist/core/asset-ref.js +17 -2
  79. package/dist/core/asset-registry.js +12 -17
  80. package/dist/core/asset-serialize.js +88 -0
  81. package/dist/core/asset-spec.js +67 -1
  82. package/dist/core/common.js +182 -0
  83. package/dist/core/concurrent.js +25 -0
  84. package/dist/core/config-io.js +347 -0
  85. package/dist/core/config-migration.js +622 -0
  86. package/dist/core/config-schema.js +534 -0
  87. package/dist/core/config-sources.js +108 -0
  88. package/dist/core/config-types.js +4 -0
  89. package/dist/core/config-walker.js +337 -0
  90. package/dist/core/config.js +364 -968
  91. package/dist/core/errors.js +42 -20
  92. package/dist/core/events.js +105 -135
  93. package/dist/core/file-lock.js +104 -0
  94. package/dist/core/frontmatter.js +75 -8
  95. package/dist/core/lesson-lint.js +3 -0
  96. package/dist/core/markdown.js +20 -0
  97. package/dist/core/memory-belief.js +62 -0
  98. package/dist/core/memory-contradiction-detect.js +274 -0
  99. package/dist/core/memory-improve.js +806 -0
  100. package/dist/core/parse.js +158 -0
  101. package/dist/core/paths.js +280 -14
  102. package/dist/core/proposal-quality-validators.js +380 -0
  103. package/dist/core/proposal-validators.js +69 -0
  104. package/dist/core/proposals.js +512 -42
  105. package/dist/core/state-db.js +1068 -0
  106. package/dist/core/text-truncation.js +107 -0
  107. package/dist/core/time.js +54 -0
  108. package/dist/core/tty.js +59 -0
  109. package/dist/core/warn.js +64 -1
  110. package/dist/core/write-source.js +3 -0
  111. package/dist/indexer/db-backup.js +391 -0
  112. package/dist/indexer/db-search.js +198 -489
  113. package/dist/indexer/db.js +990 -108
  114. package/dist/indexer/ensure-index.js +136 -0
  115. package/dist/indexer/file-context.js +3 -0
  116. package/dist/indexer/graph-boost.js +376 -101
  117. package/dist/indexer/graph-db.js +391 -0
  118. package/dist/indexer/graph-dedup.js +95 -0
  119. package/dist/indexer/graph-extraction.js +550 -114
  120. package/dist/indexer/index-context.js +4 -0
  121. package/dist/indexer/indexer.js +547 -309
  122. package/dist/indexer/llm-cache.js +52 -0
  123. package/dist/indexer/manifest.js +3 -0
  124. package/dist/indexer/matchers.js +167 -160
  125. package/dist/indexer/memory-inference.js +152 -74
  126. package/dist/indexer/metadata-contributors.js +29 -0
  127. package/dist/indexer/metadata.js +275 -196
  128. package/dist/indexer/path-resolver.js +92 -0
  129. package/dist/indexer/project-context.js +192 -0
  130. package/dist/indexer/ranking-contributors.js +331 -0
  131. package/dist/indexer/ranking.js +81 -0
  132. package/dist/indexer/search-fields.js +5 -9
  133. package/dist/indexer/search-hit-enrichers.js +111 -0
  134. package/dist/indexer/search-source.js +44 -10
  135. package/dist/indexer/semantic-status.js +6 -17
  136. package/dist/indexer/staleness-detect.js +447 -0
  137. package/dist/indexer/usage-events.js +12 -9
  138. package/dist/indexer/walker.js +28 -0
  139. package/dist/integrations/agent/builders.js +135 -0
  140. package/dist/integrations/agent/config.js +122 -230
  141. package/dist/integrations/agent/detect.js +3 -0
  142. package/dist/integrations/agent/index.js +7 -13
  143. package/dist/integrations/agent/model-aliases.js +55 -0
  144. package/dist/integrations/agent/profiles.js +70 -5
  145. package/dist/integrations/agent/prompts.js +250 -36
  146. package/dist/integrations/agent/runner.js +151 -0
  147. package/dist/integrations/agent/sdk-runner.js +126 -0
  148. package/dist/integrations/agent/spawn.js +183 -35
  149. package/dist/integrations/github.js +3 -0
  150. package/dist/integrations/lockfile.js +32 -69
  151. package/dist/integrations/session-logs/index.js +69 -0
  152. package/dist/integrations/session-logs/inline-refs.js +35 -0
  153. package/dist/integrations/session-logs/pre-filter.js +152 -0
  154. package/dist/integrations/session-logs/providers/claude-code.js +282 -0
  155. package/dist/integrations/session-logs/providers/opencode.js +258 -0
  156. package/dist/integrations/session-logs/types.js +4 -0
  157. package/dist/llm/call-ai.js +62 -0
  158. package/dist/llm/client.js +79 -88
  159. package/dist/llm/embedder.js +20 -29
  160. package/dist/llm/embedders/cache.js +3 -7
  161. package/dist/llm/embedders/local.js +42 -1
  162. package/dist/llm/embedders/remote.js +20 -8
  163. package/dist/llm/embedders/types.js +3 -7
  164. package/dist/llm/feature-gate.js +95 -48
  165. package/dist/llm/graph-extract.js +676 -72
  166. package/dist/llm/index-passes.js +44 -29
  167. package/dist/llm/memory-infer.js +80 -71
  168. package/dist/llm/metadata-enhance.js +42 -29
  169. package/dist/llm/prompts/extract-session.md +80 -0
  170. package/dist/llm/prompts/graph-extract-user-prompt.md +35 -0
  171. package/dist/output/cli-hints-full.md +292 -0
  172. package/dist/output/cli-hints-short.md +66 -0
  173. package/dist/output/cli-hints.js +7 -311
  174. package/dist/output/context.js +60 -8
  175. package/dist/output/renderers.js +306 -258
  176. package/dist/output/shapes/curate.js +56 -0
  177. package/dist/output/shapes/distill.js +10 -0
  178. package/dist/output/shapes/env-list.js +19 -0
  179. package/dist/output/shapes/events.js +11 -0
  180. package/dist/output/shapes/helpers.js +424 -0
  181. package/dist/output/shapes/history.js +7 -0
  182. package/dist/output/shapes/passthrough.js +102 -0
  183. package/dist/output/shapes/proposal-accept.js +7 -0
  184. package/dist/output/shapes/proposal-diff.js +7 -0
  185. package/dist/output/shapes/proposal-list.js +7 -0
  186. package/dist/output/shapes/proposal-producer.js +11 -0
  187. package/dist/output/shapes/proposal-reject.js +7 -0
  188. package/dist/output/shapes/proposal-show.js +7 -0
  189. package/dist/output/shapes/registry-search.js +6 -0
  190. package/dist/output/shapes/registry.js +30 -0
  191. package/dist/output/shapes/search.js +6 -0
  192. package/dist/output/shapes/secret-list.js +19 -0
  193. package/dist/output/shapes/show.js +6 -0
  194. package/dist/output/shapes/vault-list.js +19 -0
  195. package/dist/output/shapes.js +51 -511
  196. package/dist/output/text/add.js +6 -0
  197. package/dist/output/text/clone.js +6 -0
  198. package/dist/output/text/config.js +6 -0
  199. package/dist/output/text/curate.js +6 -0
  200. package/dist/output/text/distill.js +7 -0
  201. package/dist/output/text/enable-disable.js +7 -0
  202. package/dist/output/text/events.js +10 -0
  203. package/dist/output/text/feedback.js +6 -0
  204. package/dist/output/text/helpers.js +1039 -0
  205. package/dist/output/text/history.js +7 -0
  206. package/dist/output/text/import.js +6 -0
  207. package/dist/output/text/index.js +6 -0
  208. package/dist/output/text/info.js +6 -0
  209. package/dist/output/text/init.js +6 -0
  210. package/dist/output/text/list.js +6 -0
  211. package/dist/output/text/proposal-producer.js +8 -0
  212. package/dist/output/text/proposal.js +11 -0
  213. package/dist/output/text/registry-commands.js +11 -0
  214. package/dist/output/text/registry.js +30 -0
  215. package/dist/output/text/remember.js +6 -0
  216. package/dist/output/text/remove.js +6 -0
  217. package/dist/output/text/save.js +6 -0
  218. package/dist/output/text/search.js +6 -0
  219. package/dist/output/text/show.js +6 -0
  220. package/dist/output/text/update.js +6 -0
  221. package/dist/output/text/upgrade.js +6 -0
  222. package/dist/output/text/vault.js +16 -0
  223. package/dist/output/text/wiki.js +15 -0
  224. package/dist/output/text/workflow.js +14 -0
  225. package/dist/output/text.js +44 -1093
  226. package/dist/registry/build-index.js +3 -0
  227. package/dist/registry/create-provider-registry.js +3 -0
  228. package/dist/registry/factory.js +4 -1
  229. package/dist/registry/origin-resolve.js +3 -0
  230. package/dist/registry/providers/index.js +3 -0
  231. package/dist/registry/providers/skills-sh.js +71 -50
  232. package/dist/registry/providers/static-index.js +53 -48
  233. package/dist/registry/providers/types.js +3 -24
  234. package/dist/registry/resolve.js +11 -16
  235. package/dist/registry/types.js +3 -0
  236. package/dist/scripts/migrate-storage.js +17750 -0
  237. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +9031 -0
  238. package/dist/scripts/migrations/v16-to-v17.js +141 -0
  239. package/dist/setup/detect.js +3 -0
  240. package/dist/setup/ripgrep-install.js +3 -0
  241. package/dist/setup/ripgrep-resolve.js +3 -0
  242. package/dist/setup/setup.js +775 -37
  243. package/dist/setup/steps.js +3 -15
  244. package/dist/sources/include.js +3 -0
  245. package/dist/sources/provider-factory.js +5 -12
  246. package/dist/sources/provider.js +3 -20
  247. package/dist/sources/providers/filesystem.js +19 -23
  248. package/dist/sources/providers/git.js +179 -20
  249. package/dist/sources/providers/index.js +3 -0
  250. package/dist/sources/providers/install-types.js +3 -13
  251. package/dist/sources/providers/npm.js +3 -4
  252. package/dist/sources/providers/provider-utils.js +3 -0
  253. package/dist/sources/providers/sync-from-ref.js +3 -11
  254. package/dist/sources/providers/tar-utils.js +3 -0
  255. package/dist/sources/providers/website.js +18 -22
  256. package/dist/sources/resolve.js +3 -0
  257. package/dist/sources/types.js +3 -0
  258. package/dist/sources/website-ingest.js +7 -0
  259. package/dist/tasks/backends/cron.js +203 -0
  260. package/dist/tasks/backends/exec-utils.js +28 -0
  261. package/dist/tasks/backends/index.js +24 -0
  262. package/dist/tasks/backends/launchd-template.xml +19 -0
  263. package/dist/tasks/backends/launchd.js +187 -0
  264. package/dist/tasks/backends/schtasks-template.xml +29 -0
  265. package/dist/tasks/backends/schtasks.js +215 -0
  266. package/dist/tasks/parser.js +211 -0
  267. package/dist/tasks/resolveAkmBin.js +87 -0
  268. package/dist/tasks/runner.js +458 -0
  269. package/dist/tasks/schedule.js +227 -0
  270. package/dist/tasks/schema.js +15 -0
  271. package/dist/tasks/validator.js +62 -0
  272. package/dist/version.js +3 -0
  273. package/dist/wiki/index-template.md +12 -0
  274. package/dist/wiki/ingest-workflow-template.md +54 -0
  275. package/dist/wiki/log-template.md +8 -0
  276. package/dist/wiki/schema-template.md +61 -0
  277. package/dist/wiki/wiki-templates.js +15 -0
  278. package/dist/wiki/wiki.js +13 -61
  279. package/dist/workflows/authoring.js +8 -25
  280. package/dist/workflows/cli.js +3 -0
  281. package/dist/workflows/db.js +141 -2
  282. package/dist/workflows/document-cache.js +3 -10
  283. package/dist/workflows/parser.js +3 -0
  284. package/dist/workflows/renderer.js +11 -3
  285. package/dist/workflows/runs.js +91 -89
  286. package/dist/workflows/schema.js +3 -0
  287. package/dist/workflows/scope-key.js +79 -0
  288. package/dist/workflows/validator.js +4 -8
  289. package/dist/workflows/workflow-template.md +24 -0
  290. package/docs/README.md +10 -2
  291. package/docs/data-and-telemetry.md +225 -0
  292. package/docs/migration/release-notes/0.7.0.md +1 -1
  293. package/docs/migration/release-notes/0.7.4.md +1 -1
  294. package/docs/migration/release-notes/0.7.5.md +20 -0
  295. package/docs/migration/release-notes/0.8.0.md +48 -0
  296. package/docs/migration/v0.7-to-v0.8.md +1307 -0
  297. package/package.json +29 -11
  298. package/dist/commands/install-audit.js +0 -381
  299. package/dist/commands/vault.js +0 -333
  300. package/dist/templates/wiki-templates.js +0 -100
@@ -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 { randomUUID } from "node:crypto";
2
5
  import fs from "node:fs";
3
6
  import { parseAssetRef } from "../core/asset-ref";
@@ -12,55 +15,78 @@ import { resolveAssetPath } from "../sources/resolve";
12
15
  import { formatWorkflowErrors } from "./authoring";
13
16
  import { closeWorkflowDatabase, openWorkflowDatabase } from "./db";
14
17
  import { parseWorkflow } from "./parser";
15
- export async function startWorkflowRun(ref, params = {}) {
16
- const asset = await loadWorkflowAsset(ref);
17
- const workflowDb = openWorkflowDatabase();
18
+ import { getCurrentWorkflowScopeKey } from "./scope-key";
19
+ async function withWorkflowDb(fn) {
20
+ const db = openWorkflowDatabase();
18
21
  try {
22
+ return await Promise.resolve(fn(db));
23
+ }
24
+ finally {
25
+ closeWorkflowDatabase(db);
26
+ }
27
+ }
28
+ export async function startWorkflowRun(ref, params = {}, options) {
29
+ const asset = await loadWorkflowAsset(ref);
30
+ return withWorkflowDb(async (db) => {
19
31
  const now = new Date().toISOString();
20
32
  const runId = randomUUID();
33
+ const scopeKey = getCurrentWorkflowScopeKey();
21
34
  const currentStepId = asset.steps[0]?.id ?? null;
22
35
  const workflowEntryId = resolveWorkflowEntryId(asset.sourcePath, asset.ref);
23
- workflowDb.transaction(() => {
24
- workflowDb
25
- .prepare(`INSERT INTO workflow_runs (
26
- id, workflow_ref, workflow_entry_id, workflow_title, status, params_json, current_step_id, created_at, updated_at
27
- ) VALUES (?, ?, ?, ?, 'active', ?, ?, ?, ?)`)
28
- .run(runId, asset.ref, workflowEntryId, asset.title, JSON.stringify(params), currentStepId, now, now);
29
- const insertStep = workflowDb.prepare(`INSERT INTO workflow_run_steps (
36
+ // Concurrency guard (#485): if an active run already exists in this
37
+ // (workflow_ref, scope_key) pair, refuse to create a parallel run unless
38
+ // `force: true` is set. Previously every call inserted unconditionally,
39
+ // so two terminals running `akm workflow start <ref>` left two runs
40
+ // racing; `akm workflow next` then non-deterministically picked one.
41
+ if (!options?.force) {
42
+ const existing = db
43
+ .prepare("SELECT id, current_step_id FROM workflow_runs WHERE workflow_ref = ? AND scope_key = ? AND status = 'active' ORDER BY updated_at DESC LIMIT 1")
44
+ .get(asset.ref, scopeKey);
45
+ if (existing) {
46
+ throw new UsageError(`Workflow ${asset.ref} already has an active run in this scope (id=${existing.id}, step=${existing.current_step_id ?? "—"}). ` +
47
+ `Use 'akm workflow next ${asset.ref}' to resume it, 'akm workflow abandon ${existing.id}' to give up on it, or pass --force to start a parallel run.`, "RESOURCE_ALREADY_EXISTS");
48
+ }
49
+ }
50
+ db.transaction(() => {
51
+ db.prepare(`INSERT INTO workflow_runs (
52
+ id, workflow_ref, scope_key, workflow_entry_id, workflow_title, status, params_json, current_step_id, created_at, updated_at
53
+ ) VALUES (?, ?, ?, ?, ?, 'active', ?, ?, ?, ?)`).run(runId, asset.ref, scopeKey, workflowEntryId, asset.title, JSON.stringify(params), currentStepId, now, now);
54
+ const insertStep = db.prepare(`INSERT INTO workflow_run_steps (
30
55
  run_id, step_id, step_title, instructions, completion_json, sequence_index, status
31
56
  ) VALUES (?, ?, ?, ?, ?, ?, 'pending')`);
32
57
  for (const step of asset.steps) {
33
58
  insertStep.run(runId, step.id, step.title, step.instructions, step.completionCriteria ? JSON.stringify(step.completionCriteria) : null, step.sequenceIndex ?? 0);
34
59
  }
35
60
  })();
36
- const result = getWorkflowStatus(runId);
61
+ const result = await getWorkflowStatus(runId);
37
62
  appendEvent({
38
63
  eventType: "workflow_started",
39
64
  ref: ref,
40
65
  metadata: { runId: result.run.id, title: result.run.workflowTitle },
41
66
  });
42
67
  return result;
43
- }
44
- finally {
45
- closeWorkflowDatabase(workflowDb);
46
- }
68
+ });
47
69
  }
48
- export function getWorkflowStatus(runId) {
49
- const workflowDb = openWorkflowDatabase();
50
- try {
51
- const run = readWorkflowRun(workflowDb, runId);
52
- const steps = readWorkflowRunSteps(workflowDb, run.id);
70
+ export async function getWorkflowStatus(runId) {
71
+ return withWorkflowDb((db) => {
72
+ const run = readWorkflowRun(db, runId);
73
+ const steps = readWorkflowRunSteps(db, run.id);
53
74
  return buildWorkflowRunDetail(run, steps);
54
- }
55
- finally {
56
- closeWorkflowDatabase(workflowDb);
57
- }
75
+ });
58
76
  }
59
- export function listWorkflowRuns(input) {
60
- const workflowDb = openWorkflowDatabase();
61
- try {
77
+ export async function hasWorkflowRun(runId) {
78
+ return withWorkflowDb((db) => {
79
+ const row = db.prepare("SELECT 1 FROM workflow_runs WHERE id = ? LIMIT 1").get(runId);
80
+ return !!row;
81
+ });
82
+ }
83
+ export async function listWorkflowRuns(input) {
84
+ return withWorkflowDb((db) => {
62
85
  const filters = [];
63
86
  const params = [];
87
+ const scopeKey = getCurrentWorkflowScopeKey();
88
+ filters.push("scope_key = ?");
89
+ params.push(scopeKey);
64
90
  if (input?.workflowRef) {
65
91
  const parsed = parseAssetRef(input.workflowRef);
66
92
  if (parsed.type !== "workflow") {
@@ -73,20 +99,16 @@ export function listWorkflowRuns(input) {
73
99
  filters.push("status IN ('active', 'blocked')");
74
100
  }
75
101
  const where = filters.length > 0 ? `WHERE ${filters.join(" AND ")}` : "";
76
- const rows = workflowDb
102
+ const rows = db
77
103
  .prepare(`SELECT * FROM workflow_runs ${where} ORDER BY updated_at DESC, created_at DESC`)
78
104
  .all(...params);
79
105
  return { runs: rows.map(toWorkflowRunSummary) };
80
- }
81
- finally {
82
- closeWorkflowDatabase(workflowDb);
83
- }
106
+ });
84
107
  }
85
108
  export async function getNextWorkflowStep(specifier, params) {
86
- const workflowDb = openWorkflowDatabase();
87
- try {
88
- const { run, autoStarted } = await resolveRunSpecifier(workflowDb, specifier, params);
89
- const steps = readWorkflowRunSteps(workflowDb, run.id);
109
+ return withWorkflowDb(async (db) => {
110
+ const { run, autoStarted } = await resolveRunSpecifier(db, specifier, params);
111
+ const steps = readWorkflowRunSteps(db, run.id);
90
112
  const currentStep = resolveCurrentStep(run, steps);
91
113
  const done = run.status === "completed" ? true : undefined;
92
114
  return {
@@ -100,54 +122,44 @@ export async function getNextWorkflowStep(specifier, params) {
100
122
  ...(done ? { done } : {}),
101
123
  ...(autoStarted ? { autoStarted } : {}),
102
124
  };
103
- }
104
- finally {
105
- closeWorkflowDatabase(workflowDb);
106
- }
125
+ });
107
126
  }
108
- export function resumeWorkflowRun(runId) {
109
- const workflowDb = openWorkflowDatabase();
110
- try {
111
- const run = readWorkflowRun(workflowDb, runId);
127
+ export async function resumeWorkflowRun(runId) {
128
+ return withWorkflowDb((db) => {
129
+ const run = readWorkflowRun(db, runId);
112
130
  if (run.status === "completed") {
113
131
  throw new UsageError(`Workflow run ${run.id} is already completed and cannot be resumed.`);
114
132
  }
115
133
  if (run.status === "active") {
116
- const steps = readWorkflowRunSteps(workflowDb, run.id);
134
+ const steps = readWorkflowRunSteps(db, run.id);
117
135
  return buildWorkflowRunDetail(run, steps);
118
136
  }
119
137
  // blocked or failed → flip back to active and re-open the current step so
120
138
  // it can be reclassified (completed, failed, skipped) after resuming.
121
139
  const now = new Date().toISOString();
122
- workflowDb.transaction(() => {
140
+ db.transaction(() => {
123
141
  if (run.current_step_id) {
124
- workflowDb
125
- .prepare(`UPDATE workflow_run_steps
142
+ db.prepare(`UPDATE workflow_run_steps
126
143
  SET status = 'pending', notes = NULL, evidence_json = NULL, completed_at = NULL
127
- WHERE run_id = ? AND step_id = ? AND status IN ('blocked', 'failed')`)
128
- .run(run.id, run.current_step_id);
144
+ WHERE run_id = ? AND step_id = ? AND status IN ('blocked', 'failed')`).run(run.id, run.current_step_id);
129
145
  }
130
- workflowDb.prepare("UPDATE workflow_runs SET status = 'active', updated_at = ? WHERE id = ?").run(now, run.id);
146
+ db.prepare("UPDATE workflow_runs SET status = 'active', updated_at = ? WHERE id = ?").run(now, run.id);
131
147
  })();
132
148
  const updated = { ...run, status: "active", updated_at: now };
133
- const steps = readWorkflowRunSteps(workflowDb, run.id);
149
+ const steps = readWorkflowRunSteps(db, run.id);
134
150
  return buildWorkflowRunDetail(updated, steps);
135
- }
136
- finally {
137
- closeWorkflowDatabase(workflowDb);
138
- }
151
+ });
139
152
  }
140
- export function completeWorkflowStep(input) {
141
- const workflowDb = openWorkflowDatabase();
142
- try {
153
+ export async function completeWorkflowStep(input) {
154
+ return withWorkflowDb((db) => {
143
155
  let updatedRun;
144
156
  let refreshedSteps = [];
145
- workflowDb.transaction(() => {
146
- const run = readWorkflowRun(workflowDb, input.runId);
157
+ db.transaction(() => {
158
+ const run = readWorkflowRun(db, input.runId);
147
159
  if (run.status !== "active") {
148
160
  throw new UsageError(`Workflow run ${run.id} is ${run.status} and cannot be updated.`);
149
161
  }
150
- const existing = workflowDb
162
+ const existing = db
151
163
  .prepare("SELECT * FROM workflow_run_steps WHERE run_id = ? AND step_id = ?")
152
164
  .get(run.id, input.stepId);
153
165
  if (!existing) {
@@ -160,18 +172,14 @@ export function completeWorkflowStep(input) {
160
172
  throw new UsageError(`Step "${input.stepId}" is not the current step for workflow run ${run.id}. Complete "${run.current_step_id}" first.`);
161
173
  }
162
174
  const completedAt = new Date().toISOString();
163
- workflowDb
164
- .prepare(`UPDATE workflow_run_steps
175
+ db.prepare(`UPDATE workflow_run_steps
165
176
  SET status = ?, notes = ?, evidence_json = ?, completed_at = ?
166
- WHERE run_id = ? AND step_id = ?`)
167
- .run(input.status, input.notes?.trim() || null, input.evidence ? JSON.stringify(input.evidence) : null, completedAt, run.id, input.stepId);
168
- refreshedSteps = readWorkflowRunSteps(workflowDb, run.id);
177
+ WHERE run_id = ? AND step_id = ?`).run(input.status, input.notes?.trim() || null, input.evidence ? JSON.stringify(input.evidence) : null, completedAt, run.id, input.stepId);
178
+ refreshedSteps = readWorkflowRunSteps(db, run.id);
169
179
  const state = deriveRunState(refreshedSteps);
170
- workflowDb
171
- .prepare(`UPDATE workflow_runs
180
+ db.prepare(`UPDATE workflow_runs
172
181
  SET status = ?, current_step_id = ?, updated_at = ?, completed_at = ?
173
- WHERE id = ?`)
174
- .run(state.status, state.currentStepId, completedAt, state.completedAt, run.id);
182
+ WHERE id = ?`).run(state.status, state.currentStepId, completedAt, state.completedAt, run.id);
175
183
  updatedRun = {
176
184
  ...run,
177
185
  status: state.status,
@@ -190,10 +198,7 @@ export function completeWorkflowStep(input) {
190
198
  appendEvent({ eventType: "workflow_finished", ref: detail.run.workflowRef, metadata: { runId: input.runId } });
191
199
  }
192
200
  return detail;
193
- }
194
- finally {
195
- closeWorkflowDatabase(workflowDb);
196
- }
201
+ });
197
202
  }
198
203
  async function resolveRunSpecifier(db, specifier, params) {
199
204
  const explicitRun = db.prepare("SELECT * FROM workflow_runs WHERE id = ?").get(specifier);
@@ -211,9 +216,10 @@ async function resolveRunSpecifier(db, specifier, params) {
211
216
  throw new UsageError(`Expected a workflow ref or workflow run id, got "${specifier}".`);
212
217
  }
213
218
  const ref = `${parsed.origin ? `${parsed.origin}//` : ""}workflow:${parsed.name}`;
219
+ const scopeKey = getCurrentWorkflowScopeKey();
214
220
  const active = db
215
- .prepare("SELECT * FROM workflow_runs WHERE workflow_ref = ? AND status = 'active' ORDER BY updated_at DESC LIMIT 1")
216
- .get(ref);
221
+ .prepare("SELECT * FROM workflow_runs WHERE workflow_ref = ? AND scope_key = ? AND status = 'active' ORDER BY updated_at DESC LIMIT 1")
222
+ .get(ref, scopeKey);
217
223
  if (active) {
218
224
  if (params && Object.keys(params).length > 0) {
219
225
  throw new UsageError(`--params can only be set on a new run; ${ref} already has an active run`);
@@ -246,7 +252,7 @@ async function loadWorkflowAsset(ref) {
246
252
  if (!assetPath) {
247
253
  throw new NotFoundError(`Workflow not found for ref: workflow:${parsed.name}`);
248
254
  }
249
- const resolvedSourcePath = sourcePath ?? loadConfig().stashDir ?? assetPath;
255
+ const resolvedSourcePath = sourcePath ?? config.stashDir ?? assetPath;
250
256
  const fullRef = `${parsed.origin ? `${parsed.origin}//` : ""}workflow:${parsed.name}`;
251
257
  const cached = readWorkflowDocumentFromIndex(resolvedSourcePath, fullRef);
252
258
  const document = cached ?? loadWorkflowDocumentFromDisk(assetPath);
@@ -358,6 +364,7 @@ function toWorkflowRunSummary(run) {
358
364
  return {
359
365
  id: run.id,
360
366
  workflowRef: run.workflow_ref,
367
+ scopeKey: run.scope_key,
361
368
  workflowEntryId: run.workflow_entry_id,
362
369
  workflowTitle: run.workflow_title,
363
370
  status: run.status,
@@ -435,18 +442,13 @@ function parseJsonArray(value) {
435
442
  }
436
443
  return undefined;
437
444
  }
438
- export function getActiveWorkflowRun() {
439
- try {
440
- const workflowDb = openWorkflowDatabase();
441
- const row = workflowDb
442
- .query("SELECT id, current_step_id, workflow_ref FROM workflow_runs WHERE status IN ('active', 'blocked') ORDER BY updated_at DESC LIMIT 1")
443
- .get();
444
- closeWorkflowDatabase(workflowDb);
445
+ export async function getActiveWorkflowRun(scopeKey = getCurrentWorkflowScopeKey()) {
446
+ return withWorkflowDb((db) => {
447
+ const row = db
448
+ .query("SELECT id, current_step_id, workflow_ref FROM workflow_runs WHERE scope_key = ? AND status IN ('active', 'blocked') ORDER BY updated_at DESC LIMIT 1")
449
+ .get(scopeKey);
445
450
  if (!row)
446
451
  return null;
447
452
  return { runId: row.id, stepId: row.current_step_id, workflowRef: row.workflow_ref };
448
- }
449
- catch {
450
- return null; // fail-open: never crash show output due to DB error
451
- }
453
+ }).catch(() => null); // fail-open: never crash show output due to DB error
452
454
  }
@@ -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
  * Validated JSON shape for a workflow asset.
3
6
  *
@@ -0,0 +1,79 @@
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
+ import { createHash } from "node:crypto";
5
+ import fs from "node:fs";
6
+ import path from "node:path";
7
+ import { isWithin, resolveStashDir, safeRealpath, toPosix } from "../core/common";
8
+ const PROJECT_CONFIG_RELATIVE_PATH = path.join(".akm", "config.json");
9
+ export function getCurrentWorkflowScopeKey() {
10
+ const anchor = resolveWorkflowScopeAnchor(process.cwd());
11
+ const normalized = normalizeScopePath(anchor);
12
+ const digest = createHash("sha256").update(normalized).digest("hex");
13
+ return `dir:v1:${digest}`;
14
+ }
15
+ export function resolveWorkflowScopeAnchor(startDir) {
16
+ const cwd = safeRealpath(startDir);
17
+ const projectRoot = findNearestProjectConfigRoot(cwd);
18
+ if (projectRoot)
19
+ return projectRoot;
20
+ const gitRoot = findNearestGitRoot(cwd);
21
+ if (gitRoot)
22
+ return gitRoot;
23
+ try {
24
+ const stashDir = safeRealpath(resolveStashDir({ readOnly: true }));
25
+ if (isWithin(cwd, stashDir))
26
+ return stashDir;
27
+ }
28
+ catch {
29
+ // Ignore stash resolution failures and fall back to cwd.
30
+ }
31
+ return cwd;
32
+ }
33
+ function findNearestProjectConfigRoot(startDir) {
34
+ let currentDir = startDir;
35
+ while (true) {
36
+ const configPath = path.join(currentDir, PROJECT_CONFIG_RELATIVE_PATH);
37
+ if (isFile(configPath)) {
38
+ return safeRealpath(currentDir);
39
+ }
40
+ const parentDir = path.dirname(currentDir);
41
+ if (parentDir === currentDir)
42
+ return null;
43
+ currentDir = parentDir;
44
+ }
45
+ }
46
+ function findNearestGitRoot(startDir) {
47
+ let currentDir = startDir;
48
+ while (true) {
49
+ const gitPath = path.join(currentDir, ".git");
50
+ if (exists(gitPath)) {
51
+ return safeRealpath(currentDir);
52
+ }
53
+ const parentDir = path.dirname(currentDir);
54
+ if (parentDir === currentDir)
55
+ return null;
56
+ currentDir = parentDir;
57
+ }
58
+ }
59
+ function exists(candidate) {
60
+ try {
61
+ fs.accessSync(candidate, fs.constants.F_OK);
62
+ return true;
63
+ }
64
+ catch {
65
+ return false;
66
+ }
67
+ }
68
+ function isFile(candidate) {
69
+ try {
70
+ return fs.statSync(candidate).isFile();
71
+ }
72
+ catch {
73
+ return false;
74
+ }
75
+ }
76
+ function normalizeScopePath(value) {
77
+ const posix = toPosix(value);
78
+ return process.platform === "win32" ? posix.toLowerCase() : posix;
79
+ }
@@ -1,12 +1,8 @@
1
- /**
2
- * Cross-cutting semantic checks over an assembled WorkflowDocument draft.
3
- *
4
- * The parser handles per-line shape checks; this module runs rules that need
5
- * the whole document or the raw frontmatter at once: duplicate step IDs,
6
- * step-id format, and the frontmatter key whitelist.
7
- */
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/.
8
4
  const STEP_ID_REGEX = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
9
- const ALLOWED_FRONTMATTER_KEYS = new Set(["description", "tags", "params"]);
5
+ const ALLOWED_FRONTMATTER_KEYS = new Set(["description", "tags", "params", "name", "updated"]);
10
6
  export function runSemanticChecks(draft, frontmatterData, frontmatterEndLine, errors) {
11
7
  checkFrontmatterKeys(frontmatterData, frontmatterEndLine, errors);
12
8
  checkStepIdFormat(draft, errors);
@@ -0,0 +1,24 @@
1
+ ---
2
+ description: Describe what this workflow accomplishes
3
+ tags:
4
+ - example
5
+ params:
6
+ example_param: Explain this parameter
7
+ ---
8
+
9
+ # Workflow: {{TITLE}}
10
+
11
+ ## Step: {{FIRST_STEP_TITLE}}
12
+ Step ID: {{FIRST_STEP_ID}}
13
+
14
+ ### Instructions
15
+ Describe what to do in this step.
16
+
17
+ ### Completion Criteria
18
+ - Confirm the first step is complete
19
+
20
+ ## Step: Second Step
21
+ Step ID: second-step
22
+
23
+ ### Instructions
24
+ Describe what happens next.
package/docs/README.md CHANGED
@@ -10,8 +10,9 @@
10
10
 
11
11
  ## Upgrading
12
12
 
13
+ - [Roadmap](roadmap.md) -- High-level focus for the 0.9 and 1.0 releases
13
14
  - [v1 migration guide](migration/v1.md) -- The path from 0.x to v1.0, including the `.stash.json` removal scheduled for v0.8.0
14
- - [Release notes (latest: 0.7.0)](migration/release-notes/0.7.0.md) -- Per-release notes drop into `migration/release-notes/`, including current pre-release removals
15
+ - [Release notes (latest: 0.8.0)](migration/release-notes/0.8.0.md) -- Per-release notes drop into `migration/release-notes/`, including current pre-release removals
15
16
  - [v0.5 → v0.6 migration guide](migration/v0.5-to-v0.6.md) -- Every breaking change with before/after code, publisher checklist, and troubleshooting
16
17
 
17
18
  ## Reference
@@ -28,17 +29,24 @@
28
29
  - [itlackey/akm-plugins](https://github.com/itlackey/akm-plugins) -- optional integrations for tools like OpenCode
29
30
  - [itlackey/akm-bench](https://github.com/itlackey/akm-bench) -- the standalone benchmark and evaluation repo for akm
30
31
 
32
+ ## Operations
33
+
34
+ - Analyzing `akm improve` runs -- use [`akm health`](../src/commands/health.ts) (0.8.0+): `--since`, `--detail per-run`, `--window-compare`, `--windows`. See [health-command-enhancements.md](technical/health-command-enhancements.md).
35
+
31
36
  ## Internals
32
37
 
33
38
  - [Search](technical/search.md) -- Hybrid search architecture and scoring
34
39
  - [Indexing](technical/indexing.md) -- How the search index is built
35
40
  - [Classification](technical/classification.md) -- Matcher and renderer behavior
41
+ - [Functional Contract Patterns](technical/functional-contract-patterns.md) -- Quick reference for contributor pipelines and small process contracts
42
+ - [Implementation Plan: Functional Contract Refactor](technical/implementation-plan-functional-contract-refactor.md) -- Phased plan to move behavior from type-centric switchboards to process-local contributors
43
+ - [Architecture Cleanup Checklist](technical/architecture-cleanup-checklist.md) -- Living checklist for executing the cleanup plan with parity gates, reviews, and git hygiene
36
44
  - [Show Response](technical/show-response.md) -- `akm show` output fields by asset type
37
45
  - [Testing Workflow](technical/testing-workflow.md) -- End-to-end, Docker, deployment, and upgrade validation
38
46
  - [Ref Format](technical/ref.md) -- Wire format for asset references
39
47
  - [Test Coverage Guide](technical/test-coverage-guide.md) -- High-value testing areas
40
48
  - [Core Principles](technical/akm-core-principles.md) -- Design principles and constraints
41
- - [akm-bench](technical/benchmark.md) -- Search-quality benchmark suite
49
+ - `technical/benchmark.md` (planned) -- Search-quality benchmark suite
42
50
 
43
51
  ## Posts
44
52