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
@@ -0,0 +1,88 @@
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+ /**
5
+ * Canonical asset-on-disk serialization.
6
+ *
7
+ * Before this module, 9+ call sites across `src/` independently reimplemented
8
+ * `yamlStringify(fm).trimEnd() + "---\n…\n---\n\n${body}"` to assemble a
9
+ * Markdown asset. The reimplementations drifted (different body normalization,
10
+ * different separator newlines, different trailing-newline policy), which is
11
+ * exactly the kind of silent format-shift the proposal-quality validators end
12
+ * up chasing downstream. This file is the single point of truth for
13
+ * "what does a well-formed AKM asset look like on disk".
14
+ *
15
+ * Two helpers are exported:
16
+ * - `serializeFrontmatter(fm)` — YAML for the frontmatter block only, with
17
+ * no `---` fences and no trailing newline. Single home for quoting style,
18
+ * field-order policy, and trailing-whitespace rules.
19
+ * - `assembleAsset(fm, body)` — frontmatter wrapped in `---` fences with a
20
+ * blank line between the closing fence and the body, and exactly one
21
+ * trailing `\n`. Single home for body normalization and the file-shape
22
+ * contract.
23
+ *
24
+ * Contract (must hold for the dedup to be safe):
25
+ * - Idempotent: `parseFrontmatter(assembleAsset(fm, body))` re-assembled
26
+ * reproduces the same bytes.
27
+ * - Field order: insertion order of `fm` is preserved (the caller controls
28
+ * ordering; the helper never reorders).
29
+ * - Quoting: `yaml.stringify` defaults — no custom quoting logic.
30
+ * - Trailing newline: exactly one `\n` at end of output.
31
+ * - Body normalization: leading newlines are stripped (`/^\n+/`). This
32
+ * collapses the assorted `body.replace(/^\n+/, "")` /
33
+ * `body.startsWith("\n") ? "" : "\n" + body` / bare `${body}` patterns
34
+ * onto the most aggressive existing normalizer.
35
+ */
36
+ import { stringify as yamlStringify } from "yaml";
37
+ /**
38
+ * Serialize a frontmatter object to its on-disk YAML form, without `---`
39
+ * fences and without a trailing newline.
40
+ *
41
+ * Two calls with the same input produce byte-identical output. Field order is
42
+ * preserved from the input object's insertion order — callers control
43
+ * ordering, the helper never reorders.
44
+ */
45
+ export function serializeFrontmatter(frontmatter) {
46
+ return yamlStringify(frontmatter).trimEnd();
47
+ }
48
+ /**
49
+ * Assemble a complete asset file string from a frontmatter object and a body.
50
+ *
51
+ * Output shape: `---\n<yaml>\n---\n\n<body>\n` where:
52
+ * - `<yaml>` is `serializeFrontmatter(frontmatter)`.
53
+ * - `<body>` has any leading `\n` characters stripped.
54
+ * - Exactly one `\n` terminates the file.
55
+ *
56
+ * Idempotent under round-trip through the project's `parseFrontmatter`.
57
+ */
58
+ export function assembleAsset(frontmatter, body) {
59
+ return assembleAssetFromString(serializeFrontmatter(frontmatter), body);
60
+ }
61
+ /**
62
+ * Same fence/body assembly as `assembleAsset` but takes a pre-serialized
63
+ * frontmatter string. Use this when a caller needs its own frontmatter
64
+ * serializer (e.g. defensive single-line flattening for untrusted LLM
65
+ * output, or JSON.stringify-per-value for guaranteed-quoted scalars) while
66
+ * still sharing the canonical fence-and-body template.
67
+ *
68
+ * The `serializedFm` argument must already match `serializeFrontmatter`'s
69
+ * contract: no `---` fences, no trailing newline. Trailing whitespace is
70
+ * trimmed defensively.
71
+ *
72
+ * Output contract — identical to `assembleAsset`:
73
+ * - `---\n<serializedFm>\n---\n\n<body>\n`
74
+ * - body has leading `\n` characters stripped
75
+ * - exactly one `\n` terminates the file
76
+ *
77
+ * This helper is the single point of truth for the fence-and-body template.
78
+ * Three command surfaces (`reflect`, `distill`, `consolidate`) call it
79
+ * directly because their inputs are pre-validated LLM payloads where the
80
+ * full `yamlStringify` may emit shapes (`|`-block scalars, anchors) that
81
+ * the project's hand-rolled `parseFrontmatter` subset parser cannot read.
82
+ */
83
+ export function assembleAssetFromString(serializedFm, body) {
84
+ const yaml = serializedFm.replace(/\s+$/, "");
85
+ const normalizedBody = body.replace(/^\n+/, "");
86
+ const withTrailingNewline = normalizedBody.endsWith("\n") ? normalizedBody : `${normalizedBody}\n`;
87
+ return `---\n${yaml}\n---\n\n${withTrailingNewline}`;
88
+ }
@@ -1,7 +1,11 @@
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 path from "node:path";
2
5
  import { buildWorkflowAction } from "../output/renderers";
3
6
  import { registerActionBuilder, registerTypeRenderer } from "./asset-registry";
4
7
  import { toPosix } from "./common";
8
+ const buildTaskAction = (ref) => `akm tasks show ${ref.replace(/^task:/, "")} -> inspect; akm tasks run <id> -> run now; akm tasks remove <id> -> unschedule`;
5
9
  const markdownSpec = {
6
10
  isRelevantFile: (fileName) => path.extname(fileName).toLowerCase() === ".md",
7
11
  toCanonicalName: (typeRoot, filePath) => {
@@ -62,6 +66,34 @@ const ASSET_SPECS_INTERNAL = {
62
66
  },
63
67
  script: { stashDir: "scripts", ...scriptSpec },
64
68
  memory: { stashDir: "memories", ...markdownSpec },
69
+ // Environment assets — whole `.env` files sourced/injected wholesale. Replaces
70
+ // the deprecated `vault` type (see below). Key NAMES + start-of-line comments
71
+ // are surfaced as metadata; values are never read for indexing.
72
+ env: {
73
+ stashDir: "env",
74
+ isRelevantFile: (fileName) => fileName === ".env" || fileName.endsWith(".env"),
75
+ toCanonicalName: (typeRoot, filePath) => {
76
+ const rel = toPosix(path.relative(typeRoot, filePath));
77
+ const fileName = path.basename(rel);
78
+ // Treat ".env" as the "default" env; "<name>.env" → "<name>"
79
+ if (fileName === ".env") {
80
+ const dir = path.dirname(rel);
81
+ return dir === "." || dir === "" ? "default" : `${dir}/default`;
82
+ }
83
+ const stripped = rel.endsWith(".env") ? rel.slice(0, -4) : rel;
84
+ return stripped;
85
+ },
86
+ toAssetPath: (typeRoot, name) => {
87
+ if (name === "default")
88
+ return path.join(typeRoot, ".env");
89
+ return path.join(typeRoot, name.endsWith(".env") ? name : `${name}.env`);
90
+ },
91
+ rendererName: "env-file",
92
+ actionBuilder: (ref) => `akm show ${ref} -> inspect key names; akm env run ${ref} -- <command> -> run with the whole .env injected (values never reach stdout); akm env export ${ref} --out <file> -> write a sourceable script to a file`,
93
+ },
94
+ // DEPRECATED in 0.8.0, removed in 0.9.0 — use `env` instead. Retained so the
95
+ // frozen `vaults/` copy left by the migration still resolves and so existing
96
+ // `vault:` refs keep working through the deprecation window.
65
97
  vault: {
66
98
  stashDir: "vaults",
67
99
  isRelevantFile: (fileName) => fileName === ".env" || fileName.endsWith(".env"),
@@ -82,7 +114,23 @@ const ASSET_SPECS_INTERNAL = {
82
114
  return path.join(typeRoot, name.endsWith(".env") ? name : `${name}.env`);
83
115
  },
84
116
  rendererName: "vault-env",
85
- actionBuilder: (ref) => `akm vault list ${ref} -> see key names; eval "$(akm vault load ${ref})" -> load values into the current shell (values never echoed)`,
117
+ actionBuilder: (ref) => `DEPRECATED (use env): akm show ${ref} -> inspect key names; akm env run ${ref} -- <command> -> run with injected env`,
118
+ },
119
+ // Secrets — a single sensitive value used on its own for authentication (a
120
+ // PEM key, API token, TLS cert). Unlike `env` (a group of related .env
121
+ // configuration), the ENTIRE file is the one secret value — there is no safe
122
+ // region to parse, so only the filename is ever surfaced as metadata. The
123
+ // value reaches a command only via `akm secret run` (injected into a child
124
+ // env var) or `akm secret path` (Docker `_FILE` convention). A secret is any
125
+ // regular file under `secrets/` except `.lock`/`.sensitive` sidecars; the
126
+ // canonical name preserves the natural filename (e.g. `id_rsa`, `team/deploy.key`).
127
+ secret: {
128
+ stashDir: "secrets",
129
+ isRelevantFile: (fileName) => !fileName.endsWith(".lock") && !fileName.endsWith(".sensitive"),
130
+ toCanonicalName: (typeRoot, filePath) => toPosix(path.relative(typeRoot, filePath)),
131
+ toAssetPath: (typeRoot, name) => path.join(typeRoot, name),
132
+ rendererName: "secret-file",
133
+ actionBuilder: (ref) => `akm show ${ref} -> name only (value never shown); akm secret path ${ref} -> file path; akm secret run ${ref} <VAR> -- <command> -> run with value injected into $VAR`,
86
134
  },
87
135
  wiki: {
88
136
  stashDir: "wikis",
@@ -102,6 +150,24 @@ const ASSET_SPECS_INTERNAL = {
102
150
  rendererName: "lesson-md",
103
151
  actionBuilder: (ref) => `akm show ${ref} -> read the lesson and apply when_to_use`,
104
152
  },
153
+ // Scheduled tasks. A task file pairs a cron-style schedule with a target
154
+ // (workflow ref, prompt, or command) that `akm tasks` registers with the
155
+ // OS-native scheduler (cron / launchd / schtasks). Stored as pure YAML
156
+ // under <stash>/tasks/<id>.yml.
157
+ task: {
158
+ stashDir: "tasks",
159
+ isRelevantFile: (fileName) => path.extname(fileName).toLowerCase() === ".yml",
160
+ toCanonicalName: (typeRoot, filePath) => {
161
+ const rel = toPosix(path.relative(typeRoot, filePath));
162
+ return rel.endsWith(".yml") ? rel.slice(0, -4) : rel;
163
+ },
164
+ toAssetPath: (typeRoot, name) => {
165
+ const withExt = name.endsWith(".yml") ? name : `${name}.yml`;
166
+ return path.join(typeRoot, withExt);
167
+ },
168
+ rendererName: "task-yaml",
169
+ actionBuilder: buildTaskAction,
170
+ },
105
171
  };
106
172
  export const ASSET_SPECS = ASSET_SPECS_INTERNAL;
107
173
  /**
@@ -1,23 +1,116 @@
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 crypto from "node:crypto";
1
5
  import fs from "node:fs";
2
6
  import path from "node:path";
3
7
  import { TYPE_DIRS } from "./asset-spec";
4
8
  import { ConfigError } from "./errors";
5
9
  import { getConfigPath, getDefaultStashDir } from "./paths";
10
+ // ── Types ───────────────────────────────────────────────────────────────────
11
+ export const ASSET_TYPES = [
12
+ "skill",
13
+ "command",
14
+ "agent",
15
+ "knowledge",
16
+ "workflow",
17
+ "script",
18
+ "memory",
19
+ "env",
20
+ "vault",
21
+ "secret",
22
+ "wiki",
23
+ "lesson",
24
+ ];
25
+ export const ASSET_TYPE_SET = new Set(ASSET_TYPES);
6
26
  // ── Constants ───────────────────────────────────────────────────────────────
7
27
  export const IS_WINDOWS = process.platform === "win32";
8
28
  export function isHttpUrl(value) {
9
29
  return !!value && /^https?:\/\//.test(value);
10
30
  }
31
+ /**
32
+ * Returns `true` when `value` looks like a remote URL that a VCS or HTTP
33
+ * fetch can access. Covers http/https, git@, ssh://, and git:// schemes.
34
+ * Consolidates the repeated inline URL-detection pattern in source-manage.ts.
35
+ */
36
+ export function isRemoteUrl(value) {
37
+ if (!value)
38
+ return false;
39
+ return (value.startsWith("http://") ||
40
+ value.startsWith("https://") ||
41
+ value.startsWith("git@") ||
42
+ value.startsWith("ssh://") ||
43
+ value.startsWith("git://"));
44
+ }
11
45
  export function filterNonEmptyStrings(value) {
12
46
  if (!Array.isArray(value))
13
47
  return undefined;
14
48
  return value.filter((entry) => typeof entry === "string" && entry.trim().length > 0);
15
49
  }
16
50
  // ── Validators ──────────────────────────────────────────────────────────────
51
+ /**
52
+ * Returns true if `type` is a known asset type — either a built-in from
53
+ * {@link ASSET_TYPES} or one dynamically registered via `registerAssetType`.
54
+ *
55
+ * The type guard narrows to `AkmAssetType` for all built-in types. Dynamic
56
+ * types (e.g. registered by plugins) are also accepted at runtime, but the
57
+ * type system treats them as `AkmAssetType` via assertion since they are not
58
+ * part of the static union.
59
+ */
17
60
  export function isAssetType(type) {
18
61
  return Object.hasOwn(TYPE_DIRS, type);
19
62
  }
20
63
  // ── Utilities ───────────────────────────────────────────────────────────────
64
+ /**
65
+ * Write content to a file atomically via a temp file + rename.
66
+ * Prevents partial-write corruption on crash.
67
+ * The temp file is opened with the target `mode` (default 0o600) from the
68
+ * start, so it is never world-readable even briefly.
69
+ *
70
+ * Durability: fsync'd against the May 2026 config-clobber incident (#472).
71
+ * On ext4 (data=ordered) and NVMe-with-TRIM, a power-loss inside the kernel
72
+ * writeback window could leave the renamed file truncated to zero — defeating
73
+ * the purpose of the atomic rename. We:
74
+ * 1. fdatasync the temp fd before close, so the data is on disk before the
75
+ * rename observes it.
76
+ * 2. fsync the parent directory after rename, so the directory entry change
77
+ * is durable too. Some filesystems (FAT, certain FUSE mounts) don't
78
+ * support directory fsync; we ignore EINVAL/ENOTSUP so atomic writes
79
+ * don't fail on exotic mounts.
80
+ */
81
+ export function writeFileAtomic(target, content, mode) {
82
+ const tmp = `${target}.tmp.${process.pid}.${crypto.randomBytes(8).toString("hex")}`;
83
+ const fd = fs.openSync(tmp, "w", mode ?? 0o600);
84
+ try {
85
+ fs.writeSync(fd, content);
86
+ try {
87
+ fs.fdatasyncSync(fd);
88
+ }
89
+ catch {
90
+ // Best-effort: some pseudo-filesystems lack fdatasync. Fall through
91
+ // to closeSync — the rename below still preserves atomicity even if
92
+ // the data isn't durable, and the calling code's retry will recover.
93
+ }
94
+ }
95
+ finally {
96
+ fs.closeSync(fd);
97
+ }
98
+ fs.renameSync(tmp, target);
99
+ try {
100
+ const dirFd = fs.openSync(path.dirname(target), "r");
101
+ try {
102
+ fs.fsyncSync(dirFd);
103
+ }
104
+ finally {
105
+ fs.closeSync(dirFd);
106
+ }
107
+ }
108
+ catch {
109
+ // Directory fsync is unsupported on FAT, some FUSE mounts, and Windows
110
+ // (where directories cannot be opened for read like POSIX). Silently
111
+ // ignore so writeFileAtomic remains portable.
112
+ }
113
+ }
21
114
  /**
22
115
  * Resolve the stash directory using a three-level fallback chain:
23
116
  * 1. AKM_STASH_DIR environment variable (override for CI/scripts)
@@ -329,3 +422,92 @@ function parseRetryAfter(response) {
329
422
  export function toErrorMessage(error) {
330
423
  return error instanceof Error ? error.message : String(error);
331
424
  }
425
+ // ── Date / timestamp utilities ───────────────────────────────────────────────
426
+ /**
427
+ * Return today's date in ISO-8601 format (`YYYY-MM-DD`).
428
+ * Consolidates the `new Date().toISOString().slice(0, 10)` pattern that
429
+ * appears at multiple call sites.
430
+ */
431
+ export function todayIso() {
432
+ return new Date().toISOString().slice(0, 10);
433
+ }
434
+ /**
435
+ * Return a filesystem-safe timestamp string derived from the current instant.
436
+ * Colons and dots are replaced with hyphens so the result is safe as a
437
+ * filename component on all platforms (e.g. `2024-01-15T10-30-00-000Z`).
438
+ */
439
+ export function timestampForFilename() {
440
+ return new Date().toISOString().replace(/[:.]/g, "-");
441
+ }
442
+ // ── String coercion ──────────────────────────────────────────────────────────
443
+ /**
444
+ * Return the trimmed string value if non-empty, otherwise `undefined`.
445
+ * Consolidates `toStringOrUndefined` (frontmatter.ts), `asNonEmptyString`
446
+ * (config.ts), and `firstString` (memory-improve.ts) — all had the same
447
+ * "return a string or undefined" contract with minor semantic differences.
448
+ */
449
+ export function asNonEmptyString(value) {
450
+ if (typeof value !== "string")
451
+ return undefined;
452
+ const trimmed = value.trim();
453
+ return trimmed.length > 0 ? trimmed : undefined;
454
+ }
455
+ // ── Generic data utilities ───────────────────────────────────────────────────
456
+ /**
457
+ * Return the trimmed string if non-empty, otherwise `undefined`.
458
+ * Equivalent to `firstString` previously defined in `memory-improve.ts`.
459
+ */
460
+ export function firstString(value) {
461
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
462
+ }
463
+ /**
464
+ * Coerce an unknown value to a filtered, trimmed string array.
465
+ * Non-strings and empty/whitespace-only entries are dropped.
466
+ */
467
+ export function stringArray(value) {
468
+ if (!Array.isArray(value))
469
+ return [];
470
+ const out = [];
471
+ for (const item of value) {
472
+ if (typeof item === "string" && item.trim().length > 0)
473
+ out.push(item.trim());
474
+ }
475
+ return out;
476
+ }
477
+ /**
478
+ * Group an array of values by a string key derived from each element.
479
+ * Returns a `Map` so insertion order within each group is preserved.
480
+ */
481
+ /**
482
+ * Return true if a process with the given PID is currently alive.
483
+ * Uses `process.kill(pid, 0)` which does not deliver a signal but
484
+ * throws ESRCH when the process does not exist.
485
+ */
486
+ export function isProcessAlive(pid) {
487
+ try {
488
+ process.kill(pid, 0);
489
+ return true;
490
+ }
491
+ catch {
492
+ return false;
493
+ }
494
+ }
495
+ /**
496
+ * Convert a number of days to milliseconds. Consolidates the
497
+ * `N * 24 * 60 * 60 * 1000` pattern used throughout the cooldown logic.
498
+ */
499
+ export function daysToMs(days) {
500
+ return days * 86_400_000;
501
+ }
502
+ export function groupBy(values, keyFn) {
503
+ const groups = new Map();
504
+ for (const value of values) {
505
+ const key = keyFn(value);
506
+ const existing = groups.get(key);
507
+ if (existing)
508
+ existing.push(value);
509
+ else
510
+ groups.set(key, [value]);
511
+ }
512
+ return groups;
513
+ }
@@ -0,0 +1,25 @@
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+ /**
5
+ * Maps over items concurrently with a pool size limit.
6
+ * Uses Promise.allSettled semantics — one failure does not cancel others.
7
+ */
8
+ export async function concurrentMap(items, fn, concurrency = 1) {
9
+ const results = new Array(items.length).fill(undefined);
10
+ let nextIndex = 0;
11
+ async function worker() {
12
+ while (nextIndex < items.length) {
13
+ const i = nextIndex++;
14
+ try {
15
+ results[i] = await fn(items[i], i);
16
+ }
17
+ catch {
18
+ // individual failure: leave undefined, caller checks
19
+ }
20
+ }
21
+ }
22
+ const workers = Array.from({ length: Math.min(concurrency, items.length) }, worker);
23
+ await Promise.all(workers);
24
+ return results;
25
+ }