akm-cli 0.7.5 → 0.8.0-rc.6

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 (236) hide show
  1. package/{.github/CHANGELOG.md → CHANGELOG.md} +113 -2
  2. package/README.md +20 -4
  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.js +1995 -551
  9. package/dist/commands/agent-dispatch.js +110 -0
  10. package/dist/commands/agent-support.js +68 -0
  11. package/dist/commands/completions.js +3 -0
  12. package/dist/commands/config-cli.js +130 -534
  13. package/dist/commands/consolidate.js +1531 -0
  14. package/dist/commands/curate.js +44 -3
  15. package/dist/commands/db-cli.js +23 -0
  16. package/dist/commands/distill-promotion-policy.js +660 -0
  17. package/dist/commands/distill.js +990 -75
  18. package/dist/commands/eval-cases.js +43 -0
  19. package/dist/commands/events.js +5 -23
  20. package/dist/commands/graph.js +477 -0
  21. package/dist/commands/health.js +400 -0
  22. package/dist/commands/help/help-accept.md +9 -0
  23. package/dist/commands/help/help-improve.md +77 -0
  24. package/dist/commands/help/help-proposals.md +15 -0
  25. package/dist/commands/help/help-propose.md +17 -0
  26. package/dist/commands/help/help-reject.md +8 -0
  27. package/dist/commands/history.js +54 -46
  28. package/dist/commands/improve-profiles.js +146 -0
  29. package/dist/commands/improve-result-file.js +103 -0
  30. package/dist/commands/improve.js +2175 -0
  31. package/dist/commands/info.js +5 -2
  32. package/dist/commands/init.js +50 -2
  33. package/dist/commands/installed-stashes.js +102 -139
  34. package/dist/commands/knowledge.js +136 -0
  35. package/dist/commands/lint/agent-linter.js +49 -0
  36. package/dist/commands/lint/base-linter.js +479 -0
  37. package/dist/commands/lint/command-linter.js +49 -0
  38. package/dist/commands/lint/default-linter.js +16 -0
  39. package/dist/commands/lint/index.js +183 -0
  40. package/dist/commands/lint/knowledge-linter.js +16 -0
  41. package/dist/commands/lint/markdown-insertion.js +343 -0
  42. package/dist/commands/lint/memory-linter.js +61 -0
  43. package/dist/commands/lint/registry.js +36 -0
  44. package/dist/commands/lint/skill-linter.js +45 -0
  45. package/dist/commands/lint/task-linter.js +50 -0
  46. package/dist/commands/lint/types.js +4 -0
  47. package/dist/commands/lint/vault-key-rules.js +139 -0
  48. package/dist/commands/lint/workflow-linter.js +56 -0
  49. package/dist/commands/lint.js +4 -0
  50. package/dist/commands/migration-help.js +5 -2
  51. package/dist/commands/proposal.js +66 -12
  52. package/dist/commands/propose.js +86 -31
  53. package/dist/commands/reflect.js +1119 -73
  54. package/dist/commands/registry-search.js +5 -2
  55. package/dist/commands/remember.js +69 -6
  56. package/dist/commands/schema-repair.js +203 -0
  57. package/dist/commands/search.js +115 -14
  58. package/dist/commands/self-update.js +3 -0
  59. package/dist/commands/show.js +144 -25
  60. package/dist/commands/source-add.js +17 -45
  61. package/dist/commands/source-clone.js +3 -0
  62. package/dist/commands/source-manage.js +14 -19
  63. package/dist/commands/tasks.js +438 -0
  64. package/dist/commands/url-checker.js +42 -0
  65. package/dist/commands/vault.js +130 -77
  66. package/dist/core/action-contributors.js +28 -0
  67. package/dist/core/asset-ref.js +7 -0
  68. package/dist/core/asset-registry.js +7 -16
  69. package/dist/core/asset-serialize.js +88 -0
  70. package/dist/core/asset-spec.js +22 -0
  71. package/dist/core/common.js +157 -0
  72. package/dist/core/concurrent.js +25 -0
  73. package/dist/core/config-io.js +347 -0
  74. package/dist/core/config-migration.js +625 -0
  75. package/dist/core/config-schema.js +501 -0
  76. package/dist/core/config-sources.js +108 -0
  77. package/dist/core/config-types.js +4 -0
  78. package/dist/core/config-walker.js +337 -0
  79. package/dist/core/config.js +327 -987
  80. package/dist/core/errors.js +40 -19
  81. package/dist/core/events.js +91 -138
  82. package/dist/core/file-lock.js +104 -0
  83. package/dist/core/frontmatter.js +3 -6
  84. package/dist/core/lesson-lint.js +3 -0
  85. package/dist/core/markdown.js +20 -0
  86. package/dist/core/memory-belief.js +62 -0
  87. package/dist/core/memory-contradiction-detect.js +274 -0
  88. package/dist/core/memory-improve.js +806 -0
  89. package/dist/core/parse.js +158 -0
  90. package/dist/core/paths.js +326 -14
  91. package/dist/core/proposal-quality-validators.js +364 -0
  92. package/dist/core/proposal-validators.js +69 -0
  93. package/dist/core/proposals.js +498 -42
  94. package/dist/core/state-db.js +927 -0
  95. package/dist/core/text-truncation.js +107 -0
  96. package/dist/core/time.js +54 -0
  97. package/dist/core/warn.js +62 -1
  98. package/dist/core/write-source.js +3 -0
  99. package/dist/indexer/db-backup.js +391 -0
  100. package/dist/indexer/db-search.js +152 -253
  101. package/dist/indexer/db.js +933 -103
  102. package/dist/indexer/ensure-index.js +64 -0
  103. package/dist/indexer/file-context.js +3 -0
  104. package/dist/indexer/graph-boost.js +376 -101
  105. package/dist/indexer/graph-db.js +391 -0
  106. package/dist/indexer/graph-dedup.js +95 -0
  107. package/dist/indexer/graph-extraction.js +550 -124
  108. package/dist/indexer/index-context.js +4 -0
  109. package/dist/indexer/indexer.js +506 -291
  110. package/dist/indexer/llm-cache.js +47 -0
  111. package/dist/indexer/manifest.js +3 -0
  112. package/dist/indexer/matchers.js +148 -160
  113. package/dist/indexer/memory-inference.js +99 -74
  114. package/dist/indexer/metadata-contributors.js +29 -0
  115. package/dist/indexer/metadata.js +255 -196
  116. package/dist/indexer/path-resolver.js +92 -0
  117. package/dist/indexer/project-context.js +192 -0
  118. package/dist/indexer/ranking-contributors.js +331 -0
  119. package/dist/indexer/ranking.js +81 -0
  120. package/dist/indexer/search-fields.js +5 -9
  121. package/dist/indexer/search-hit-enrichers.js +111 -0
  122. package/dist/indexer/search-source.js +44 -10
  123. package/dist/indexer/semantic-status.js +5 -16
  124. package/dist/indexer/staleness-detect.js +447 -0
  125. package/dist/indexer/usage-events.js +12 -9
  126. package/dist/indexer/walker.js +28 -0
  127. package/dist/integrations/agent/builders.js +135 -0
  128. package/dist/integrations/agent/config.js +122 -230
  129. package/dist/integrations/agent/detect.js +3 -0
  130. package/dist/integrations/agent/index.js +7 -13
  131. package/dist/integrations/agent/model-aliases.js +55 -0
  132. package/dist/integrations/agent/profiles.js +70 -5
  133. package/dist/integrations/agent/prompts.js +150 -74
  134. package/dist/integrations/agent/runner.js +151 -0
  135. package/dist/integrations/agent/sdk-runner.js +126 -0
  136. package/dist/integrations/agent/spawn.js +118 -23
  137. package/dist/integrations/github.js +3 -0
  138. package/dist/integrations/lockfile.js +32 -69
  139. package/dist/integrations/session-logs/index.js +68 -0
  140. package/dist/integrations/session-logs/providers/claude-code.js +59 -0
  141. package/dist/integrations/session-logs/providers/opencode.js +55 -0
  142. package/dist/integrations/session-logs/types.js +4 -0
  143. package/dist/llm/call-ai.js +62 -0
  144. package/dist/llm/client.js +72 -124
  145. package/dist/llm/embedder.js +3 -19
  146. package/dist/llm/embedders/cache.js +3 -7
  147. package/dist/llm/embedders/local.js +3 -0
  148. package/dist/llm/embedders/remote.js +20 -8
  149. package/dist/llm/embedders/types.js +3 -7
  150. package/dist/llm/feature-gate.js +89 -48
  151. package/dist/llm/graph-extract.js +676 -70
  152. package/dist/llm/index-passes.js +9 -23
  153. package/dist/llm/memory-infer.js +52 -71
  154. package/dist/llm/metadata-enhance.js +42 -29
  155. package/dist/llm/prompts/graph-extract-user-prompt.md +35 -0
  156. package/dist/output/cli-hints-full.md +281 -0
  157. package/dist/output/cli-hints-short.md +65 -0
  158. package/dist/output/cli-hints.js +5 -318
  159. package/dist/output/context.js +3 -0
  160. package/dist/output/renderers.js +223 -256
  161. package/dist/output/shapes.js +150 -105
  162. package/dist/output/text.js +318 -30
  163. package/dist/registry/build-index.js +3 -0
  164. package/dist/registry/create-provider-registry.js +3 -0
  165. package/dist/registry/factory.js +3 -0
  166. package/dist/registry/origin-resolve.js +3 -0
  167. package/dist/registry/providers/index.js +3 -0
  168. package/dist/registry/providers/skills-sh.js +70 -49
  169. package/dist/registry/providers/static-index.js +53 -48
  170. package/dist/registry/providers/types.js +3 -24
  171. package/dist/registry/resolve.js +11 -16
  172. package/dist/registry/types.js +3 -0
  173. package/dist/scripts/migrate-storage.js +17307 -0
  174. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +8900 -0
  175. package/dist/scripts/migrations/v16-to-v17.js +141 -0
  176. package/dist/setup/detect.js +3 -0
  177. package/dist/setup/ripgrep-install.js +3 -0
  178. package/dist/setup/ripgrep-resolve.js +3 -0
  179. package/dist/setup/setup.js +775 -37
  180. package/dist/setup/steps.js +3 -15
  181. package/dist/sources/include.js +3 -0
  182. package/dist/sources/provider-factory.js +5 -12
  183. package/dist/sources/provider.js +3 -20
  184. package/dist/sources/providers/filesystem.js +19 -23
  185. package/dist/sources/providers/git.js +7 -5
  186. package/dist/sources/providers/index.js +3 -0
  187. package/dist/sources/providers/install-types.js +3 -13
  188. package/dist/sources/providers/npm.js +3 -4
  189. package/dist/sources/providers/provider-utils.js +3 -0
  190. package/dist/sources/providers/sync-from-ref.js +3 -11
  191. package/dist/sources/providers/tar-utils.js +3 -0
  192. package/dist/sources/providers/website.js +18 -22
  193. package/dist/sources/resolve.js +3 -0
  194. package/dist/sources/types.js +3 -0
  195. package/dist/sources/website-ingest.js +7 -0
  196. package/dist/tasks/backends/cron.js +203 -0
  197. package/dist/tasks/backends/exec-utils.js +28 -0
  198. package/dist/tasks/backends/index.js +24 -0
  199. package/dist/tasks/backends/launchd-template.xml +19 -0
  200. package/dist/tasks/backends/launchd.js +187 -0
  201. package/dist/tasks/backends/schtasks-template.xml +29 -0
  202. package/dist/tasks/backends/schtasks.js +215 -0
  203. package/dist/tasks/parser.js +211 -0
  204. package/dist/tasks/resolveAkmBin.js +87 -0
  205. package/dist/tasks/runner.js +458 -0
  206. package/dist/tasks/schedule.js +211 -0
  207. package/dist/tasks/schema.js +15 -0
  208. package/dist/tasks/validator.js +62 -0
  209. package/dist/version.js +3 -0
  210. package/dist/wiki/index-template.md +12 -0
  211. package/dist/wiki/ingest-workflow-template.md +54 -0
  212. package/dist/wiki/log-template.md +8 -0
  213. package/dist/wiki/schema-template.md +61 -0
  214. package/dist/wiki/wiki-templates.js +15 -0
  215. package/dist/wiki/wiki.js +13 -61
  216. package/dist/workflows/authoring.js +8 -25
  217. package/dist/workflows/cli.js +3 -0
  218. package/dist/workflows/db.js +140 -10
  219. package/dist/workflows/document-cache.js +3 -10
  220. package/dist/workflows/parser.js +3 -0
  221. package/dist/workflows/renderer.js +11 -3
  222. package/dist/workflows/runs.js +62 -91
  223. package/dist/workflows/schema.js +3 -0
  224. package/dist/workflows/scope-key.js +3 -0
  225. package/dist/workflows/validator.js +4 -8
  226. package/dist/workflows/workflow-template.md +24 -0
  227. package/docs/README.md +9 -2
  228. package/docs/data-and-telemetry.md +225 -0
  229. package/docs/migration/release-notes/0.7.0.md +1 -1
  230. package/docs/migration/release-notes/0.7.5.md +2 -2
  231. package/docs/migration/release-notes/0.8.0.md +48 -0
  232. package/docs/migration/v0.7-to-v0.8.md +1307 -0
  233. package/package.json +20 -8
  234. package/.github/LICENSE +0 -374
  235. package/dist/commands/install-audit.js +0 -381
  236. package/dist/templates/wiki-templates.js +0 -100
@@ -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
  *
@@ -8,6 +11,7 @@
8
11
  */
9
12
  import { makeAssetRef } from "../core/asset-ref";
10
13
  import { UsageError } from "../core/errors";
14
+ import { registerMetadataContributor } from "../indexer/metadata-contributors";
11
15
  import { cacheWorkflowDocument } from "./document-cache";
12
16
  import { parseWorkflow } from "./parser";
13
17
  function shellQuote(value) {
@@ -54,8 +58,12 @@ export const workflowMdRenderer = {
54
58
  })),
55
59
  };
56
60
  },
57
- extractMetadata(entry, ctx) {
58
- const doc = loadDocument(ctx);
61
+ };
62
+ registerMetadataContributor({
63
+ name: "workflow-document-metadata",
64
+ appliesTo: ({ rendererName }) => rendererName === "workflow-md",
65
+ contribute(entry, { renderContext }) {
66
+ const doc = loadDocument(renderContext);
59
67
  const hints = new Set(entry.searchHints ?? []);
60
68
  hints.add(doc.title);
61
69
  for (const step of doc.steps) {
@@ -75,4 +83,4 @@ export const workflowMdRenderer = {
75
83
  }
76
84
  cacheWorkflowDocument(entry, doc);
77
85
  },
78
- };
86
+ });
@@ -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";
@@ -13,64 +16,58 @@ import { formatWorkflowErrors } from "./authoring";
13
16
  import { closeWorkflowDatabase, openWorkflowDatabase } from "./db";
14
17
  import { parseWorkflow } from "./parser";
15
18
  import { getCurrentWorkflowScopeKey } from "./scope-key";
19
+ async function withWorkflowDb(fn) {
20
+ const db = openWorkflowDatabase();
21
+ try {
22
+ return await Promise.resolve(fn(db));
23
+ }
24
+ finally {
25
+ closeWorkflowDatabase(db);
26
+ }
27
+ }
16
28
  export async function startWorkflowRun(ref, params = {}) {
17
29
  const asset = await loadWorkflowAsset(ref);
18
- const workflowDb = openWorkflowDatabase();
19
- try {
30
+ return withWorkflowDb(async (db) => {
20
31
  const now = new Date().toISOString();
21
32
  const runId = randomUUID();
22
33
  const scopeKey = getCurrentWorkflowScopeKey();
23
34
  const currentStepId = asset.steps[0]?.id ?? null;
24
35
  const workflowEntryId = resolveWorkflowEntryId(asset.sourcePath, asset.ref);
25
- workflowDb.transaction(() => {
26
- workflowDb
27
- .prepare(`INSERT INTO workflow_runs (
36
+ db.transaction(() => {
37
+ db.prepare(`INSERT INTO workflow_runs (
28
38
  id, workflow_ref, scope_key, workflow_entry_id, workflow_title, status, params_json, current_step_id, created_at, updated_at
29
- ) VALUES (?, ?, ?, ?, ?, 'active', ?, ?, ?, ?)`)
30
- .run(runId, asset.ref, scopeKey, workflowEntryId, asset.title, JSON.stringify(params), currentStepId, now, now);
31
- const insertStep = workflowDb.prepare(`INSERT INTO workflow_run_steps (
39
+ ) VALUES (?, ?, ?, ?, ?, 'active', ?, ?, ?, ?)`).run(runId, asset.ref, scopeKey, workflowEntryId, asset.title, JSON.stringify(params), currentStepId, now, now);
40
+ const insertStep = db.prepare(`INSERT INTO workflow_run_steps (
32
41
  run_id, step_id, step_title, instructions, completion_json, sequence_index, status
33
42
  ) VALUES (?, ?, ?, ?, ?, ?, 'pending')`);
34
43
  for (const step of asset.steps) {
35
44
  insertStep.run(runId, step.id, step.title, step.instructions, step.completionCriteria ? JSON.stringify(step.completionCriteria) : null, step.sequenceIndex ?? 0);
36
45
  }
37
46
  })();
38
- const result = getWorkflowStatus(runId);
47
+ const result = await getWorkflowStatus(runId);
39
48
  appendEvent({
40
49
  eventType: "workflow_started",
41
50
  ref: ref,
42
51
  metadata: { runId: result.run.id, title: result.run.workflowTitle },
43
52
  });
44
53
  return result;
45
- }
46
- finally {
47
- closeWorkflowDatabase(workflowDb);
48
- }
54
+ });
49
55
  }
50
- export function getWorkflowStatus(runId) {
51
- const workflowDb = openWorkflowDatabase();
52
- try {
53
- const run = readWorkflowRun(workflowDb, runId);
54
- const steps = readWorkflowRunSteps(workflowDb, run.id);
56
+ export async function getWorkflowStatus(runId) {
57
+ return withWorkflowDb((db) => {
58
+ const run = readWorkflowRun(db, runId);
59
+ const steps = readWorkflowRunSteps(db, run.id);
55
60
  return buildWorkflowRunDetail(run, steps);
56
- }
57
- finally {
58
- closeWorkflowDatabase(workflowDb);
59
- }
61
+ });
60
62
  }
61
- export function hasWorkflowRun(runId) {
62
- const workflowDb = openWorkflowDatabase();
63
- try {
64
- const row = workflowDb.prepare("SELECT 1 FROM workflow_runs WHERE id = ? LIMIT 1").get(runId);
63
+ export async function hasWorkflowRun(runId) {
64
+ return withWorkflowDb((db) => {
65
+ const row = db.prepare("SELECT 1 FROM workflow_runs WHERE id = ? LIMIT 1").get(runId);
65
66
  return !!row;
66
- }
67
- finally {
68
- closeWorkflowDatabase(workflowDb);
69
- }
67
+ });
70
68
  }
71
- export function listWorkflowRuns(input) {
72
- const workflowDb = openWorkflowDatabase();
73
- try {
69
+ export async function listWorkflowRuns(input) {
70
+ return withWorkflowDb((db) => {
74
71
  const filters = [];
75
72
  const params = [];
76
73
  const scopeKey = getCurrentWorkflowScopeKey();
@@ -88,20 +85,16 @@ export function listWorkflowRuns(input) {
88
85
  filters.push("status IN ('active', 'blocked')");
89
86
  }
90
87
  const where = filters.length > 0 ? `WHERE ${filters.join(" AND ")}` : "";
91
- const rows = workflowDb
88
+ const rows = db
92
89
  .prepare(`SELECT * FROM workflow_runs ${where} ORDER BY updated_at DESC, created_at DESC`)
93
90
  .all(...params);
94
91
  return { runs: rows.map(toWorkflowRunSummary) };
95
- }
96
- finally {
97
- closeWorkflowDatabase(workflowDb);
98
- }
92
+ });
99
93
  }
100
94
  export async function getNextWorkflowStep(specifier, params) {
101
- const workflowDb = openWorkflowDatabase();
102
- try {
103
- const { run, autoStarted } = await resolveRunSpecifier(workflowDb, specifier, params);
104
- const steps = readWorkflowRunSteps(workflowDb, run.id);
95
+ return withWorkflowDb(async (db) => {
96
+ const { run, autoStarted } = await resolveRunSpecifier(db, specifier, params);
97
+ const steps = readWorkflowRunSteps(db, run.id);
105
98
  const currentStep = resolveCurrentStep(run, steps);
106
99
  const done = run.status === "completed" ? true : undefined;
107
100
  return {
@@ -115,54 +108,44 @@ export async function getNextWorkflowStep(specifier, params) {
115
108
  ...(done ? { done } : {}),
116
109
  ...(autoStarted ? { autoStarted } : {}),
117
110
  };
118
- }
119
- finally {
120
- closeWorkflowDatabase(workflowDb);
121
- }
111
+ });
122
112
  }
123
- export function resumeWorkflowRun(runId) {
124
- const workflowDb = openWorkflowDatabase();
125
- try {
126
- const run = readWorkflowRun(workflowDb, runId);
113
+ export async function resumeWorkflowRun(runId) {
114
+ return withWorkflowDb((db) => {
115
+ const run = readWorkflowRun(db, runId);
127
116
  if (run.status === "completed") {
128
117
  throw new UsageError(`Workflow run ${run.id} is already completed and cannot be resumed.`);
129
118
  }
130
119
  if (run.status === "active") {
131
- const steps = readWorkflowRunSteps(workflowDb, run.id);
120
+ const steps = readWorkflowRunSteps(db, run.id);
132
121
  return buildWorkflowRunDetail(run, steps);
133
122
  }
134
123
  // blocked or failed → flip back to active and re-open the current step so
135
124
  // it can be reclassified (completed, failed, skipped) after resuming.
136
125
  const now = new Date().toISOString();
137
- workflowDb.transaction(() => {
126
+ db.transaction(() => {
138
127
  if (run.current_step_id) {
139
- workflowDb
140
- .prepare(`UPDATE workflow_run_steps
128
+ db.prepare(`UPDATE workflow_run_steps
141
129
  SET status = 'pending', notes = NULL, evidence_json = NULL, completed_at = NULL
142
- WHERE run_id = ? AND step_id = ? AND status IN ('blocked', 'failed')`)
143
- .run(run.id, run.current_step_id);
130
+ WHERE run_id = ? AND step_id = ? AND status IN ('blocked', 'failed')`).run(run.id, run.current_step_id);
144
131
  }
145
- workflowDb.prepare("UPDATE workflow_runs SET status = 'active', updated_at = ? WHERE id = ?").run(now, run.id);
132
+ db.prepare("UPDATE workflow_runs SET status = 'active', updated_at = ? WHERE id = ?").run(now, run.id);
146
133
  })();
147
134
  const updated = { ...run, status: "active", updated_at: now };
148
- const steps = readWorkflowRunSteps(workflowDb, run.id);
135
+ const steps = readWorkflowRunSteps(db, run.id);
149
136
  return buildWorkflowRunDetail(updated, steps);
150
- }
151
- finally {
152
- closeWorkflowDatabase(workflowDb);
153
- }
137
+ });
154
138
  }
155
- export function completeWorkflowStep(input) {
156
- const workflowDb = openWorkflowDatabase();
157
- try {
139
+ export async function completeWorkflowStep(input) {
140
+ return withWorkflowDb((db) => {
158
141
  let updatedRun;
159
142
  let refreshedSteps = [];
160
- workflowDb.transaction(() => {
161
- const run = readWorkflowRun(workflowDb, input.runId);
143
+ db.transaction(() => {
144
+ const run = readWorkflowRun(db, input.runId);
162
145
  if (run.status !== "active") {
163
146
  throw new UsageError(`Workflow run ${run.id} is ${run.status} and cannot be updated.`);
164
147
  }
165
- const existing = workflowDb
148
+ const existing = db
166
149
  .prepare("SELECT * FROM workflow_run_steps WHERE run_id = ? AND step_id = ?")
167
150
  .get(run.id, input.stepId);
168
151
  if (!existing) {
@@ -175,18 +158,14 @@ export function completeWorkflowStep(input) {
175
158
  throw new UsageError(`Step "${input.stepId}" is not the current step for workflow run ${run.id}. Complete "${run.current_step_id}" first.`);
176
159
  }
177
160
  const completedAt = new Date().toISOString();
178
- workflowDb
179
- .prepare(`UPDATE workflow_run_steps
161
+ db.prepare(`UPDATE workflow_run_steps
180
162
  SET status = ?, notes = ?, evidence_json = ?, completed_at = ?
181
- WHERE run_id = ? AND step_id = ?`)
182
- .run(input.status, input.notes?.trim() || null, input.evidence ? JSON.stringify(input.evidence) : null, completedAt, run.id, input.stepId);
183
- refreshedSteps = readWorkflowRunSteps(workflowDb, run.id);
163
+ 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);
164
+ refreshedSteps = readWorkflowRunSteps(db, run.id);
184
165
  const state = deriveRunState(refreshedSteps);
185
- workflowDb
186
- .prepare(`UPDATE workflow_runs
166
+ db.prepare(`UPDATE workflow_runs
187
167
  SET status = ?, current_step_id = ?, updated_at = ?, completed_at = ?
188
- WHERE id = ?`)
189
- .run(state.status, state.currentStepId, completedAt, state.completedAt, run.id);
168
+ WHERE id = ?`).run(state.status, state.currentStepId, completedAt, state.completedAt, run.id);
190
169
  updatedRun = {
191
170
  ...run,
192
171
  status: state.status,
@@ -205,10 +184,7 @@ export function completeWorkflowStep(input) {
205
184
  appendEvent({ eventType: "workflow_finished", ref: detail.run.workflowRef, metadata: { runId: input.runId } });
206
185
  }
207
186
  return detail;
208
- }
209
- finally {
210
- closeWorkflowDatabase(workflowDb);
211
- }
187
+ });
212
188
  }
213
189
  async function resolveRunSpecifier(db, specifier, params) {
214
190
  const explicitRun = db.prepare("SELECT * FROM workflow_runs WHERE id = ?").get(specifier);
@@ -262,7 +238,7 @@ async function loadWorkflowAsset(ref) {
262
238
  if (!assetPath) {
263
239
  throw new NotFoundError(`Workflow not found for ref: workflow:${parsed.name}`);
264
240
  }
265
- const resolvedSourcePath = sourcePath ?? loadConfig().stashDir ?? assetPath;
241
+ const resolvedSourcePath = sourcePath ?? config.stashDir ?? assetPath;
266
242
  const fullRef = `${parsed.origin ? `${parsed.origin}//` : ""}workflow:${parsed.name}`;
267
243
  const cached = readWorkflowDocumentFromIndex(resolvedSourcePath, fullRef);
268
244
  const document = cached ?? loadWorkflowDocumentFromDisk(assetPath);
@@ -452,18 +428,13 @@ function parseJsonArray(value) {
452
428
  }
453
429
  return undefined;
454
430
  }
455
- export function getActiveWorkflowRun(scopeKey = getCurrentWorkflowScopeKey()) {
456
- try {
457
- const workflowDb = openWorkflowDatabase();
458
- const row = workflowDb
431
+ export async function getActiveWorkflowRun(scopeKey = getCurrentWorkflowScopeKey()) {
432
+ return withWorkflowDb((db) => {
433
+ const row = db
459
434
  .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")
460
435
  .get(scopeKey);
461
- closeWorkflowDatabase(workflowDb);
462
436
  if (!row)
463
437
  return null;
464
438
  return { runId: row.id, stepId: row.current_step_id, workflowRef: row.workflow_ref };
465
- }
466
- catch {
467
- return null; // fail-open: never crash show output due to DB error
468
- }
439
+ }).catch(() => null); // fail-open: never crash show output due to DB error
469
440
  }
@@ -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
  *
@@ -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 { createHash } from "node:crypto";
2
5
  import fs from "node:fs";
3
6
  import path from "node:path";
@@ -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
@@ -11,7 +11,7 @@
11
11
  ## Upgrading
12
12
 
13
13
  - [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
14
+ - [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
15
  - [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
16
 
17
17
  ## Reference
@@ -28,17 +28,24 @@
28
28
  - [itlackey/akm-plugins](https://github.com/itlackey/akm-plugins) -- optional integrations for tools like OpenCode
29
29
  - [itlackey/akm-bench](https://github.com/itlackey/akm-bench) -- the standalone benchmark and evaluation repo for akm
30
30
 
31
+ ## Operations
32
+
33
+ - [Improve Stats](improve-stats.md) -- Analyze `akm improve` run logs with the `scripts/improve-stats/` toolkit (runs-trend, run-show, actions-breakdown, lint-current)
34
+
31
35
  ## Internals
32
36
 
33
37
  - [Search](technical/search.md) -- Hybrid search architecture and scoring
34
38
  - [Indexing](technical/indexing.md) -- How the search index is built
35
39
  - [Classification](technical/classification.md) -- Matcher and renderer behavior
40
+ - [Functional Contract Patterns](technical/functional-contract-patterns.md) -- Quick reference for contributor pipelines and small process contracts
41
+ - [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
42
+ - [Architecture Cleanup Checklist](technical/architecture-cleanup-checklist.md) -- Living checklist for executing the cleanup plan with parity gates, reviews, and git hygiene
36
43
  - [Show Response](technical/show-response.md) -- `akm show` output fields by asset type
37
44
  - [Testing Workflow](technical/testing-workflow.md) -- End-to-end, Docker, deployment, and upgrade validation
38
45
  - [Ref Format](technical/ref.md) -- Wire format for asset references
39
46
  - [Test Coverage Guide](technical/test-coverage-guide.md) -- High-value testing areas
40
47
  - [Core Principles](technical/akm-core-principles.md) -- Design principles and constraints
41
- - [akm-bench](technical/benchmark.md) -- Search-quality benchmark suite
48
+ - `technical/benchmark.md` (planned) -- Search-quality benchmark suite
42
49
 
43
50
  ## Posts
44
51