akm-cli 0.9.0-beta.9 → 0.9.0-rc.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 (325) hide show
  1. package/CHANGELOG.md +592 -0
  2. package/README.md +12 -4
  3. package/dist/akm +38 -0
  4. package/dist/akm-migrate-storage +38 -0
  5. package/dist/assets/help/help-improve.md +9 -6
  6. package/dist/assets/hints/cli-hints-full.md +6 -5
  7. package/dist/assets/profiles/default.json +9 -4
  8. package/dist/assets/profiles/frequent.json +1 -1
  9. package/dist/assets/profiles/memory-focus.json +1 -1
  10. package/dist/assets/profiles/proactive-maintenance.json +25 -0
  11. package/dist/assets/profiles/quick.json +1 -1
  12. package/dist/assets/profiles/recombine-only.json +21 -0
  13. package/dist/assets/profiles/reflect-distill.json +30 -0
  14. package/dist/assets/profiles/synthesize.json +15 -0
  15. package/dist/assets/profiles/thorough.json +1 -1
  16. package/dist/assets/prompts/consolidate-system.md +23 -0
  17. package/dist/assets/prompts/contradiction-judge.md +33 -0
  18. package/dist/assets/prompts/distill-knowledge-system.md +22 -0
  19. package/dist/assets/prompts/distill-lesson-system.md +36 -0
  20. package/dist/assets/prompts/extract-session.md +11 -3
  21. package/dist/assets/prompts/graph-extract-system.md +1 -0
  22. package/dist/assets/prompts/graph-extract-user-prompt.md +1 -1
  23. package/dist/assets/prompts/memory-infer-system.md +1 -0
  24. package/dist/assets/prompts/memory-infer-user.md +5 -0
  25. package/dist/assets/prompts/metadata-enhance-system.md +1 -0
  26. package/dist/assets/prompts/procedural-system.md +44 -0
  27. package/dist/assets/prompts/recombine-system.md +40 -0
  28. package/dist/assets/prompts/staleness-detect-system.md +6 -0
  29. package/dist/assets/prompts/validate-summary-judge.md +1 -0
  30. package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +38 -0
  31. package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +38 -0
  32. package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +39 -0
  33. package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +40 -0
  34. package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +43 -0
  35. package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +38 -0
  36. package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +43 -0
  37. package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +40 -0
  38. package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +43 -0
  39. package/dist/assets/templates/html/health.html +281 -111
  40. package/dist/assets/wiki/ingest-workflow-template.md +45 -16
  41. package/dist/assets/wiki/schema-template.md +4 -4
  42. package/dist/cli/clack.js +56 -0
  43. package/dist/cli/config-migrate.js +7 -1
  44. package/dist/cli/confirm.js +1 -1
  45. package/dist/cli/parse-args.js +46 -1
  46. package/dist/cli/shared.js +28 -0
  47. package/dist/cli.js +25 -21
  48. package/dist/commands/agent/agent-dispatch.js +3 -2
  49. package/dist/commands/agent/agent-support.js +0 -7
  50. package/dist/commands/agent/contribute-cli.js +26 -7
  51. package/dist/commands/config-cli.js +26 -13
  52. package/dist/commands/env/child-env.js +47 -0
  53. package/dist/commands/env/env-cli.js +220 -227
  54. package/dist/commands/env/env.js +14 -67
  55. package/dist/commands/env/secret-cli.js +140 -138
  56. package/dist/commands/feedback-cli.js +156 -155
  57. package/dist/commands/graph/graph-cli.js +5 -13
  58. package/dist/commands/graph/graph.js +3 -3
  59. package/dist/commands/health/advisories.js +151 -0
  60. package/dist/commands/health/checks.js +103 -16
  61. package/dist/commands/health/html-report.js +447 -81
  62. package/dist/commands/health/improve-metrics.js +771 -0
  63. package/dist/commands/health/llm-usage.js +65 -0
  64. package/dist/commands/health/md-report.js +103 -0
  65. package/dist/commands/health/metrics.js +278 -0
  66. package/dist/commands/health/stash-exposure.js +46 -0
  67. package/dist/commands/health/surfaces.js +216 -0
  68. package/dist/commands/health/task-runs.js +135 -0
  69. package/dist/commands/health/types.js +26 -0
  70. package/dist/commands/health/windows.js +195 -0
  71. package/dist/commands/health.js +91 -1091
  72. package/dist/commands/improve/anti-collapse.js +170 -0
  73. package/dist/commands/improve/calibration.js +161 -0
  74. package/dist/commands/improve/collapse-detector.js +421 -0
  75. package/dist/commands/improve/consolidate/chunking.js +141 -0
  76. package/dist/commands/improve/consolidate/eligibility.js +64 -0
  77. package/dist/commands/improve/consolidate/merge.js +145 -0
  78. package/dist/commands/improve/consolidate/sanitize.js +231 -0
  79. package/dist/commands/{lint.js → improve/consolidate/types.js} +1 -1
  80. package/dist/commands/improve/consolidate.js +1295 -1277
  81. package/dist/commands/improve/dedup.js +482 -0
  82. package/dist/commands/improve/distill/content-repair.js +202 -0
  83. package/dist/commands/improve/distill/promote-memory.js +229 -0
  84. package/dist/commands/improve/distill/quality-gate.js +236 -0
  85. package/dist/commands/improve/distill-guards.js +127 -0
  86. package/dist/commands/improve/distill-promotion-policy.js +826 -167
  87. package/dist/commands/improve/distill.js +228 -605
  88. package/dist/commands/improve/eligibility.js +434 -0
  89. package/dist/commands/improve/encoding-salience.js +205 -0
  90. package/dist/commands/improve/extract-cli.js +179 -59
  91. package/dist/commands/improve/extract-prompt.js +54 -3
  92. package/dist/commands/improve/extract-watch.js +140 -0
  93. package/dist/commands/improve/extract.js +409 -43
  94. package/dist/commands/improve/feedback-valence.js +54 -0
  95. package/dist/commands/improve/hot-probation.js +45 -0
  96. package/dist/commands/improve/improve-auto-accept.js +157 -10
  97. package/dist/commands/improve/improve-cli.js +115 -73
  98. package/dist/commands/improve/improve-profiles.js +28 -8
  99. package/dist/commands/improve/improve-result-file.js +15 -25
  100. package/dist/commands/improve/improve-session.js +58 -0
  101. package/dist/commands/improve/improve.js +485 -2764
  102. package/dist/commands/improve/locks.js +154 -0
  103. package/dist/commands/improve/loop-stages.js +1100 -0
  104. package/dist/commands/improve/memory/memory-belief.js +14 -15
  105. package/dist/commands/improve/memory/memory-contradiction-detect.js +83 -60
  106. package/dist/commands/improve/memory/memory-improve.js +27 -27
  107. package/dist/commands/improve/outcome-loop.js +270 -0
  108. package/dist/commands/improve/preparation.js +2002 -0
  109. package/dist/commands/improve/proactive-maintenance.js +37 -35
  110. package/dist/commands/improve/procedural.js +398 -0
  111. package/dist/commands/improve/recombine.js +818 -0
  112. package/dist/commands/improve/reflect-noise.js +0 -0
  113. package/dist/commands/improve/reflect.js +206 -45
  114. package/dist/commands/improve/salience.js +455 -0
  115. package/dist/commands/improve/schema-similarity-gate.js +168 -0
  116. package/dist/commands/improve/shared.js +51 -0
  117. package/dist/commands/improve/triage.js +93 -0
  118. package/dist/commands/lint/agent-linter.js +19 -24
  119. package/dist/commands/lint/base-linter.js +173 -60
  120. package/dist/commands/lint/command-linter.js +19 -24
  121. package/dist/commands/lint/env-key-rules.js +38 -1
  122. package/dist/commands/lint/fact-linter.js +39 -0
  123. package/dist/commands/lint/index.js +31 -13
  124. package/dist/commands/lint/memory-linter.js +1 -1
  125. package/dist/commands/lint/registry.js +7 -2
  126. package/dist/commands/lint/task-linter.js +3 -3
  127. package/dist/commands/lint/workflow-linter.js +26 -1
  128. package/dist/commands/observability-cli.js +4 -4
  129. package/dist/commands/proposal/drain-policies.js +13 -4
  130. package/dist/commands/proposal/drain.js +45 -51
  131. package/dist/commands/proposal/legacy-import.js +115 -0
  132. package/dist/commands/proposal/proposal-cli.js +24 -34
  133. package/dist/commands/proposal/proposal.js +2 -1
  134. package/dist/commands/proposal/propose.js +8 -3
  135. package/dist/commands/proposal/repository.js +829 -0
  136. package/dist/commands/proposal/validators/proposal-quality-validators.js +9 -8
  137. package/dist/commands/proposal/validators/proposals.js +93 -895
  138. package/dist/commands/read/curate.js +410 -111
  139. package/dist/commands/read/knowledge.js +10 -3
  140. package/dist/commands/read/remember-cli.js +133 -138
  141. package/dist/commands/read/search-cli.js +15 -8
  142. package/dist/commands/read/search.js +22 -11
  143. package/dist/commands/read/show.js +106 -14
  144. package/dist/commands/registry-cli.js +76 -87
  145. package/dist/commands/remember.js +11 -12
  146. package/dist/commands/sources/add-cli.js +91 -95
  147. package/dist/commands/sources/history.js +1 -1
  148. package/dist/commands/sources/init.js +66 -18
  149. package/dist/commands/sources/installed-stashes.js +11 -3
  150. package/dist/commands/sources/schema-repair.js +44 -46
  151. package/dist/commands/sources/self-update.js +2 -2
  152. package/dist/commands/sources/source-add.js +7 -3
  153. package/dist/commands/sources/sources-cli.js +3 -3
  154. package/dist/commands/sources/stash-cli.js +19 -39
  155. package/dist/commands/sources/stash-skeleton.js +57 -8
  156. package/dist/commands/tasks/default-tasks.js +15 -2
  157. package/dist/commands/tasks/tasks-cli.js +20 -29
  158. package/dist/commands/tasks/tasks.js +39 -11
  159. package/dist/commands/wiki-cli.js +23 -38
  160. package/dist/commands/workflow-cli.js +15 -1
  161. package/dist/core/asset/asset-registry.js +3 -1
  162. package/dist/core/asset/asset-spec.js +21 -4
  163. package/dist/core/asset/frontmatter.js +188 -167
  164. package/dist/core/asset/markdown.js +8 -0
  165. package/dist/core/authoring-rules.js +92 -0
  166. package/dist/core/common.js +4 -23
  167. package/dist/core/concurrent.js +10 -1
  168. package/dist/core/config/config-io.js +10 -1
  169. package/dist/core/config/config-migration.js +18 -40
  170. package/dist/core/config/config-schema.js +382 -62
  171. package/dist/core/config/config-types.js +3 -3
  172. package/dist/core/config/config.js +67 -22
  173. package/dist/core/deep-merge.js +38 -0
  174. package/dist/core/errors.js +1 -0
  175. package/dist/core/eval/rank-metrics.js +113 -0
  176. package/dist/core/events.js +4 -7
  177. package/dist/core/improve-types.js +47 -8
  178. package/dist/core/logs-db.js +14 -75
  179. package/dist/core/parse.js +36 -16
  180. package/dist/core/paths.js +18 -18
  181. package/dist/core/standards/resolve-standards-context.js +87 -0
  182. package/dist/core/standards/resolve-stash-standards.js +99 -0
  183. package/dist/core/standards/resolve-type-conventions.js +66 -0
  184. package/dist/core/state/migrations.js +770 -0
  185. package/dist/core/state-db.js +132 -1126
  186. package/dist/core/structured.js +69 -0
  187. package/dist/core/time.js +53 -0
  188. package/dist/core/warn.js +21 -0
  189. package/dist/core/write-source.js +37 -0
  190. package/dist/indexer/db/db.js +259 -769
  191. package/dist/indexer/db/entry-mapper.js +41 -0
  192. package/dist/indexer/db/graph-db.js +129 -86
  193. package/dist/indexer/db/llm-cache.js +2 -2
  194. package/dist/indexer/db/schema.js +516 -0
  195. package/dist/indexer/ensure-index.js +36 -92
  196. package/dist/indexer/feedback/utility-policy.js +75 -0
  197. package/dist/indexer/graph/graph-boost.js +51 -41
  198. package/dist/indexer/graph/graph-extraction.js +207 -4
  199. package/dist/indexer/index-writer-lock.js +18 -11
  200. package/dist/indexer/index-written-assets.js +105 -0
  201. package/dist/indexer/indexer.js +182 -204
  202. package/dist/indexer/passes/dir-staleness.js +114 -0
  203. package/dist/indexer/passes/memory-inference.js +13 -5
  204. package/dist/indexer/passes/metadata.js +20 -0
  205. package/dist/indexer/read-preflight.js +23 -0
  206. package/dist/indexer/search/db-search.js +89 -13
  207. package/dist/indexer/search/fts-query.js +51 -0
  208. package/dist/indexer/search/ranking-contributors.js +95 -9
  209. package/dist/indexer/search/ranking.js +79 -3
  210. package/dist/indexer/search/search-fields.js +6 -0
  211. package/dist/indexer/search/search-source.js +32 -21
  212. package/dist/indexer/search/semantic-status.js +4 -0
  213. package/dist/indexer/walk/matchers.js +9 -0
  214. package/dist/indexer/walk/walker.js +21 -13
  215. package/dist/integrations/agent/builders.js +39 -13
  216. package/dist/integrations/agent/config.js +20 -59
  217. package/dist/integrations/agent/detect.js +9 -0
  218. package/dist/integrations/agent/index.js +3 -19
  219. package/dist/integrations/agent/model-aliases.js +7 -2
  220. package/dist/integrations/agent/profiles.js +7 -1
  221. package/dist/integrations/agent/prompts.js +75 -9
  222. package/dist/integrations/agent/runner-dispatch.js +59 -0
  223. package/dist/integrations/agent/runner.js +13 -9
  224. package/dist/integrations/agent/spawn.js +69 -67
  225. package/dist/integrations/harnesses/claude/agent-builder.js +1 -1
  226. package/dist/integrations/harnesses/claude/index.js +2 -0
  227. package/dist/integrations/harnesses/claude/session-log.js +10 -0
  228. package/dist/integrations/harnesses/index.js +2 -3
  229. package/dist/integrations/harnesses/opencode/agent-builder.js +1 -1
  230. package/dist/integrations/harnesses/opencode/index.js +2 -0
  231. package/dist/integrations/harnesses/opencode/session-log.js +173 -3
  232. package/dist/integrations/harnesses/opencode-sdk/index.js +2 -2
  233. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +98 -17
  234. package/dist/integrations/harnesses/types.js +1 -0
  235. package/dist/integrations/session-logs/index.js +16 -0
  236. package/dist/llm/call-ai.js +2 -2
  237. package/dist/llm/client.js +34 -11
  238. package/dist/llm/embedder.js +67 -4
  239. package/dist/llm/embedders/cache.js +3 -1
  240. package/dist/llm/embedders/deterministic.js +66 -0
  241. package/dist/llm/embedders/local.js +73 -3
  242. package/dist/llm/feature-gate.js +16 -15
  243. package/dist/llm/graph-extract.js +67 -44
  244. package/dist/llm/memory-infer-impl.js +138 -0
  245. package/dist/llm/memory-infer.js +1 -127
  246. package/dist/llm/metadata-enhance.js +44 -31
  247. package/dist/llm/structured-call.js +49 -0
  248. package/dist/migrate-storage-node.mjs +8 -0
  249. package/dist/output/context.js +5 -5
  250. package/dist/output/renderers.js +85 -14
  251. package/dist/output/shapes/curate.js +14 -2
  252. package/dist/output/shapes/helpers.js +0 -3
  253. package/dist/output/shapes/passthrough.js +2 -1
  254. package/dist/output/text/helpers.js +29 -1
  255. package/dist/output/text/workflow.js +1 -0
  256. package/dist/registry/providers/skills-sh.js +21 -147
  257. package/dist/registry/providers/static-index.js +15 -157
  258. package/dist/registry/resolve.js +27 -9
  259. package/dist/runtime.js +25 -1
  260. package/dist/scripts/migrate-storage.js +2661 -2369
  261. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +883 -596
  262. package/dist/setup/detect.js +9 -0
  263. package/dist/setup/legacy-config.js +106 -0
  264. package/dist/setup/prompt.js +57 -0
  265. package/dist/setup/providers.js +14 -0
  266. package/dist/setup/registry-stash-loader.js +12 -0
  267. package/dist/setup/semantic-assets.js +124 -0
  268. package/dist/setup/setup.js +52 -1614
  269. package/dist/setup/steps/connection.js +734 -0
  270. package/dist/setup/steps/output.js +31 -0
  271. package/dist/setup/steps/platforms.js +124 -0
  272. package/dist/setup/steps/semantic.js +27 -0
  273. package/dist/setup/steps/sources.js +222 -0
  274. package/dist/setup/steps/stashdir.js +42 -0
  275. package/dist/setup/steps/tasks.js +152 -0
  276. package/dist/sources/include.js +6 -2
  277. package/dist/sources/providers/filesystem.js +0 -1
  278. package/dist/sources/providers/git-install.js +210 -0
  279. package/dist/sources/providers/git-provider.js +234 -0
  280. package/dist/sources/providers/git-stash.js +248 -0
  281. package/dist/sources/providers/git.js +10 -661
  282. package/dist/sources/providers/npm.js +2 -6
  283. package/dist/sources/providers/provider-utils.js +13 -7
  284. package/dist/sources/providers/sync-from-ref.js +9 -1
  285. package/dist/sources/providers/website.js +9 -5
  286. package/dist/sources/website-ingest.js +187 -29
  287. package/dist/sources/wiki-fetchers/registry.js +53 -0
  288. package/dist/sources/wiki-fetchers/youtube.js +239 -0
  289. package/dist/storage/database.js +45 -10
  290. package/dist/storage/managed-db.js +82 -0
  291. package/dist/storage/repositories/canaries-repository.js +107 -0
  292. package/dist/storage/repositories/consolidation-repository.js +38 -0
  293. package/dist/storage/repositories/embeddings-repository.js +72 -0
  294. package/dist/storage/repositories/events-repository.js +187 -0
  295. package/dist/storage/repositories/extract-sessions-repository.js +96 -0
  296. package/dist/storage/repositories/improve-runs-repository.js +146 -0
  297. package/dist/storage/repositories/index-db.js +14 -8
  298. package/dist/storage/repositories/proposals-repository.js +220 -0
  299. package/dist/storage/repositories/recombine-repository.js +213 -0
  300. package/dist/storage/repositories/registry-cache.js +93 -0
  301. package/dist/storage/repositories/registry-index-cache-repository.js +46 -0
  302. package/dist/storage/repositories/task-history-repository.js +93 -0
  303. package/dist/storage/sqlite-pragmas.js +146 -0
  304. package/dist/tasks/backends/cron.js +1 -1
  305. package/dist/tasks/backends/index.js +9 -0
  306. package/dist/tasks/backends/launchd.js +1 -1
  307. package/dist/tasks/backends/schtasks.js +1 -1
  308. package/dist/tasks/{resolveAkmBin.js → resolve-akm-bin.js} +2 -2
  309. package/dist/tasks/runner.js +15 -13
  310. package/dist/text-import-hook.mjs +0 -0
  311. package/dist/wiki/wiki.js +52 -11
  312. package/dist/workflows/cli.js +1 -0
  313. package/dist/workflows/db.js +3 -4
  314. package/dist/workflows/runtime/runs.js +43 -118
  315. package/dist/workflows/runtime/workflow-asset-loader.js +125 -0
  316. package/dist/workflows/validate-summary.js +2 -7
  317. package/docs/README.md +69 -18
  318. package/docs/data-and-telemetry.md +5 -4
  319. package/docs/migration/release-notes/0.7.0.md +1 -1
  320. package/docs/migration/release-notes/0.9.0.md +39 -0
  321. package/package.json +10 -10
  322. package/dist/assets/tasks/core/update-stashes.yml +0 -4
  323. package/dist/commands/db-cli.js +0 -23
  324. package/dist/indexer/db/db-backup.js +0 -376
  325. package/dist/indexer/passes/staleness-detect.js +0 -488
@@ -0,0 +1,69 @@
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
+ * `runStructured<T>()` — transport-free structured-output core (P0.5 seam).
6
+ *
7
+ * akm has two structured-output paths that share no code: the LLM HTTP path
8
+ * (`llm/structured-call.ts` + `responseSchema`) and the agent JSON path
9
+ * (`agent/spawn.ts` `parseOutput: "json"` — embedded-JSON scan, no schema,
10
+ * no retry). This module is the ONE validation-driven retry loop both are
11
+ * meant to converge on: the workflow engine's schema units compose it with a
12
+ * dispatch adapter per runner (llm = `chatCompletion` + `responseSchema`;
13
+ * agent/sdk = `runAgent`/`runOpencodeSdk` + prompt-injected schema;
14
+ * native-schema CLIs pass the schema through and still validate).
15
+ *
16
+ * Layering: this file lives in `core/` — NOT `llm/` — because the agent path
17
+ * must be able to reach it and `agent/ ⇏ llm/` is an enforced invariant
18
+ * (`tests/architecture/agent-no-llm-sdk-guard.test.ts`). The transport is
19
+ * always injected; this module never performs IO of its own.
20
+ *
21
+ * Retry semantics: only parse/validation misses are retried (with corrective
22
+ * feedback appended to the re-dispatch). Transport errors PROPAGATE — the
23
+ * transports own their own retry discipline (`chatCompletion` has a bounded
24
+ * jittered retry; `runAgent` has timeout/abort semantics).
25
+ */
26
+ import { parseEmbeddedJsonResponse } from "./parse.js";
27
+ function defaultFeedback(failure) {
28
+ if (failure.reason === "parse_error") {
29
+ return "Your previous response contained no parseable JSON. Respond with ONLY a JSON value that matches the requested schema — no prose, no code fences.";
30
+ }
31
+ return `Your previous JSON response failed validation:\n- ${failure.errors.join("\n- ")}\nRespond again with ONLY a corrected JSON value.`;
32
+ }
33
+ /**
34
+ * Run the dispatch→parse→validate loop, re-dispatching with corrective
35
+ * feedback on parse/validation misses, up to `maxAttempts` total attempts.
36
+ * Never throws for structure problems (returns a typed failure); transport
37
+ * throws propagate untouched.
38
+ */
39
+ export async function runStructured(options) {
40
+ const parse = options.parse ?? ((raw) => parseEmbeddedJsonResponse(raw));
41
+ const buildFeedback = options.buildFeedback ?? defaultFeedback;
42
+ const maxAttempts = Math.max(1, options.maxAttempts ?? 2);
43
+ let feedback;
44
+ let lastFailure;
45
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
46
+ const raw = await options.dispatch(feedback);
47
+ const candidate = parse(raw);
48
+ if (candidate === undefined) {
49
+ lastFailure = {
50
+ ok: false,
51
+ reason: "parse_error",
52
+ errors: ["no JSON structure found in response"],
53
+ attempts: attempt,
54
+ raw,
55
+ };
56
+ feedback = buildFeedback({ reason: "parse_error", errors: lastFailure.errors, raw });
57
+ continue;
58
+ }
59
+ const verdict = options.validate(candidate);
60
+ if (verdict.ok) {
61
+ return { ok: true, value: verdict.value, attempts: attempt };
62
+ }
63
+ lastFailure = { ok: false, reason: "validation_error", errors: verdict.errors, attempts: attempt, raw };
64
+ feedback = buildFeedback({ reason: "validation_error", errors: verdict.errors, raw });
65
+ }
66
+ // maxAttempts >= 1 guarantees at least one loop iteration set lastFailure
67
+ // (a success would have returned), so the non-null assertion is safe.
68
+ return lastFailure;
69
+ }
package/dist/core/time.js CHANGED
@@ -9,6 +9,59 @@
9
9
  * consistently without private re-implementations drifting apart.
10
10
  */
11
11
  import { UsageError } from "./errors.js";
12
+ // ── Duration-shorthand parsing ───────────────────────────────────────────────
13
+ const MINUTE_MS = 60 * 1000;
14
+ const HOUR_MS = 60 * 60 * 1000;
15
+ const DAY_MS = 24 * 60 * 60 * 1000;
16
+ /** A month is approximated as 30 days — this shorthand is not calendar-exact. */
17
+ const MONTH_MS = 30 * DAY_MS;
18
+ /**
19
+ * Canonical duration-shorthand unit map shared by every `--since` / `--expires`
20
+ * / `--window-compare` consumer.
21
+ *
22
+ * The grammar is intentionally uniform across the whole CLI:
23
+ * - `m` = MINUTES, `M` = MONTHS (30-day approximation)
24
+ * - `h`/`H` = hours, `d`/`D` = days
25
+ *
26
+ * Matching is CASE-SENSITIVE (see {@link parseDuration}), which is what lets
27
+ * `m` and `M` mean different things. Historically `akm health --since` and
28
+ * `remember --expires` read a case-insensitive `m` as MONTHS while
29
+ * `consolidate` / `--window-compare` read it as MINUTES; that split is now
30
+ * resolved in favour of the conventional `m`=minutes, with `M` reserved for
31
+ * months. Upper-case `H`/`D` aliases are retained so specs that previously
32
+ * relied on the old case-insensitive parsers (e.g. `"7D"`) keep working.
33
+ */
34
+ export const DURATION_UNITS = {
35
+ m: MINUTE_MS,
36
+ M: MONTH_MS,
37
+ h: HOUR_MS,
38
+ H: HOUR_MS,
39
+ d: DAY_MS,
40
+ D: DAY_MS,
41
+ };
42
+ /**
43
+ * Parse a compact duration shorthand (e.g. `"30d"`, `"12h"`, `"5m"`, `"3M"`)
44
+ * into a number of milliseconds using an explicit `units` map (default
45
+ * {@link DURATION_UNITS}), or return `null` when the input does not match
46
+ * `<digits><letter>` or the unit is not in the map.
47
+ *
48
+ * Matching is CASE-SENSITIVE against the map keys, so `m` (minutes) and `M`
49
+ * (months) are distinct — do NOT lower-case the spec before calling, or the
50
+ * two collapse. Amount is parsed with base-10 `parseInt`; `null` is returned
51
+ * rather than throwing so each caller keeps its own error/fallback policy.
52
+ */
53
+ export function parseDuration(spec, units = DURATION_UNITS) {
54
+ const match = spec.trim().match(/^(\d+)([a-zA-Z])$/);
55
+ if (!match)
56
+ return null;
57
+ const amount = Number.parseInt(match[1] ?? "", 10);
58
+ if (!Number.isFinite(amount))
59
+ return null;
60
+ const multiplier = units[match[2] ?? ""];
61
+ if (multiplier === undefined)
62
+ return null;
63
+ return amount * multiplier;
64
+ }
12
65
  // ── Since-flag parsing ───────────────────────────────────────────────────────
13
66
  /**
14
67
  * Parse a user-supplied `--since` value and return an ISO-8601 timestamp
package/dist/core/warn.js CHANGED
@@ -17,6 +17,11 @@ import path from "node:path";
17
17
  let quiet = false;
18
18
  let verbose = false;
19
19
  let logFilePath;
20
+ let sinkOverride;
21
+ /** TEST-ONLY. Swap the output sink; pass undefined to restore real output. */
22
+ export function _setWarnSinkForTests(fake) {
23
+ sinkOverride = fake;
24
+ }
20
25
  export function setQuiet(value) {
21
26
  quiet = value;
22
27
  }
@@ -96,6 +101,10 @@ function appendToLogFile(level, args) {
96
101
  * Use for progress counters and status lines (replaces console.error used for progress).
97
102
  */
98
103
  export function info(...args) {
104
+ if (sinkOverride) {
105
+ sinkOverride("info", args);
106
+ return;
107
+ }
99
108
  appendToLogFile("INFO", args);
100
109
  if (!quiet) {
101
110
  console.warn(...args);
@@ -107,6 +116,10 @@ export function info(...args) {
107
116
  * Drop-in replacement for console.warn() across the codebase.
108
117
  */
109
118
  export function warn(...args) {
119
+ if (sinkOverride) {
120
+ sinkOverride("warn", args);
121
+ return;
122
+ }
110
123
  appendToLogFile("WARN", args);
111
124
  if (!quiet) {
112
125
  console.warn(...args);
@@ -118,6 +131,10 @@ export function warn(...args) {
118
131
  * Drop-in replacement for console.error() used for diagnostic failures.
119
132
  */
120
133
  export function error(...args) {
134
+ if (sinkOverride) {
135
+ sinkOverride("error", args);
136
+ return;
137
+ }
121
138
  appendToLogFile("ERROR", args);
122
139
  if (!quiet) {
123
140
  console.error(...args);
@@ -129,6 +146,10 @@ export function error(...args) {
129
146
  * default verbosity (e.g. registry-content workflow validation errors).
130
147
  */
131
148
  export function warnVerbose(...args) {
149
+ if (sinkOverride) {
150
+ sinkOverride("warnVerbose", args);
151
+ return;
152
+ }
132
153
  if (isVerbose()) {
133
154
  warn(...args);
134
155
  }
@@ -30,6 +30,7 @@ import { resolveAssetPathFromName, TYPE_DIRS } from "./asset/asset-spec.js";
30
30
  import { isWithin, resolveStashDir } from "./common.js";
31
31
  import { resolveConfiguredSources } from "./config/config.js";
32
32
  import { ConfigError, UsageError } from "./errors.js";
33
+ import { warn } from "./warn.js";
33
34
  /**
34
35
  * Source kinds that the loader is allowed to mark `writable: true`. Anything
35
36
  * else is rejected at config load (per locked decision 4) — see
@@ -80,6 +81,34 @@ export function sanitizeCommitMessage(input) {
80
81
  }
81
82
  return out;
82
83
  }
84
+ // ── Portability advisory (review 13, D1) ────────────────────────────────────
85
+ /**
86
+ * Matches an absolute host **home** path — `/home/<user>` or `/Users/<user>` —
87
+ * requiring at least one user segment after the prefix. A bare `/home/` or
88
+ * `/Users/` (no user segment) does NOT match. The user segment stops at the
89
+ * first path separator, whitespace, or common delimiter so we capture just the
90
+ * `/home/<user>` prefix rather than the whole path.
91
+ *
92
+ * Deliberately conservative: it does not exempt fenced code blocks, so content
93
+ * that legitimately *documents* a system path (e.g. a tutorial) can produce a
94
+ * false positive. That is accepted — the advisory is non-fatal and correctness
95
+ * (never missing a real leak) is preferred over cleverness here.
96
+ */
97
+ const ABSOLUTE_HOME_PATH_RE = /\/(?:home|Users)\/[^\s/"'`)\]}<>|:;,]+/g;
98
+ /**
99
+ * Return the distinct `/home/<user>` / `/Users/<user>` prefixes embedded in
100
+ * `content`, in first-seen order. Empty when the content is portable.
101
+ *
102
+ * Used by {@link writeAssetToSource} to emit a write-time advisory: absolute
103
+ * host home paths make the stash non-portable and leak the local username.
104
+ */
105
+ export function findAbsoluteHomePaths(content) {
106
+ const seen = new Set();
107
+ for (const match of content.matchAll(ABSOLUTE_HOME_PATH_RE)) {
108
+ seen.add(match[0]);
109
+ }
110
+ return [...seen];
111
+ }
83
112
  // ── Public helpers ──────────────────────────────────────────────────────────
84
113
  /**
85
114
  * Resolve the effective `writable` flag for a source config entry, applying
@@ -132,6 +161,14 @@ export async function writeAssetToSource(source, config, ref, content) {
132
161
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
133
162
  const normalized = content.endsWith("\n") ? content : `${content}\n`;
134
163
  fs.writeFileSync(filePath, normalized, "utf8");
164
+ // Non-fatal portability advisory (review 13, D1): flag absolute host home
165
+ // paths in the written content. These make the stash non-portable and leak
166
+ // the local username. We warn AFTER the write so the advisory never blocks it.
167
+ const hostPaths = findAbsoluteHomePaths(normalized);
168
+ if (hostPaths.length > 0) {
169
+ warn(`warning: asset "${formatRefForMessage(ref)}" embeds absolute host path(s): ${hostPaths.join(", ")}. ` +
170
+ "These make the stash non-portable and leak the local username — prefer $HOME or ~ relative references.");
171
+ }
135
172
  return { path: filePath, ref: makeAssetRef(ref.type, ref.name, ref.origin) };
136
173
  }
137
174
  /**