akm-cli 0.6.1 → 0.7.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 (333) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/dist/{cli.js → src/cli.js} +712 -34
  3. package/dist/{commands → src/commands}/config-cli.js +47 -4
  4. package/dist/src/commands/distill.js +283 -0
  5. package/dist/src/commands/events.js +108 -0
  6. package/dist/src/commands/history.js +191 -0
  7. package/dist/{commands → src/commands}/installed-stashes.js +1 -1
  8. package/dist/src/commands/proposal.js +119 -0
  9. package/dist/src/commands/propose.js +171 -0
  10. package/dist/src/commands/reflect.js +193 -0
  11. package/dist/{commands → src/commands}/registry-search.js +71 -7
  12. package/dist/{commands → src/commands}/remember.js +12 -0
  13. package/dist/{commands → src/commands}/search.js +104 -4
  14. package/dist/{commands → src/commands}/self-update.js +4 -3
  15. package/dist/{commands → src/commands}/show.js +73 -0
  16. package/dist/{commands → src/commands}/source-add.js +5 -1
  17. package/dist/{commands → src/commands}/source-manage.js +7 -1
  18. package/dist/{core → src/core}/asset-ref.js +5 -5
  19. package/dist/{core → src/core}/asset-spec.js +12 -0
  20. package/dist/{core → src/core}/common.js +1 -1
  21. package/dist/{core → src/core}/config.js +203 -121
  22. package/dist/{core → src/core}/errors.js +4 -0
  23. package/dist/src/core/events.js +239 -0
  24. package/dist/src/core/lesson-lint.js +86 -0
  25. package/dist/src/core/proposals.js +406 -0
  26. package/dist/src/core/warn.js +72 -0
  27. package/dist/{core → src/core}/write-source.js +80 -5
  28. package/dist/{indexer → src/indexer}/db-search.js +114 -24
  29. package/dist/{indexer → src/indexer}/db.js +76 -23
  30. package/dist/{indexer → src/indexer}/file-context.js +0 -3
  31. package/dist/src/indexer/graph-boost.js +179 -0
  32. package/dist/src/indexer/graph-extraction.js +212 -0
  33. package/dist/{indexer → src/indexer}/indexer.js +88 -7
  34. package/dist/{indexer → src/indexer}/matchers.js +1 -1
  35. package/dist/src/indexer/memory-inference.js +263 -0
  36. package/dist/{indexer → src/indexer}/metadata.js +111 -3
  37. package/dist/{indexer → src/indexer}/search-source.js +4 -2
  38. package/dist/src/integrations/agent/config.js +292 -0
  39. package/dist/src/integrations/agent/detect.js +94 -0
  40. package/dist/src/integrations/agent/index.js +17 -0
  41. package/dist/src/integrations/agent/profiles.js +65 -0
  42. package/dist/src/integrations/agent/prompts.js +167 -0
  43. package/dist/src/integrations/agent/spawn.js +272 -0
  44. package/dist/{integrations → src/integrations}/github.js +9 -3
  45. package/dist/{integrations → src/integrations}/lockfile.js +0 -26
  46. package/dist/{llm → src/llm}/client.js +33 -2
  47. package/dist/{llm → src/llm}/embedders/remote.js +37 -3
  48. package/dist/src/llm/feature-gate.js +108 -0
  49. package/dist/src/llm/graph-extract.js +107 -0
  50. package/dist/src/llm/index-passes.js +35 -0
  51. package/dist/src/llm/memory-infer.js +86 -0
  52. package/dist/{output → src/output}/cli-hints.js +15 -2
  53. package/dist/{output → src/output}/renderers.js +63 -2
  54. package/dist/src/output/shapes.js +523 -0
  55. package/dist/src/output/text.js +1116 -0
  56. package/dist/{registry → src/registry}/build-index.js +19 -8
  57. package/dist/{registry → src/registry}/factory.js +0 -8
  58. package/dist/{registry → src/registry}/providers/static-index.js +6 -3
  59. package/dist/{registry → src/registry}/resolve.js +68 -2
  60. package/dist/{setup → src/setup}/setup.js +52 -5
  61. package/dist/{sources → src/sources}/providers/git.js +7 -15
  62. package/dist/{wiki → src/wiki}/wiki.js +54 -6
  63. package/dist/{workflows → src/workflows}/runs.js +37 -3
  64. package/dist/tests/add-website-source.test.js +119 -0
  65. package/dist/tests/agent/agent-config-loader.test.js +70 -0
  66. package/dist/tests/agent/agent-config.test.js +221 -0
  67. package/dist/tests/agent/agent-detect.test.js +100 -0
  68. package/dist/tests/agent/agent-spawn.test.js +234 -0
  69. package/dist/tests/agent-output.test.js +186 -0
  70. package/dist/tests/architecture/agent-no-llm-sdk-guard.test.js +103 -0
  71. package/dist/tests/architecture/agent-spawn-seam.test.js +193 -0
  72. package/dist/tests/architecture/llm-stateless-seam.test.js +112 -0
  73. package/dist/tests/asset-ref.test.js +192 -0
  74. package/dist/tests/asset-registry.test.js +103 -0
  75. package/dist/tests/asset-spec.test.js +241 -0
  76. package/dist/tests/bench/attribution.test.js +996 -0
  77. package/dist/tests/bench/cleanup-sigint.test.js +83 -0
  78. package/dist/tests/bench/cleanup.js +234 -0
  79. package/dist/tests/bench/cleanup.test.js +166 -0
  80. package/dist/tests/bench/cli.js +1018 -0
  81. package/dist/tests/bench/cli.test.js +445 -0
  82. package/dist/tests/bench/compare.test.js +556 -0
  83. package/dist/tests/bench/corpus.js +317 -0
  84. package/dist/tests/bench/corpus.test.js +258 -0
  85. package/dist/tests/bench/doctor.js +525 -0
  86. package/dist/tests/bench/driver.js +401 -0
  87. package/dist/tests/bench/driver.test.js +584 -0
  88. package/dist/tests/bench/environment.js +233 -0
  89. package/dist/tests/bench/environment.test.js +199 -0
  90. package/dist/tests/bench/evolve-metrics.js +179 -0
  91. package/dist/tests/bench/evolve-metrics.test.js +187 -0
  92. package/dist/tests/bench/evolve.js +647 -0
  93. package/dist/tests/bench/evolve.test.js +624 -0
  94. package/dist/tests/bench/failure-modes.test.js +349 -0
  95. package/dist/tests/bench/feedback-integrity.test.js +457 -0
  96. package/dist/tests/bench/leakage.test.js +228 -0
  97. package/dist/tests/bench/learning-curve.test.js +134 -0
  98. package/dist/tests/bench/metrics.js +2395 -0
  99. package/dist/tests/bench/metrics.test.js +1150 -0
  100. package/dist/tests/bench/no-os-tmpdir-invariant.test.js +43 -0
  101. package/dist/tests/bench/opencode-config.js +194 -0
  102. package/dist/tests/bench/opencode-config.test.js +370 -0
  103. package/dist/tests/bench/report.js +1885 -0
  104. package/dist/tests/bench/report.test.js +1038 -0
  105. package/dist/tests/bench/run-config.js +355 -0
  106. package/dist/tests/bench/run-config.test.js +298 -0
  107. package/dist/tests/bench/run-curate-test.js +32 -0
  108. package/dist/tests/bench/run-failing-tasks.js +56 -0
  109. package/dist/tests/bench/run-full-bench.js +51 -0
  110. package/dist/tests/bench/run-items36-targeted.js +69 -0
  111. package/dist/tests/bench/run-nano-quick.js +42 -0
  112. package/dist/tests/bench/run-waveg-targeted.js +62 -0
  113. package/dist/tests/bench/runner.js +699 -0
  114. package/dist/tests/bench/runner.test.js +958 -0
  115. package/dist/tests/bench/search-bridge.test.js +331 -0
  116. package/dist/tests/bench/tmp.js +131 -0
  117. package/dist/tests/bench/trajectory.js +116 -0
  118. package/dist/tests/bench/trajectory.test.js +127 -0
  119. package/dist/tests/bench/verifier.js +114 -0
  120. package/dist/tests/bench/verifier.test.js +118 -0
  121. package/dist/tests/bench/workflow-evaluator.js +557 -0
  122. package/dist/tests/bench/workflow-evaluator.test.js +421 -0
  123. package/dist/tests/bench/workflow-spec.js +345 -0
  124. package/dist/tests/bench/workflow-spec.test.js +363 -0
  125. package/dist/tests/bench/workflow-trace.js +472 -0
  126. package/dist/tests/bench/workflow-trace.test.js +254 -0
  127. package/dist/tests/benchmark-search-quality.js +536 -0
  128. package/dist/tests/benchmark-suite.js +1441 -0
  129. package/dist/tests/capture-cli.test.js +112 -0
  130. package/dist/tests/cli-errors.test.js +204 -0
  131. package/dist/tests/commands/events.test.js +370 -0
  132. package/dist/tests/commands/history.test.js +418 -0
  133. package/dist/tests/commands/import.test.js +103 -0
  134. package/dist/tests/commands/proposal-cli.test.js +209 -0
  135. package/dist/tests/commands/reflect-propose-cli.test.js +333 -0
  136. package/dist/tests/commands/remember.test.js +97 -0
  137. package/dist/tests/commands/scope-flags.test.js +300 -0
  138. package/dist/tests/commands/search.test.js +537 -0
  139. package/dist/tests/commands/show-indexer-parity.test.js +117 -0
  140. package/dist/tests/commands/show.test.js +294 -0
  141. package/dist/tests/common.test.js +266 -0
  142. package/dist/tests/completions.test.js +142 -0
  143. package/dist/tests/config-cli.test.js +193 -0
  144. package/dist/tests/config-llm-features.test.js +139 -0
  145. package/dist/tests/config.test.js +569 -0
  146. package/dist/tests/contracts/migration-baseline.test.js +43 -0
  147. package/dist/tests/contracts/reflect-propose-envelope.test.js +139 -0
  148. package/dist/tests/contracts/spec-helpers.js +46 -0
  149. package/dist/tests/contracts/v1-spec-section-11-proposal-queue.test.js +228 -0
  150. package/dist/tests/contracts/v1-spec-section-12-agent-config.test.js +56 -0
  151. package/dist/tests/contracts/v1-spec-section-13-lesson-type.test.js +34 -0
  152. package/dist/tests/contracts/v1-spec-section-14-llm-features.test.js +94 -0
  153. package/dist/tests/contracts/v1-spec-section-4-1-asset-types.test.js +39 -0
  154. package/dist/tests/contracts/v1-spec-section-4-2-quality-rules.test.js +44 -0
  155. package/dist/tests/contracts/v1-spec-section-5-configuration.test.js +47 -0
  156. package/dist/tests/contracts/v1-spec-section-6-orchestration.test.js +40 -0
  157. package/dist/tests/contracts/v1-spec-section-7-module-layout.test.js +58 -0
  158. package/dist/tests/contracts/v1-spec-section-8-extension-points.test.js +34 -0
  159. package/dist/tests/contracts/v1-spec-section-9-4-cli-surface.test.js +75 -0
  160. package/dist/tests/contracts/v1-spec-section-9-7-llm-agent-boundary.test.js +36 -0
  161. package/dist/tests/core/write-source.test.js +366 -0
  162. package/dist/tests/curate-command.test.js +87 -0
  163. package/dist/tests/db-scoring.test.js +201 -0
  164. package/dist/tests/db.test.js +654 -0
  165. package/dist/tests/distill-cli-flag.test.js +208 -0
  166. package/dist/tests/distill.test.js +515 -0
  167. package/dist/tests/docker-install.test.js +120 -0
  168. package/dist/tests/e2e.test.js +1419 -0
  169. package/dist/tests/embedder.test.js +340 -0
  170. package/dist/tests/embedding-model-config.test.js +379 -0
  171. package/dist/tests/feedback-command.test.js +172 -0
  172. package/dist/tests/file-context.test.js +552 -0
  173. package/dist/tests/fixtures/scripts/git/summarize-diff.js +9 -0
  174. package/dist/tests/fixtures/scripts/lint/eslint-check.js +7 -0
  175. package/dist/tests/fixtures/stashes/load.js +166 -0
  176. package/dist/tests/fixtures/stashes/load.test.js +97 -0
  177. package/dist/tests/fixtures/stashes/ranking-baseline/scripts/mem0-search.js +12 -0
  178. package/dist/tests/frontmatter.test.js +190 -0
  179. package/dist/tests/fts-field-weighting.test.js +254 -0
  180. package/dist/tests/fuzzy-search.test.js +230 -0
  181. package/dist/tests/git-provider-clone.test.js +45 -0
  182. package/dist/tests/github.test.js +161 -0
  183. package/dist/tests/graph-boost-ranking.test.js +305 -0
  184. package/dist/tests/graph-extraction.test.js +282 -0
  185. package/dist/tests/helpers/usage-events.js +8 -0
  186. package/dist/tests/index-pass-llm.test.js +161 -0
  187. package/dist/tests/indexer.test.js +570 -0
  188. package/dist/tests/info-command.test.js +166 -0
  189. package/dist/tests/init.test.js +69 -0
  190. package/dist/tests/install-script.test.js +246 -0
  191. package/dist/tests/integration/agent-real-profile.test.js +94 -0
  192. package/dist/tests/issue-36-repro.test.js +304 -0
  193. package/dist/tests/issues-191-194.test.js +160 -0
  194. package/dist/tests/lesson-lint.test.js +111 -0
  195. package/dist/tests/llm-client.test.js +115 -0
  196. package/dist/tests/llm-feature-gate.test.js +151 -0
  197. package/dist/tests/llm.test.js +139 -0
  198. package/dist/tests/lockfile.test.js +216 -0
  199. package/dist/tests/manifest.test.js +205 -0
  200. package/dist/tests/markdown.test.js +126 -0
  201. package/dist/tests/matchers-unit.test.js +189 -0
  202. package/dist/tests/memory-inference.test.js +299 -0
  203. package/dist/tests/merge-scoring.test.js +136 -0
  204. package/dist/tests/metadata.test.js +313 -0
  205. package/dist/tests/migration-help.test.js +89 -0
  206. package/dist/tests/origin-resolve.test.js +124 -0
  207. package/dist/tests/output-baseline.test.js +218 -0
  208. package/dist/tests/output-shapes-unit.test.js +478 -0
  209. package/dist/tests/parallel-search.test.js +272 -0
  210. package/dist/tests/parameter-metadata.test.js +365 -0
  211. package/dist/tests/paths.test.js +177 -0
  212. package/dist/tests/progressive-disclosure.test.js +280 -0
  213. package/dist/tests/proposals.test.js +279 -0
  214. package/dist/tests/proposed-quality.test.js +271 -0
  215. package/dist/tests/provider-registry.test.js +32 -0
  216. package/dist/tests/ranking-regression.test.js +548 -0
  217. package/dist/tests/reflect-propose.test.js +455 -0
  218. package/dist/tests/registry-build-index.test.js +394 -0
  219. package/dist/tests/registry-cli.test.js +290 -0
  220. package/dist/tests/registry-index-v2.test.js +430 -0
  221. package/dist/tests/registry-install.test.js +728 -0
  222. package/dist/tests/registry-providers/parity.test.js +189 -0
  223. package/dist/tests/registry-providers/skills-sh.test.js +309 -0
  224. package/dist/tests/registry-providers/static-index.test.js +238 -0
  225. package/dist/tests/registry-resolve.test.js +126 -0
  226. package/dist/tests/registry-search.test.js +923 -0
  227. package/dist/tests/remember-frontmatter.test.js +378 -0
  228. package/dist/tests/remember-unit.test.js +123 -0
  229. package/dist/tests/ripgrep-install.test.js +251 -0
  230. package/dist/tests/ripgrep-resolve.test.js +108 -0
  231. package/dist/tests/ripgrep.test.js +163 -0
  232. package/dist/tests/save-command.test.js +94 -0
  233. package/dist/tests/save-trust-qa-fixes.test.js +270 -0
  234. package/dist/tests/scoring-pipeline.test.js +648 -0
  235. package/dist/tests/search-include-proposed-cli.test.js +118 -0
  236. package/dist/tests/self-update.test.js +442 -0
  237. package/dist/tests/semantic-search-e2e.test.js +512 -0
  238. package/dist/tests/semantic-status.test.js +471 -0
  239. package/dist/tests/setup-run.integration.js +877 -0
  240. package/dist/tests/setup-wizard.test.js +198 -0
  241. package/dist/tests/setup.test.js +131 -0
  242. package/dist/tests/source-add.test.js +11 -0
  243. package/dist/tests/source-clone.test.js +254 -0
  244. package/dist/tests/source-manage.test.js +366 -0
  245. package/dist/tests/source-providers/filesystem.test.js +82 -0
  246. package/dist/tests/source-providers/git.test.js +252 -0
  247. package/dist/tests/source-providers/website.test.js +128 -0
  248. package/dist/tests/source-qa-fixes.test.js +286 -0
  249. package/dist/tests/source-registry.test.js +350 -0
  250. package/dist/tests/source-resolve.test.js +100 -0
  251. package/dist/tests/source-source.test.js +281 -0
  252. package/dist/tests/source.test.js +533 -0
  253. package/dist/tests/tar-utils-scan.test.js +73 -0
  254. package/dist/tests/toggle-components.test.js +73 -0
  255. package/dist/tests/usage-telemetry.test.js +265 -0
  256. package/dist/tests/utility-scoring.test.js +558 -0
  257. package/dist/tests/vault-load-error.test.js +78 -0
  258. package/dist/tests/vault-qa-fixes.test.js +194 -0
  259. package/dist/tests/vault.test.js +429 -0
  260. package/dist/tests/vector-search.test.js +608 -0
  261. package/dist/tests/walker.test.js +252 -0
  262. package/dist/tests/wave2-cluster-bc.test.js +228 -0
  263. package/dist/tests/wave2-cluster-d.test.js +180 -0
  264. package/dist/tests/wave2-cluster-e.test.js +179 -0
  265. package/dist/tests/wiki-qa-fixes.test.js +270 -0
  266. package/dist/tests/wiki.test.js +529 -0
  267. package/dist/tests/workflow-cli.test.js +271 -0
  268. package/dist/tests/workflow-markdown.test.js +171 -0
  269. package/dist/tests/workflow-path-escape.test.js +132 -0
  270. package/dist/tests/workflow-qa-fixes.test.js +395 -0
  271. package/dist/tests/workflows/indexer-rejection.test.js +213 -0
  272. package/docs/README.md +8 -0
  273. package/docs/migration/release-notes/0.7.0.md +244 -0
  274. package/package.json +2 -2
  275. package/dist/core/warn.js +0 -27
  276. package/dist/output/shapes.js +0 -212
  277. package/dist/output/text.js +0 -520
  278. /package/dist/{commands → src/commands}/completions.js +0 -0
  279. /package/dist/{commands → src/commands}/curate.js +0 -0
  280. /package/dist/{commands → src/commands}/info.js +0 -0
  281. /package/dist/{commands → src/commands}/init.js +0 -0
  282. /package/dist/{commands → src/commands}/install-audit.js +0 -0
  283. /package/dist/{commands → src/commands}/migration-help.js +0 -0
  284. /package/dist/{commands → src/commands}/source-clone.js +0 -0
  285. /package/dist/{commands → src/commands}/vault.js +0 -0
  286. /package/dist/{core → src/core}/asset-registry.js +0 -0
  287. /package/dist/{core → src/core}/frontmatter.js +0 -0
  288. /package/dist/{core → src/core}/markdown.js +0 -0
  289. /package/dist/{core → src/core}/paths.js +0 -0
  290. /package/dist/{indexer → src/indexer}/manifest.js +0 -0
  291. /package/dist/{indexer → src/indexer}/search-fields.js +0 -0
  292. /package/dist/{indexer → src/indexer}/semantic-status.js +0 -0
  293. /package/dist/{indexer → src/indexer}/usage-events.js +0 -0
  294. /package/dist/{indexer → src/indexer}/walker.js +0 -0
  295. /package/dist/{llm → src/llm}/embedder.js +0 -0
  296. /package/dist/{llm → src/llm}/embedders/cache.js +0 -0
  297. /package/dist/{llm → src/llm}/embedders/local.js +0 -0
  298. /package/dist/{llm → src/llm}/embedders/types.js +0 -0
  299. /package/dist/{llm → src/llm}/metadata-enhance.js +0 -0
  300. /package/dist/{output → src/output}/context.js +0 -0
  301. /package/dist/{registry → src/registry}/create-provider-registry.js +0 -0
  302. /package/dist/{registry → src/registry}/origin-resolve.js +0 -0
  303. /package/dist/{registry → src/registry}/providers/index.js +0 -0
  304. /package/dist/{registry → src/registry}/providers/skills-sh.js +0 -0
  305. /package/dist/{registry → src/registry}/providers/types.js +0 -0
  306. /package/dist/{registry → src/registry}/types.js +0 -0
  307. /package/dist/{setup → src/setup}/detect.js +0 -0
  308. /package/dist/{setup → src/setup}/ripgrep-install.js +0 -0
  309. /package/dist/{setup → src/setup}/ripgrep-resolve.js +0 -0
  310. /package/dist/{setup → src/setup}/steps.js +0 -0
  311. /package/dist/{sources → src/sources}/include.js +0 -0
  312. /package/dist/{sources → src/sources}/provider-factory.js +0 -0
  313. /package/dist/{sources → src/sources}/provider.js +0 -0
  314. /package/dist/{sources → src/sources}/providers/filesystem.js +0 -0
  315. /package/dist/{sources → src/sources}/providers/index.js +0 -0
  316. /package/dist/{sources → src/sources}/providers/install-types.js +0 -0
  317. /package/dist/{sources → src/sources}/providers/npm.js +0 -0
  318. /package/dist/{sources → src/sources}/providers/provider-utils.js +0 -0
  319. /package/dist/{sources → src/sources}/providers/sync-from-ref.js +0 -0
  320. /package/dist/{sources → src/sources}/providers/tar-utils.js +0 -0
  321. /package/dist/{sources → src/sources}/providers/website.js +0 -0
  322. /package/dist/{sources → src/sources}/resolve.js +0 -0
  323. /package/dist/{sources → src/sources}/types.js +0 -0
  324. /package/dist/{templates → src/templates}/wiki-templates.js +0 -0
  325. /package/dist/{version.js → src/version.js} +0 -0
  326. /package/dist/{workflows → src/workflows}/authoring.js +0 -0
  327. /package/dist/{workflows → src/workflows}/cli.js +0 -0
  328. /package/dist/{workflows → src/workflows}/db.js +0 -0
  329. /package/dist/{workflows → src/workflows}/document-cache.js +0 -0
  330. /package/dist/{workflows → src/workflows}/parser.js +0 -0
  331. /package/dist/{workflows → src/workflows}/renderer.js +0 -0
  332. /package/dist/{workflows → src/workflows}/schema.js +0 -0
  333. /package/dist/{workflows → src/workflows}/validator.js +0 -0
@@ -0,0 +1,395 @@
1
+ import { afterEach, describe, expect, test } from "bun:test";
2
+ import { spawnSync } from "node:child_process";
3
+ import fs from "node:fs";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+ const CLI = path.join(__dirname, "..", "src", "cli.ts");
7
+ const tempDirs = [];
8
+ function makeTempDir(prefix) {
9
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
10
+ tempDirs.push(dir);
11
+ return dir;
12
+ }
13
+ function createWorkflowEnv() {
14
+ const stashDir = makeTempDir("akm-wfqa-stash-");
15
+ const xdgCache = makeTempDir("akm-wfqa-cache-");
16
+ const xdgConfig = makeTempDir("akm-wfqa-config-");
17
+ return {
18
+ ...process.env,
19
+ AKM_STASH_DIR: stashDir,
20
+ XDG_CACHE_HOME: xdgCache,
21
+ XDG_CONFIG_HOME: xdgConfig,
22
+ };
23
+ }
24
+ function runCli(args, env) {
25
+ return spawnSync("bun", [CLI, ...args], {
26
+ encoding: "utf8",
27
+ timeout: 30_000,
28
+ env,
29
+ });
30
+ }
31
+ afterEach(() => {
32
+ for (const dir of tempDirs.splice(0)) {
33
+ fs.rmSync(dir, { recursive: true, force: true });
34
+ }
35
+ });
36
+ const TWO_STEP_WORKFLOW = `---
37
+ description: Test workflow
38
+ ---
39
+
40
+ # Workflow: Test Flow
41
+
42
+ ## Step: First Step
43
+ Step ID: first
44
+
45
+ ### Instructions
46
+ Do the first thing.
47
+
48
+ ### Completion Criteria
49
+ - First thing done
50
+
51
+ ## Step: Second Step
52
+ Step ID: second
53
+
54
+ ### Instructions
55
+ Do the second thing.
56
+ `;
57
+ function setupWorkflow(env, name = "test-flow") {
58
+ const sourceDir = makeTempDir("akm-wfqa-src-");
59
+ const sourcePath = path.join(sourceDir, "wf.md");
60
+ fs.writeFileSync(sourcePath, TWO_STEP_WORKFLOW, "utf8");
61
+ const result = runCli(["workflow", "create", name, "--from", sourcePath], env);
62
+ if (result.status !== 0) {
63
+ throw new Error(`Failed to create workflow: ${result.stderr}`);
64
+ }
65
+ }
66
+ // ---------------------------------------------------------------------------
67
+ // 1. resumeWorkflowRun — blocked → active; fails on completed
68
+ // ---------------------------------------------------------------------------
69
+ describe("workflow resume command", () => {
70
+ test("resume flips a blocked run back to active", () => {
71
+ const env = createWorkflowEnv();
72
+ setupWorkflow(env);
73
+ const started = runCli(["workflow", "start", "workflow:test-flow"], env);
74
+ expect(started.status).toBe(0);
75
+ const { run: startRun } = JSON.parse(started.stdout);
76
+ // Complete first step as blocked
77
+ expect(runCli(["workflow", "complete", startRun.id, "--step", "first", "--state", "blocked"], env).status).toBe(0);
78
+ // Verify it's blocked
79
+ const statusBlocked = runCli(["workflow", "status", startRun.id], env);
80
+ expect(statusBlocked.status).toBe(0);
81
+ const { run: blockedRun } = JSON.parse(statusBlocked.stdout);
82
+ expect(blockedRun.status).toBe("blocked");
83
+ // Resume it
84
+ const resumed = runCli(["workflow", "resume", startRun.id], env);
85
+ expect(resumed.status).toBe(0);
86
+ const { run: resumedRun } = JSON.parse(resumed.stdout);
87
+ expect(resumedRun.status).toBe("active");
88
+ });
89
+ test("resume on a completed run returns an error", () => {
90
+ const env = createWorkflowEnv();
91
+ setupWorkflow(env);
92
+ const started = runCli(["workflow", "start", "workflow:test-flow"], env);
93
+ expect(started.status).toBe(0);
94
+ const { run: startRun } = JSON.parse(started.stdout);
95
+ expect(runCli(["workflow", "complete", startRun.id, "--step", "first"], env).status).toBe(0);
96
+ expect(runCli(["workflow", "complete", startRun.id, "--step", "second"], env).status).toBe(0);
97
+ const resumed = runCli(["workflow", "resume", startRun.id], env);
98
+ expect(resumed.status).toBe(2);
99
+ const err = JSON.parse(resumed.stderr);
100
+ expect(err.error).toContain("already completed");
101
+ });
102
+ test("resume on a failed run flips it to active", () => {
103
+ const env = createWorkflowEnv();
104
+ setupWorkflow(env);
105
+ const started = runCli(["workflow", "start", "workflow:test-flow"], env);
106
+ expect(started.status).toBe(0);
107
+ const { run: startRun } = JSON.parse(started.stdout);
108
+ expect(runCli(["workflow", "complete", startRun.id, "--step", "first", "--state", "failed"], env).status).toBe(0);
109
+ const resumed = runCli(["workflow", "resume", startRun.id], env);
110
+ expect(resumed.status).toBe(0);
111
+ const { run: resumedRun } = JSON.parse(resumed.stdout);
112
+ expect(resumedRun.status).toBe("active");
113
+ });
114
+ // Issue #156: after resuming a blocked run, the previously-blocked step must be
115
+ // re-actionable so it can be reclassified to completed/failed/skipped.
116
+ for (const newState of ["completed", "failed", "skipped"]) {
117
+ test(`resume re-opens a blocked step so it can be reclassified to ${newState}`, () => {
118
+ const env = createWorkflowEnv();
119
+ setupWorkflow(env);
120
+ const started = runCli(["workflow", "start", "workflow:test-flow"], env);
121
+ expect(started.status).toBe(0);
122
+ const { run: startRun } = JSON.parse(started.stdout);
123
+ expect(runCli(["workflow", "complete", startRun.id, "--step", "first", "--state", "blocked"], env).status).toBe(0);
124
+ expect(runCli(["workflow", "resume", startRun.id], env).status).toBe(0);
125
+ const reclassified = runCli(["workflow", "complete", startRun.id, "--step", "first", "--state", newState, "--notes", "resolved"], env);
126
+ expect(reclassified.status).toBe(0);
127
+ const parsed = JSON.parse(reclassified.stdout);
128
+ const firstStep = parsed.workflow.steps.find((s) => s.id === "first");
129
+ expect(firstStep?.status).toBe(newState);
130
+ });
131
+ }
132
+ test("resume does not disturb already-completed earlier steps", () => {
133
+ const env = createWorkflowEnv();
134
+ setupWorkflow(env);
135
+ const started = runCli(["workflow", "start", "workflow:test-flow"], env);
136
+ expect(started.status).toBe(0);
137
+ const { run: startRun } = JSON.parse(started.stdout);
138
+ expect(runCli(["workflow", "complete", startRun.id, "--step", "first"], env).status).toBe(0);
139
+ expect(runCli(["workflow", "complete", startRun.id, "--step", "second", "--state", "blocked"], env).status).toBe(0);
140
+ expect(runCli(["workflow", "resume", startRun.id], env).status).toBe(0);
141
+ const status = runCli(["workflow", "status", startRun.id], env);
142
+ expect(status.status).toBe(0);
143
+ const detail = JSON.parse(status.stdout);
144
+ expect(detail.workflow.steps.find((s) => s.id === "first")?.status).toBe("completed");
145
+ expect(detail.workflow.steps.find((s) => s.id === "second")?.status).toBe("pending");
146
+ });
147
+ });
148
+ // ---------------------------------------------------------------------------
149
+ // 2. listWorkflowRuns --active includes blocked runs
150
+ // ---------------------------------------------------------------------------
151
+ describe("workflow list --active includes blocked", () => {
152
+ test("blocked run appears in --active list", () => {
153
+ const env = createWorkflowEnv();
154
+ setupWorkflow(env);
155
+ const started = runCli(["workflow", "start", "workflow:test-flow"], env);
156
+ expect(started.status).toBe(0);
157
+ const { run: startRun } = JSON.parse(started.stdout);
158
+ expect(runCli(["workflow", "complete", startRun.id, "--step", "first", "--state", "blocked"], env).status).toBe(0);
159
+ const listed = runCli(["workflow", "list", "--ref", "workflow:test-flow", "--active"], env);
160
+ expect(listed.status).toBe(0);
161
+ const { runs } = JSON.parse(listed.stdout);
162
+ expect(runs.some((r) => r.id === startRun.id && r.status === "blocked")).toBe(true);
163
+ });
164
+ test("completed run does NOT appear in --active list", () => {
165
+ const env = createWorkflowEnv();
166
+ setupWorkflow(env);
167
+ const started = runCli(["workflow", "start", "workflow:test-flow"], env);
168
+ expect(started.status).toBe(0);
169
+ const { run: startRun } = JSON.parse(started.stdout);
170
+ expect(runCli(["workflow", "complete", startRun.id, "--step", "first"], env).status).toBe(0);
171
+ expect(runCli(["workflow", "complete", startRun.id, "--step", "second"], env).status).toBe(0);
172
+ const listed = runCli(["workflow", "list", "--active"], env);
173
+ expect(listed.status).toBe(0);
174
+ const { runs } = JSON.parse(listed.stdout);
175
+ expect(runs.some((r) => r.id === startRun.id)).toBe(false);
176
+ });
177
+ });
178
+ // ---------------------------------------------------------------------------
179
+ // 3. workflow next on a completed run: done:true, step:null
180
+ // ---------------------------------------------------------------------------
181
+ describe("workflow next — completed run signals done", () => {
182
+ test("next on a completed run-id returns done:true and step:null", () => {
183
+ const env = createWorkflowEnv();
184
+ setupWorkflow(env);
185
+ const started = runCli(["workflow", "start", "workflow:test-flow"], env);
186
+ expect(started.status).toBe(0);
187
+ const { run: startRun } = JSON.parse(started.stdout);
188
+ expect(runCli(["workflow", "complete", startRun.id, "--step", "first"], env).status).toBe(0);
189
+ expect(runCli(["workflow", "complete", startRun.id, "--step", "second"], env).status).toBe(0);
190
+ const next = runCli(["workflow", "next", startRun.id], env);
191
+ expect(next.status).toBe(0);
192
+ const nextJson = JSON.parse(next.stdout);
193
+ expect(nextJson.done).toBe(true);
194
+ expect(nextJson.step).toBeNull();
195
+ });
196
+ });
197
+ // ---------------------------------------------------------------------------
198
+ // 4. workflow next <ref> auto-start: autoStarted:true
199
+ // ---------------------------------------------------------------------------
200
+ describe("workflow next — auto-start flags autoStarted", () => {
201
+ test("next on a ref with no existing run returns autoStarted:true", () => {
202
+ const env = createWorkflowEnv();
203
+ setupWorkflow(env);
204
+ const next = runCli(["workflow", "next", "workflow:test-flow"], env);
205
+ expect(next.status).toBe(0);
206
+ const nextJson = JSON.parse(next.stdout);
207
+ expect(nextJson.autoStarted).toBe(true);
208
+ expect(nextJson.run.status).toBe("active");
209
+ expect(nextJson.step.id).toBe("first");
210
+ });
211
+ test("next on a ref with existing active run does NOT set autoStarted", () => {
212
+ const env = createWorkflowEnv();
213
+ setupWorkflow(env);
214
+ // First call auto-starts
215
+ const first = runCli(["workflow", "next", "workflow:test-flow"], env);
216
+ expect(first.status).toBe(0);
217
+ // Second call resumes existing — no autoStarted
218
+ const second = runCli(["workflow", "next", "workflow:test-flow"], env);
219
+ expect(second.status).toBe(0);
220
+ const secondJson = JSON.parse(second.stdout);
221
+ expect(secondJson.autoStarted).toBeUndefined();
222
+ });
223
+ });
224
+ // ---------------------------------------------------------------------------
225
+ // 5. workflow next --params: sets params on auto-start; fails on existing run
226
+ // ---------------------------------------------------------------------------
227
+ describe("workflow next --params", () => {
228
+ test("--params is accepted when auto-starting a new run", () => {
229
+ const env = createWorkflowEnv();
230
+ setupWorkflow(env);
231
+ const next = runCli(["workflow", "next", "workflow:test-flow", "--params", '{"x":1}'], env);
232
+ expect(next.status).toBe(0);
233
+ const nextJson = JSON.parse(next.stdout);
234
+ expect(nextJson.autoStarted).toBe(true);
235
+ expect(nextJson.run.params?.x).toBe(1);
236
+ });
237
+ test("--params fails when an active run already exists (ref specifier)", () => {
238
+ const env = createWorkflowEnv();
239
+ setupWorkflow(env);
240
+ // Start a run first
241
+ expect(runCli(["workflow", "start", "workflow:test-flow"], env).status).toBe(0);
242
+ const next = runCli(["workflow", "next", "workflow:test-flow", "--params", '{"x":1}'], env);
243
+ expect(next.status).toBe(2);
244
+ const err = JSON.parse(next.stderr);
245
+ expect(err.error).toContain("--params can only be set on a new run");
246
+ });
247
+ test("--params fails when given a direct run-id (existing run)", () => {
248
+ const env = createWorkflowEnv();
249
+ setupWorkflow(env);
250
+ const started = runCli(["workflow", "start", "workflow:test-flow"], env);
251
+ expect(started.status).toBe(0);
252
+ const { run: startRun } = JSON.parse(started.stdout);
253
+ const next = runCli(["workflow", "next", startRun.id, "--params", '{"x":1}'], env);
254
+ expect(next.status).toBe(2);
255
+ const err = JSON.parse(next.stderr);
256
+ expect(err.error).toContain("--params can only be used when starting a new run from a workflow ref");
257
+ expect(err.error).toContain("existing run id");
258
+ });
259
+ });
260
+ // ---------------------------------------------------------------------------
261
+ // 6. workflow status workflow:<name> resolves to most-recent run
262
+ // ---------------------------------------------------------------------------
263
+ describe("workflow status with workflow ref", () => {
264
+ test("status workflow:<name> resolves to the most-recently-updated run", () => {
265
+ const env = createWorkflowEnv();
266
+ setupWorkflow(env);
267
+ const started = runCli(["workflow", "start", "workflow:test-flow"], env);
268
+ expect(started.status).toBe(0);
269
+ const { run: startRun } = JSON.parse(started.stdout);
270
+ const status = runCli(["workflow", "status", "workflow:test-flow"], env);
271
+ expect(status.status).toBe(0);
272
+ const statusJson = JSON.parse(status.stdout);
273
+ expect(statusJson.run.id).toBe(startRun.id);
274
+ expect(statusJson.run.status).toBe("active");
275
+ });
276
+ test("status workflow:<name> returns NotFoundError when no runs exist", () => {
277
+ const env = createWorkflowEnv();
278
+ setupWorkflow(env);
279
+ const status = runCli(["workflow", "status", "workflow:test-flow"], env);
280
+ // No runs created yet — should fail with not-found (exit 1)
281
+ expect(status.status).toBe(1);
282
+ const err = JSON.parse(status.stderr);
283
+ expect(err.error).toContain("No workflow runs found");
284
+ });
285
+ test("next with an unknown run id returns WORKFLOW_NOT_FOUND", () => {
286
+ const env = createWorkflowEnv();
287
+ setupWorkflow(env);
288
+ const next = runCli(["workflow", "next", "bogus-run-id"], env);
289
+ expect(next.status).toBe(1);
290
+ const err = JSON.parse(next.stderr);
291
+ expect(err.code).toBe("WORKFLOW_NOT_FOUND");
292
+ expect(err.hint).toContain("akm workflow list --active");
293
+ });
294
+ test("status with an unknown run id returns WORKFLOW_NOT_FOUND", () => {
295
+ const env = createWorkflowEnv();
296
+ setupWorkflow(env);
297
+ const status = runCli(["workflow", "status", "bogus-run-id"], env);
298
+ expect(status.status).toBe(1);
299
+ const err = JSON.parse(status.stderr);
300
+ expect(err.code).toBe("WORKFLOW_NOT_FOUND");
301
+ expect(err.hint).toContain("akm workflow list --active");
302
+ });
303
+ });
304
+ // ---------------------------------------------------------------------------
305
+ // 7. workflow create name validation
306
+ // ---------------------------------------------------------------------------
307
+ describe("workflow create — name validation", () => {
308
+ test("name with spaces is rejected", () => {
309
+ const env = createWorkflowEnv();
310
+ const result = runCli(["workflow", "create", "name with spaces"], env);
311
+ expect(result.status).toBe(2);
312
+ const err = JSON.parse(result.stderr);
313
+ expect(err.error).toContain("Workflow name must start with a lowercase letter");
314
+ });
315
+ test("name with uppercase is rejected", () => {
316
+ const env = createWorkflowEnv();
317
+ const result = runCli(["workflow", "create", "MyWorkflow"], env);
318
+ expect(result.status).toBe(2);
319
+ const err = JSON.parse(result.stderr);
320
+ expect(err.error).toContain("Workflow name must start with a lowercase letter");
321
+ });
322
+ test("valid lowercase name is accepted", () => {
323
+ const env = createWorkflowEnv();
324
+ const result = runCli(["workflow", "create", "my-workflow"], env);
325
+ expect(result.status).toBe(0);
326
+ const json = JSON.parse(result.stdout);
327
+ expect(json.ref).toBe("workflow:my-workflow");
328
+ });
329
+ test("hierarchical name with forward slash is accepted", () => {
330
+ // Slashes are allowed for hierarchical naming (e.g. release/ship)
331
+ const env = createWorkflowEnv();
332
+ const result = runCli(["workflow", "create", "release/ship"], env);
333
+ expect(result.status).toBe(0);
334
+ const json = JSON.parse(result.stdout);
335
+ expect(json.ref).toBe("workflow:release/ship");
336
+ });
337
+ test("name validation error message mentions slashes", () => {
338
+ const env = createWorkflowEnv();
339
+ const result = runCli(["workflow", "create", "BAD NAME"], env);
340
+ expect(result.status).toBe(2);
341
+ const err = JSON.parse(result.stderr);
342
+ expect(err.error).toContain("slashes");
343
+ });
344
+ });
345
+ // ---------------------------------------------------------------------------
346
+ // 8. workflow create --force without --from/--reset is rejected
347
+ // ---------------------------------------------------------------------------
348
+ describe("workflow create --force guard", () => {
349
+ test("--force without --from or --reset is rejected", () => {
350
+ const env = createWorkflowEnv();
351
+ setupWorkflow(env);
352
+ const result = runCli(["workflow", "create", "test-flow", "--force"], env);
353
+ expect(result.status).toBe(2);
354
+ const err = JSON.parse(result.stderr);
355
+ expect(err.error).toContain("Refusing to overwrite with template");
356
+ });
357
+ test("--force --reset succeeds and overwrites with template", () => {
358
+ const env = createWorkflowEnv();
359
+ setupWorkflow(env);
360
+ const result = runCli(["workflow", "create", "test-flow", "--force", "--reset"], env);
361
+ expect(result.status).toBe(0);
362
+ const json = JSON.parse(result.stdout);
363
+ expect(json.ok).toBe(true);
364
+ expect(json.ref).toBe("workflow:test-flow");
365
+ });
366
+ test("--force --from <file> succeeds and overwrites with file content", () => {
367
+ const env = createWorkflowEnv();
368
+ setupWorkflow(env);
369
+ const sourceDir = makeTempDir("akm-wfqa-src2-");
370
+ const sourcePath = path.join(sourceDir, "new.md");
371
+ fs.writeFileSync(sourcePath, TWO_STEP_WORKFLOW, "utf8");
372
+ const result = runCli(["workflow", "create", "test-flow", "--force", "--from", sourcePath], env);
373
+ expect(result.status).toBe(0);
374
+ const json = JSON.parse(result.stdout);
375
+ expect(json.ok).toBe(true);
376
+ });
377
+ });
378
+ // ---------------------------------------------------------------------------
379
+ // 9. complete --state help text documents default (just ensure it runs)
380
+ // ---------------------------------------------------------------------------
381
+ describe("workflow complete --state default", () => {
382
+ test("complete without --state defaults to completed", () => {
383
+ const env = createWorkflowEnv();
384
+ setupWorkflow(env);
385
+ const started = runCli(["workflow", "start", "workflow:test-flow"], env);
386
+ expect(started.status).toBe(0);
387
+ const { run: startRun } = JSON.parse(started.stdout);
388
+ // No --state flag → should default to 'completed'
389
+ const completed = runCli(["workflow", "complete", startRun.id, "--step", "first"], env);
390
+ expect(completed.status).toBe(0);
391
+ const json = JSON.parse(completed.stdout);
392
+ const step = json.workflow.steps.find((s) => s.id === "first");
393
+ expect(step?.status).toBe("completed");
394
+ });
395
+ });
@@ -0,0 +1,213 @@
1
+ import { afterEach, beforeEach, expect, test } from "bun:test";
2
+ import fs from "node:fs";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { getDbPath } from "../../src/core/paths";
6
+ import { resetQuiet, resetVerbose, setVerbose } from "../../src/core/warn";
7
+ import { closeDatabase, openDatabase } from "../../src/indexer/db";
8
+ import { akmIndex } from "../../src/indexer/indexer";
9
+ let testConfigDir = "";
10
+ let testCacheDir = "";
11
+ const originalXdgConfigHome = process.env.XDG_CONFIG_HOME;
12
+ const originalXdgCacheHome = process.env.XDG_CACHE_HOME;
13
+ beforeEach(() => {
14
+ testConfigDir = fs.mkdtempSync(path.join(os.tmpdir(), "akm-wf-idx-config-"));
15
+ testCacheDir = fs.mkdtempSync(path.join(os.tmpdir(), "akm-wf-idx-cache-"));
16
+ process.env.XDG_CONFIG_HOME = testConfigDir;
17
+ process.env.XDG_CACHE_HOME = testCacheDir;
18
+ const dbPath = getDbPath();
19
+ for (const f of [dbPath, `${dbPath}-wal`, `${dbPath}-shm`]) {
20
+ try {
21
+ fs.unlinkSync(f);
22
+ }
23
+ catch {
24
+ /* ignore */
25
+ }
26
+ }
27
+ // Defensive: other test files may have left the warn module's quiet/verbose
28
+ // latches on. Reset both before each test so the noise-gate assertions read
29
+ // a clean state.
30
+ resetQuiet();
31
+ resetVerbose();
32
+ delete process.env.AKM_VERBOSE;
33
+ });
34
+ afterEach(() => {
35
+ if (originalXdgConfigHome === undefined)
36
+ delete process.env.XDG_CONFIG_HOME;
37
+ else
38
+ process.env.XDG_CONFIG_HOME = originalXdgConfigHome;
39
+ if (originalXdgCacheHome === undefined)
40
+ delete process.env.XDG_CACHE_HOME;
41
+ else
42
+ process.env.XDG_CACHE_HOME = originalXdgCacheHome;
43
+ if (testConfigDir) {
44
+ fs.rmSync(testConfigDir, { recursive: true, force: true });
45
+ testConfigDir = "";
46
+ }
47
+ if (testCacheDir) {
48
+ fs.rmSync(testCacheDir, { recursive: true, force: true });
49
+ testCacheDir = "";
50
+ }
51
+ resetVerbose();
52
+ delete process.env.AKM_VERBOSE;
53
+ });
54
+ function tmpStash() {
55
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "akm-wf-idx-"));
56
+ fs.mkdirSync(path.join(dir, "workflows"), { recursive: true });
57
+ return dir;
58
+ }
59
+ function writeWorkflow(stashDir, name, content) {
60
+ const file = path.join(stashDir, "workflows", `${name}.md`);
61
+ fs.writeFileSync(file, content);
62
+ return file;
63
+ }
64
+ const VALID_WORKFLOW = `# Workflow: Ship Release
65
+
66
+ ## Step: Validate
67
+ Step ID: validate
68
+
69
+ ### Instructions
70
+ Confirm release notes are present.
71
+ `;
72
+ const BROKEN_WORKFLOW = `# Workflow: Bad
73
+
74
+ ## Step: First
75
+ Step ID: first
76
+ ### Instructions
77
+ do A
78
+
79
+ ## Step: Second
80
+ Step ID: first
81
+ ### Instructions
82
+ do B
83
+ `;
84
+ test("indexer admits valid workflows and writes their JSON to workflow_documents", async () => {
85
+ const stashDir = tmpStash();
86
+ writeWorkflow(stashDir, "good", VALID_WORKFLOW);
87
+ const result = await akmIndex({ stashDir, full: true });
88
+ expect(result.totalEntries).toBe(1);
89
+ const db = openDatabase();
90
+ try {
91
+ const row = db
92
+ .prepare(`SELECT wd.document_json, wd.schema_version, wd.source_path
93
+ FROM workflow_documents wd
94
+ JOIN entries e ON e.id = wd.entry_id
95
+ WHERE e.entry_type = 'workflow' AND e.entry_key LIKE ?`)
96
+ .get(`${stashDir}:workflow:%`);
97
+ expect(row).toBeDefined();
98
+ if (!row)
99
+ return;
100
+ expect(row.schema_version).toBe(1);
101
+ expect(row.source_path).toContain("good.md");
102
+ const doc = JSON.parse(row.document_json);
103
+ expect(doc.title).toBe("Ship Release");
104
+ expect(doc.steps).toHaveLength(1);
105
+ expect(doc.steps[0].instructions.text).toContain("Confirm release notes");
106
+ expect(doc.steps[0].source.start).toBeGreaterThan(0);
107
+ }
108
+ finally {
109
+ closeDatabase(db);
110
+ }
111
+ });
112
+ test("indexer rejects broken workflows and surfaces every error in IndexResponse.warnings", async () => {
113
+ const stashDir = tmpStash();
114
+ writeWorkflow(stashDir, "good", VALID_WORKFLOW);
115
+ const brokenPath = writeWorkflow(stashDir, "bad", BROKEN_WORKFLOW);
116
+ const result = await akmIndex({ stashDir, full: true });
117
+ expect(result.totalEntries).toBe(1); // only the good one
118
+ expect(result.warnings ?? []).toBeDefined();
119
+ const warnings = result.warnings ?? [];
120
+ // The broken workflow has a duplicate step ID; the warning string must
121
+ // mention the file and at least one of its errors.
122
+ const brokenWarning = warnings.find((w) => w.includes(brokenPath));
123
+ expect(brokenWarning).toBeDefined();
124
+ expect(brokenWarning).toMatch(/already used|Step ID/);
125
+ const db = openDatabase();
126
+ try {
127
+ const goodRow = db
128
+ .prepare(`SELECT 1 FROM workflow_documents wd
129
+ JOIN entries e ON e.id = wd.entry_id
130
+ WHERE e.entry_key = ?`)
131
+ .get(`${stashDir}:workflow:good`);
132
+ expect(goodRow).toBeDefined();
133
+ const badRow = db
134
+ .prepare(`SELECT 1 FROM workflow_documents wd
135
+ JOIN entries e ON e.id = wd.entry_id
136
+ WHERE e.entry_key = ?`)
137
+ .get(`${stashDir}:workflow:bad`);
138
+ expect(badRow).toBeFalsy();
139
+ }
140
+ finally {
141
+ closeDatabase(db);
142
+ }
143
+ });
144
+ // ── Workflow validation noise gate (issue #273) ─────────────────────────────
145
+ async function captureStderr(fn) {
146
+ const lines = [];
147
+ const originalWarn = console.warn.bind(console);
148
+ console.warn = (...args) => {
149
+ lines.push(args.map((a) => (typeof a === "string" ? a : JSON.stringify(a))).join(" "));
150
+ };
151
+ try {
152
+ const result = await fn();
153
+ return { result, lines };
154
+ }
155
+ finally {
156
+ console.warn = originalWarn;
157
+ }
158
+ }
159
+ test("default verbosity emits one summary line, not per-spec workflow warnings", async () => {
160
+ const stashDir = tmpStash();
161
+ // Two broken workflows so we can prove the summary line is emitted instead
162
+ // of two separate per-spec warnings on stderr.
163
+ writeWorkflow(stashDir, "bad1", BROKEN_WORKFLOW);
164
+ writeWorkflow(stashDir, "bad2", BROKEN_WORKFLOW);
165
+ const { lines } = await captureStderr(() => akmIndex({ stashDir, full: true }));
166
+ const perSpec = lines.filter((l) => l.startsWith("Skipped workflow "));
167
+ expect(perSpec).toHaveLength(0);
168
+ const summary = lines.filter((l) => l.includes("workflow specs skipped due to validation errors"));
169
+ expect(summary).toHaveLength(1);
170
+ expect(summary[0]).toMatch(/^2 workflow specs skipped/);
171
+ expect(summary[0]).toContain("--verbose");
172
+ expect(summary[0]).toContain("AKM_VERBOSE");
173
+ });
174
+ test("default verbosity uses singular 'workflow spec' when only one was skipped", async () => {
175
+ const stashDir = tmpStash();
176
+ writeWorkflow(stashDir, "bad", BROKEN_WORKFLOW);
177
+ const { lines } = await captureStderr(() => akmIndex({ stashDir, full: true }));
178
+ const summary = lines.filter((l) => l.includes("workflow spec skipped"));
179
+ expect(summary).toHaveLength(1);
180
+ expect(summary[0]).toMatch(/^1 workflow spec skipped/);
181
+ });
182
+ test("--verbose flag restores per-spec workflow warnings and suppresses the summary", async () => {
183
+ const stashDir = tmpStash();
184
+ writeWorkflow(stashDir, "bad1", BROKEN_WORKFLOW);
185
+ writeWorkflow(stashDir, "bad2", BROKEN_WORKFLOW);
186
+ setVerbose(true);
187
+ const { lines } = await captureStderr(() => akmIndex({ stashDir, full: true }));
188
+ const perSpec = lines.filter((l) => l.startsWith("Skipped workflow "));
189
+ expect(perSpec).toHaveLength(2);
190
+ const summary = lines.filter((l) => l.includes("workflow specs skipped due to validation errors"));
191
+ expect(summary).toHaveLength(0);
192
+ });
193
+ test("AKM_VERBOSE=1 restores per-spec output even with the verbose flag unset", async () => {
194
+ const stashDir = tmpStash();
195
+ writeWorkflow(stashDir, "bad", BROKEN_WORKFLOW);
196
+ process.env.AKM_VERBOSE = "1";
197
+ const { lines } = await captureStderr(() => akmIndex({ stashDir, full: true }));
198
+ const perSpec = lines.filter((l) => l.startsWith("Skipped workflow "));
199
+ expect(perSpec).toHaveLength(1);
200
+ const summary = lines.filter((l) => l.includes("workflow spec skipped"));
201
+ expect(summary).toHaveLength(0);
202
+ });
203
+ test("AKM_VERBOSE=0 hard-disables verbose output even when --verbose flag was set", async () => {
204
+ const stashDir = tmpStash();
205
+ writeWorkflow(stashDir, "bad", BROKEN_WORKFLOW);
206
+ setVerbose(true);
207
+ process.env.AKM_VERBOSE = "0";
208
+ const { lines } = await captureStderr(() => akmIndex({ stashDir, full: true }));
209
+ const perSpec = lines.filter((l) => l.startsWith("Skipped workflow "));
210
+ expect(perSpec).toHaveLength(0);
211
+ const summary = lines.filter((l) => l.includes("workflow spec skipped"));
212
+ expect(summary).toHaveLength(1);
213
+ });
package/docs/README.md CHANGED
@@ -6,9 +6,12 @@
6
6
  - [Getting Started](getting-started.md) -- Quick setup guide
7
7
  - [Agent Install Guide](agents/agent-install.md) -- Step-by-step automated install for agents
8
8
  - [Stash Maker's Guide](stash-makers.md) -- Build and share a stash on GitHub, npm, or a network directory
9
+ - [Wikis](wikis.md) -- Multi-wiki knowledge bases (Karpathy-style)
9
10
 
10
11
  ## Upgrading
11
12
 
13
+ - [v1 migration guide](migration/v1.md) -- The path from 0.x to v1.0
14
+ - [Release notes (latest: 0.7.0)](migration/release-notes/0.7.0.md) -- Per-release notes drop into `migration/release-notes/`
12
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
13
16
 
14
17
  ## Reference
@@ -28,3 +31,8 @@
28
31
  - [Ref Format](technical/ref.md) -- Wire format for asset references
29
32
  - [Test Coverage Guide](technical/test-coverage-guide.md) -- High-value testing areas
30
33
  - [Core Principles](technical/akm-core-principles.md) -- Design principles and constraints
34
+ - [akm-bench](technical/benchmark.md) -- Search-quality benchmark suite
35
+
36
+ ## Posts
37
+
38
+ - [Blog posts](posts/) -- Articles and posts about akm