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
@@ -18,23 +18,28 @@
18
18
  */
19
19
  import fs from "node:fs";
20
20
  import path from "node:path";
21
+ import { findCittyTopLevelCommandIndex } from "../../cli/parse-args.js";
21
22
  import { parseAssetRef } from "../../core/asset/asset-ref.js";
22
23
  import { parseFrontmatter } from "../../core/asset/frontmatter.js";
23
24
  import { META_DIR, parseMetaRef, resolveMetaFilePath } from "../../core/asset/stash-meta.js";
24
25
  import { asNonEmptyString } from "../../core/common.js";
25
- import { loadConfig } from "../../core/config/config.js";
26
+ import { getIndexPassConfig, loadConfig } from "../../core/config/config.js";
26
27
  import { NotFoundError, rethrowIfTestIsolationError, UsageError } from "../../core/errors.js";
27
28
  import { appendEvent, readEvents } from "../../core/events.js";
28
- import { findEntryIdByRef } from "../../indexer/db/db.js";
29
- import { ensureIndex } from "../../indexer/ensure-index.js";
29
+ import { closeDatabase, computeBodyHash, findEntryIdByRef, openExistingDatabase } from "../../indexer/db/db.js";
30
+ import { hasGraphData } from "../../indexer/db/graph-db.js";
30
31
  import { listRelatedPathsForFile } from "../../indexer/graph/graph-boost.js";
32
+ import { extractGraphForSingleFile } from "../../indexer/graph/graph-extraction.js";
31
33
  import { lookup } from "../../indexer/indexer.js";
34
+ import { ensurePrimaryIndexForRead, resolveReadSources } from "../../indexer/read-preflight.js";
32
35
  import { buildEditHint, findSourceForPath, isEditable, resolveSourceEntries } from "../../indexer/search/search-source.js";
33
36
  import { insertUsageEvent } from "../../indexer/usage/usage-events.js";
34
37
  import { buildFileContext, buildRenderContext, getRenderer, runMatchers } from "../../indexer/walk/file-context.js";
35
38
  import { resolveAssetPath } from "../../indexer/walk/path-resolver.js";
39
+ import { resolveIndexPassLLM } from "../../llm/index-passes.js";
36
40
  import { resolveSourcesForOrigin } from "../../registry/origin-resolve.js";
37
- import { withIndexDb } from "../../storage/repositories/index-db.js";
41
+ import { resolveStorageLocations } from "../../storage/locations.js";
42
+ import { TELEMETRY_BUSY_TIMEOUT_MS, withIndexDb } from "../../storage/repositories/index-db.js";
38
43
  // Eagerly import source providers to trigger self-registration.
39
44
  import "../../sources/providers/index.js";
40
45
  import { getCurrentWorkflowScopeKey } from "../../workflows/authoring/scope-key.js";
@@ -142,10 +147,8 @@ export async function akmShowUnified(input) {
142
147
  }
143
148
  }
144
149
  // Auto-index when stale so the index is current before lookup.
145
- const allSources = resolveSourceEntries();
146
- if (allSources.length > 0) {
147
- await ensureIndex(allSources[0].path);
148
- }
150
+ const { primarySource } = resolveReadSources();
151
+ await ensurePrimaryIndexForRead(primarySource);
149
152
  // Try local filesystem (FTS5 index lookup)
150
153
  const result = await showLocal(input);
151
154
  // Scope filter narrows resolution: if --scope was supplied, the asset's
@@ -296,7 +299,7 @@ function logShowEvent(ref, eventSource = "user") {
296
299
  entry_id: findEntryIdByRef(db, ref),
297
300
  source: eventSource,
298
301
  });
299
- });
302
+ }, { busyTimeoutMs: TELEMETRY_BUSY_TIMEOUT_MS });
300
303
  }
301
304
  catch (err) {
302
305
  rethrowIfTestIsolationError(err);
@@ -360,6 +363,21 @@ export async function showLocal(input) {
360
363
  }
361
364
  const renderCtx = buildRenderContext(fileCtx, match, allSourceDirs, source?.registryId);
362
365
  const response = renderer.buildShowResponse(renderCtx);
366
+ // 07 P1-D: provenance-aware toolPolicy CEILING. An agent's self-declared
367
+ // `tools` frontmatter is honoured ONLY for the operator's own PRIMARY stash —
368
+ // the assets they authored. Every other source is content pulled from
369
+ // elsewhere and must not name its own tool grant: registry-installed packs, a
370
+ // configured secondary source, and even a git source the operator marked
371
+ // `--writable` to contribute edits upstream (writability is "can I push", not
372
+ // "do I trust this content to grant itself tools"). Drop the policy so dispatch
373
+ // falls back to the parent/default grant. Keys off primary-stash identity —
374
+ // `allSources[0]` is always the primary (search-source.ts) — not a
375
+ // name-derived registryId or the orthogonal `writable` bit. `source` undefined
376
+ // (unresolved path) also fails closed.
377
+ const isPrimaryStash = source !== undefined && source.path === allSources[0]?.path;
378
+ if (response.toolPolicy !== undefined && !isPrimaryStash) {
379
+ delete response.toolPolicy;
380
+ }
363
381
  const editable = isEditable(assetPath, config);
364
382
  const fullResponse = {
365
383
  ...response,
@@ -383,6 +401,15 @@ export async function showLocal(input) {
383
401
  if (activeRun) {
384
402
  fullResponse.activeRun = activeRun;
385
403
  }
404
+ // #624-P3: opt-in inline graph extraction. Default OFF — when the flag is
405
+ // unset this whole block is skipped (no hasGraphData check, no LLM call), so
406
+ // behavior is byte-identical to today. When ON, it extracts graph data for an
407
+ // ungraphed asset, but ONLY when a model is configured (model-available
408
+ // guard) and ALWAYS bounded by a 30s timeout so `show` can never hang. Any
409
+ // timeout/model-unavailable/error path returns the response unchanged.
410
+ if (getIndexPassConfig(config.index, "graph")?.lazyGraphExtraction === true) {
411
+ await maybeExtractGraphInline(config, sourceStashDir, assetPath);
412
+ }
386
413
  if (input.detail === "brief") {
387
414
  return buildBriefResponse(fullResponse, assetPath);
388
415
  }
@@ -391,6 +418,58 @@ export async function showLocal(input) {
391
418
  }
392
419
  return fullResponse;
393
420
  }
421
+ /**
422
+ * #624-P3 — opt-in inline graph extraction for `akm show`. Best-effort and
423
+ * timeout-bounded: never throws, never hangs, never mutates the response.
424
+ *
425
+ * Preconditions (caller already checked the flag): a model must be configured
426
+ * (model-available guard via {@link resolveIndexPassLLM}) and the asset must be
427
+ * ungraphed ({@link hasGraphData}). Extraction races a 30s timeout so `show`
428
+ * cannot block on a slow provider; any timeout/error/missing-model path is
429
+ * swallowed and `show` returns its already-assembled response unchanged.
430
+ */
431
+ async function maybeExtractGraphInline(config, sourceStashDir, assetPath) {
432
+ try {
433
+ // Model-available guard — no provider configured ⇒ silent skip, no LLM call.
434
+ if (!resolveIndexPassLLM("graph", config))
435
+ return;
436
+ let alreadyGraphed = false;
437
+ let bodyHash;
438
+ try {
439
+ const raw = fs.readFileSync(assetPath, "utf8");
440
+ bodyHash = computeBodyHash(parseFrontmatter(raw).content.trim());
441
+ }
442
+ catch {
443
+ return; // file gone/unreadable ⇒ nothing to extract
444
+ }
445
+ withIndexDb((db) => {
446
+ alreadyGraphed = hasGraphData(db, sourceStashDir, assetPath);
447
+ }, { busyTimeoutMs: TELEMETRY_BUSY_TIMEOUT_MS });
448
+ if (alreadyGraphed)
449
+ return;
450
+ // Open the db for the async extraction ourselves: `withIndexDb` is
451
+ // synchronous and would close the connection the instant the async fn
452
+ // returns its Promise (before extraction completes). Close it explicitly
453
+ // after the race settles instead.
454
+ const db = openExistingDatabase(resolveStorageLocations().indexDb);
455
+ let timer;
456
+ const timeout = new Promise((resolve) => {
457
+ timer = setTimeout(resolve, 30_000);
458
+ });
459
+ try {
460
+ await Promise.race([extractGraphForSingleFile(db, sourceStashDir, assetPath, bodyHash, { config }), timeout]);
461
+ }
462
+ finally {
463
+ if (timer)
464
+ clearTimeout(timer);
465
+ closeDatabase(db);
466
+ }
467
+ }
468
+ catch (err) {
469
+ rethrowIfTestIsolationError(err);
470
+ // Any other failure: silently return the unchanged show response.
471
+ }
472
+ }
394
473
  /**
395
474
  * Minimal `show`: ref → indexer lookup → file contents. Used by callers that
396
475
  * just need the raw file (e.g. clone, write-source) and don't want the full
@@ -458,6 +537,14 @@ function buildSummaryResponse(full, assetPath) {
458
537
  }
459
538
  // ── argv normalisation ───────────────────────────────────────────────────────
460
539
  const SHOW_VIEW_MODES = new Set(["toc", "frontmatter", "full", "section", "lines"]);
540
+ const SHOW_ARGV_TOP_LEVEL_ARGS = {
541
+ format: { type: "string" },
542
+ output: { type: "string" },
543
+ detail: { type: "string" },
544
+ shape: { type: "string" },
545
+ quiet: { type: "boolean", alias: "q" },
546
+ verbose: { type: "boolean" },
547
+ };
461
548
  /**
462
549
  * Normalize argv so positional view-mode arguments after the asset ref
463
550
  * are rewritten into internal flags that citty can parse.
@@ -471,15 +558,20 @@ const SHOW_VIEW_MODES = new Set(["toc", "frontmatter", "full", "section", "lines
471
558
  * Returns a new array; the input is never modified.
472
559
  */
473
560
  export function normalizeShowArgv(argv) {
474
- // argv[0]=bun argv[1]=script argv[2]=subcommand argv[3]=ref argv[4..]=rest
475
- if (argv[2] !== "show")
561
+ const rawArgs = argv.slice(2);
562
+ const commandIndex = findCittyTopLevelCommandIndex(rawArgs, SHOW_ARGV_TOP_LEVEL_ARGS);
563
+ if (commandIndex < 0 || rawArgs[commandIndex] !== "show")
476
564
  return argv;
477
- if (argv.includes("--view") || argv.includes("--heading") || argv.includes("--start") || argv.includes("--end")) {
565
+ const commandArgs = rawArgs.slice(commandIndex + 1);
566
+ if (commandArgs.includes("--view") ||
567
+ commandArgs.includes("--heading") ||
568
+ commandArgs.includes("--start") ||
569
+ commandArgs.includes("--end")) {
478
570
  throw new UsageError('Legacy show flags are no longer supported. Use positional syntax like `akm show knowledge:guide toc` or `akm show knowledge:guide section "Auth"`.');
479
571
  }
480
572
  // Separate global flags from positional/show-specific args
481
- const prefix = argv.slice(0, 3); // [bun, script, show]
482
- const rest = argv.slice(3);
573
+ const prefix = [...argv.slice(0, 2), ...rawArgs.slice(0, commandIndex + 1)];
574
+ const rest = commandArgs;
483
575
  const globalFlags = [];
484
576
  const showArgs = [];
485
577
  for (let i = 0; i < rest.length; i++) {
@@ -3,27 +3,24 @@
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
4
  import { defineCommand } from "citty";
5
5
  import { parsePositiveIntFlag } from "../cli/parse-args.js";
6
- import { output, runWithJsonErrors } from "../cli/shared.js";
6
+ import { defineJsonCommand, output } from "../cli/shared.js";
7
7
  import { DEFAULT_CONFIG, loadUserConfig, saveConfig } from "../core/config/config.js";
8
8
  import { UsageError } from "../core/errors.js";
9
9
  import { warn } from "../core/warn.js";
10
- import { getHyphenatedArg, getHyphenatedBoolean } from "../output/context.js";
11
10
  import { buildRegistryIndex, writeRegistryIndex } from "../registry/build-index.js";
12
11
  import { searchRegistry } from "./read/registry-search.js";
13
12
  export const registryCommand = defineCommand({
14
13
  meta: { name: "registry", description: "Manage stash registries" },
15
14
  subCommands: {
16
- list: defineCommand({
15
+ list: defineJsonCommand({
17
16
  meta: { name: "list", description: "List configured registries" },
18
- run() {
19
- return runWithJsonErrors(() => {
20
- const config = loadUserConfig();
21
- const registries = config.registries ?? DEFAULT_CONFIG.registries;
22
- output("registry-list", { registries });
23
- });
17
+ async run() {
18
+ const config = loadUserConfig();
19
+ const registries = config.registries ?? DEFAULT_CONFIG.registries;
20
+ output("registry-list", { registries });
24
21
  },
25
22
  }),
26
- add: defineCommand({
23
+ add: defineJsonCommand({
27
24
  meta: { name: "add", description: "Add a registry by URL" },
28
25
  args: {
29
26
  url: { type: "positional", description: "Registry index URL", required: true },
@@ -36,75 +33,71 @@ export const registryCommand = defineCommand({
36
33
  default: false,
37
34
  },
38
35
  },
39
- run({ args }) {
40
- return runWithJsonErrors(() => {
41
- if (!args.url.startsWith("http")) {
42
- throw new UsageError("Registry URL must start with http:// or https://");
43
- }
44
- if (args.url.startsWith("http://")) {
45
- const allowInsecure = getHyphenatedBoolean(args, "allow-insecure");
46
- if (!allowInsecure) {
47
- throw new UsageError("Registry URL uses plain HTTP (not HTTPS). An on-path attacker could substitute a malicious index. " +
48
- "Use https:// or pass --allow-insecure if you have explicitly accepted the risk.");
49
- }
50
- warn("Warning: registry URL uses plain HTTP (not HTTPS). --allow-insecure was set; an on-path attacker could substitute a malicious index.");
36
+ async run({ args }) {
37
+ if (!args.url.startsWith("http")) {
38
+ throw new UsageError("Registry URL must start with http:// or https://");
39
+ }
40
+ if (args.url.startsWith("http://")) {
41
+ const allowInsecure = args["allow-insecure"];
42
+ if (!allowInsecure) {
43
+ throw new UsageError("Registry URL uses plain HTTP (not HTTPS). An on-path attacker could substitute a malicious index. " +
44
+ "Use https:// or pass --allow-insecure if you have explicitly accepted the risk.");
51
45
  }
52
- const config = loadUserConfig();
53
- const registries = [...(config.registries ?? [])];
54
- // Deduplicate by URL
55
- if (registries.some((r) => r.url === args.url)) {
56
- output("registry-add", { registries, added: false, message: "Registry URL already configured" });
57
- return;
46
+ warn("Warning: registry URL uses plain HTTP (not HTTPS). --allow-insecure was set; an on-path attacker could substitute a malicious index.");
47
+ }
48
+ const config = loadUserConfig();
49
+ const registries = [...(config.registries ?? [])];
50
+ // Deduplicate by URL
51
+ if (registries.some((r) => r.url === args.url)) {
52
+ output("registry-add", { registries, added: false, message: "Registry URL already configured" });
53
+ return;
54
+ }
55
+ const entry = { url: args.url };
56
+ if (args.name)
57
+ entry.name = args.name;
58
+ if (args.provider)
59
+ entry.provider = args.provider;
60
+ if (args.options) {
61
+ try {
62
+ entry.options = JSON.parse(args.options);
58
63
  }
59
- const entry = { url: args.url };
60
- if (args.name)
61
- entry.name = args.name;
62
- if (args.provider)
63
- entry.provider = args.provider;
64
- if (args.options) {
65
- try {
66
- entry.options = JSON.parse(args.options);
67
- }
68
- catch {
69
- throw new UsageError("--options must be valid JSON");
70
- }
64
+ catch {
65
+ throw new UsageError("--options must be valid JSON");
71
66
  }
72
- registries.push(entry);
73
- saveConfig({ ...config, registries });
74
- output("registry-add", { registries, added: true });
75
- });
67
+ }
68
+ registries.push(entry);
69
+ saveConfig({ ...config, registries });
70
+ output("registry-add", { registries, added: true });
76
71
  },
77
72
  }),
78
- remove: defineCommand({
73
+ remove: defineJsonCommand({
79
74
  meta: { name: "remove", description: "Remove a registry by URL or name" },
80
75
  args: {
81
76
  target: { type: "positional", description: "Registry URL or name to remove", required: true },
82
77
  yes: { type: "boolean", alias: "y", description: "Skip confirmation prompt", default: false },
83
78
  },
84
- run({ args }) {
85
- return runWithJsonErrors(async () => {
86
- const config = loadUserConfig();
87
- const registries = [...(config.registries ?? [])];
88
- const idx = registries.findIndex((r) => r.url === args.target || r.name === args.target);
89
- if (idx === -1) {
90
- output("registry-remove", { registries, removed: false, message: "No matching registry found" });
91
- return;
92
- }
93
- const { confirmDestructive } = await import("../cli/confirm.js");
94
- const confirmed = await confirmDestructive(`Remove registry "${args.target}"? This cannot be undone.`, {
95
- yes: args.yes === true,
96
- });
97
- if (!confirmed) {
98
- process.stderr.write("Aborted.\n");
99
- return;
100
- }
101
- const removed = registries.splice(idx, 1)[0];
102
- saveConfig({ ...config, registries });
103
- output("registry-remove", { registries, removed: true, entry: removed });
79
+ async run({ args }) {
80
+ const config = loadUserConfig();
81
+ const registries = [...(config.registries ?? [])];
82
+ const idx = registries.findIndex((r) => r.url === args.target || r.name === args.target);
83
+ if (idx === -1) {
84
+ output("registry-remove", { registries, removed: false, message: "No matching registry found" });
85
+ return;
86
+ }
87
+ const { confirmDestructive } = await import("../cli/confirm.js");
88
+ const confirmed = await confirmDestructive(`Remove registry "${args.target}"? This cannot be undone.`, {
89
+ yes: args.yes === true,
104
90
  });
91
+ if (!confirmed) {
92
+ process.stderr.write("Aborted.\n");
93
+ return;
94
+ }
95
+ const removed = registries.splice(idx, 1)[0];
96
+ saveConfig({ ...config, registries });
97
+ output("registry-remove", { registries, removed: true, entry: removed });
105
98
  },
106
99
  }),
107
- search: defineCommand({
100
+ search: defineJsonCommand({
108
101
  meta: { name: "search", description: "Search enabled registries for stashes" },
109
102
  args: {
110
103
  query: { type: "positional", description: "Search query", required: true },
@@ -112,14 +105,12 @@ export const registryCommand = defineCommand({
112
105
  assets: { type: "boolean", description: "Include asset-level search results", default: false },
113
106
  },
114
107
  async run({ args }) {
115
- await runWithJsonErrors(async () => {
116
- const limitRaw = parsePositiveIntFlag(args.limit ?? undefined);
117
- const result = await searchRegistry(args.query, { limit: limitRaw, includeAssets: args.assets });
118
- output("registry-search", result);
119
- });
108
+ const limitRaw = parsePositiveIntFlag(args.limit ?? undefined);
109
+ const result = await searchRegistry(args.query, { limit: limitRaw, includeAssets: args.assets });
110
+ output("registry-search", result);
120
111
  },
121
112
  }),
122
- "build-index": defineCommand({
113
+ "build-index": defineJsonCommand({
123
114
  meta: { name: "build-index", description: "Build a v2 registry index from discovery and manual entries" },
124
115
  args: {
125
116
  out: { type: "string", description: "Output path for the generated index" },
@@ -128,21 +119,19 @@ export const registryCommand = defineCommand({
128
119
  "github-api": { type: "string", description: "Override GitHub API base URL" },
129
120
  },
130
121
  async run({ args }) {
131
- await runWithJsonErrors(async () => {
132
- const result = await buildRegistryIndex({
133
- manualEntriesPath: args.manual,
134
- npmRegistryBase: getHyphenatedArg(args, "npm-registry"),
135
- githubApiBase: getHyphenatedArg(args, "github-api"),
136
- });
137
- const outPath = writeRegistryIndex(result.index, args.out);
138
- output("registry-build-index", {
139
- outPath,
140
- version: result.index.version,
141
- updatedAt: result.index.updatedAt,
142
- totalKits: result.counts.total,
143
- counts: result.counts,
144
- manualEntriesPath: result.paths.manualEntriesPath,
145
- });
122
+ const result = await buildRegistryIndex({
123
+ manualEntriesPath: args.manual,
124
+ npmRegistryBase: args["npm-registry"],
125
+ githubApiBase: args["github-api"],
126
+ });
127
+ const outPath = writeRegistryIndex(result.index, args.out);
128
+ output("registry-build-index", {
129
+ outPath,
130
+ version: result.index.version,
131
+ updatedAt: result.index.updatedAt,
132
+ totalKits: result.counts.total,
133
+ counts: result.counts,
134
+ manualEntriesPath: result.paths.manualEntriesPath,
146
135
  });
147
136
  },
148
137
  }),
@@ -12,25 +12,24 @@ import { serializeFrontmatter } from "../core/asset/asset-serialize.js";
12
12
  import { toErrorMessage, tryReadStdinText } from "../core/common.js";
13
13
  import { getDefaultLlmConfig, loadConfig } from "../core/config/config.js";
14
14
  import { UsageError } from "../core/errors.js";
15
+ import { DURATION_UNITS, parseDuration as parseDurationSpec } from "../core/time.js";
15
16
  import { warn } from "../core/warn.js";
16
17
  import { SCOPE_KEYS } from "../indexer/passes/metadata.js";
17
18
  import { parseFlagValue } from "../output/context.js";
18
19
  /**
19
20
  * Parse a shorthand duration string to a number of milliseconds.
20
- * Supports: `30d` (days), `12h` (hours), `6m` (months, approximated as 30d).
21
+ * Supports the CLI-wide canonical grammar: `30d` (days), `12h` (hours),
22
+ * `5m` (minutes), `3M` (months, approximated as 30d).
21
23
  */
22
24
  export function parseDuration(s) {
23
- const match = s.trim().match(/^(\d+)([dhm])$/i);
24
- if (!match)
25
- throw new UsageError(`Invalid --expires format "${s}". Use shorthand like 30d, 12h, or 6m.`, "INVALID_FLAG_VALUE");
26
- const n = Number(match[1]);
27
- const unit = match[2].toLowerCase();
28
- if (unit === "d")
29
- return n * 24 * 60 * 60 * 1000;
30
- if (unit === "h")
31
- return n * 60 * 60 * 1000;
32
- // 'm' = months, approximated as 30 days
33
- return n * 30 * 24 * 60 * 60 * 1000;
25
+ // Canonical CLI unit grammar: `m` = minutes, `M` = months. Not lower-cased,
26
+ // so case distinguishes the two (`5m` = 5 minutes, `5M` = 5 months). See
27
+ // core/time.ts DURATION_UNITS.
28
+ const ms = parseDurationSpec(s.trim(), DURATION_UNITS);
29
+ if (ms === null) {
30
+ throw new UsageError(`Invalid --expires format "${s}". Use shorthand like 30d, 12h, 5m, or 3M.`, "INVALID_FLAG_VALUE");
31
+ }
32
+ return ms;
34
33
  }
35
34
  /**
36
35
  * Build a YAML frontmatter block from memory metadata.