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
@@ -1,3 +1,6 @@
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
1
4
  /**
2
5
  * `akm show` — entry point.
3
6
  *
@@ -9,33 +12,31 @@
9
12
  * edit-hints, summary-detail truncation) lives below in this file. The flow:
10
13
  *
11
14
  * 1. Special-case wiki-root refs (`wiki:<name>` with no page path).
12
- * 2. Ask `indexer.lookup(ref)` for the row in the FTS index.
13
- * 3. Fall back to the on-disk type-dir resolver only when the index has
14
- * no matching row — covers the "indexed yet?" gap when the user has
15
- * just added a file and not run `akm index`.
15
+ * 2. Auto-index when stale so the index is current.
16
+ * 3. Ask `indexer.lookup(ref)` for the row in the FTS index.
16
17
  * 4. Render the file via the matcher/renderer pipeline.
17
- *
18
- * Step (2) is the v1 spec change: reading is the indexer's job. Step (3) is a
19
- * pragmatic safety net (NOT remote provider fallback, which the spec
20
- * forbids — "Show: Local FTS5 index only. No remote provider fallback.").
21
18
  */
22
19
  import fs from "node:fs";
23
20
  import path from "node:path";
24
21
  import { parseAssetRef } from "../core/asset-ref";
22
+ import { asNonEmptyString } from "../core/common";
25
23
  import { loadConfig } from "../core/config";
26
- import { NotFoundError, UsageError } from "../core/errors";
24
+ import { NotFoundError, rethrowIfTestIsolationError, UsageError } from "../core/errors";
27
25
  import { appendEvent, readEvents } from "../core/events";
28
- import { parseFrontmatter, toStringOrUndefined } from "../core/frontmatter";
26
+ import { parseFrontmatter } from "../core/frontmatter";
29
27
  import { closeDatabase, findEntryIdByRef, openExistingDatabase } from "../indexer/db";
28
+ import { ensureIndex } from "../indexer/ensure-index";
30
29
  import { buildFileContext, buildRenderContext, getRenderer, runMatchers } from "../indexer/file-context";
30
+ import { listRelatedPathsForFile } from "../indexer/graph-boost";
31
31
  import { lookup } from "../indexer/indexer";
32
+ import { resolveAssetPath } from "../indexer/path-resolver";
32
33
  import { buildEditHint, findSourceForPath, isEditable, resolveSourceEntries } from "../indexer/search-source";
33
34
  import { insertUsageEvent } from "../indexer/usage-events";
34
35
  import { resolveSourcesForOrigin } from "../registry/origin-resolve";
35
36
  // Eagerly import source providers to trigger self-registration.
36
37
  import "../sources/providers/index";
37
- import { resolveAssetPath } from "../sources/resolve";
38
38
  import { getActiveWorkflowRun } from "../workflows/runs";
39
+ import { getCurrentWorkflowScopeKey } from "../workflows/scope-key";
39
40
  /**
40
41
  * Show a wiki root (no page path) — returns the same payload as
41
42
  * `akm wiki show <name>`.
@@ -128,7 +129,12 @@ export async function akmShowUnified(input) {
128
129
  new NotFoundError(`Wiki not found: ${parsed.name}. Run \`akm wiki create ${parsed.name}\` to create it.`));
129
130
  }
130
131
  }
131
- // Try local filesystem (FTS5 index lookup, then on-disk fallback)
132
+ // Auto-index when stale so the index is current before lookup.
133
+ const allSources = resolveSourceEntries();
134
+ if (allSources.length > 0) {
135
+ await ensureIndex(allSources[0].path);
136
+ }
137
+ // Try local filesystem (FTS5 index lookup)
132
138
  const result = await showLocal(input);
133
139
  // Scope filter narrows resolution: if --scope was supplied, the asset's
134
140
  // frontmatter scope must satisfy every supplied key. We re-read the file
@@ -139,7 +145,7 @@ export async function akmShowUnified(input) {
139
145
  }
140
146
  // Count prior shows of this ref before logging the current one.
141
147
  const priorShowCount = recentShowCount(ref);
142
- logShowEvent(ref);
148
+ logShowEvent(ref, undefined, input.eventSource);
143
149
  if (priorShowCount >= 2) {
144
150
  // Agent has shown this same asset 3+ times — inject a loop-break hint.
145
151
  result.showLoopWarning = priorShowCount + 1;
@@ -177,7 +183,7 @@ function enforceScopeOrThrow(filePath, ref, scope) {
177
183
  for (const [key, expectedValue] of expected) {
178
184
  if (expectedValue === undefined)
179
185
  continue;
180
- const actual = toStringOrUndefined(fm[`scope_${key}`]);
186
+ const actual = asNonEmptyString(fm[`scope_${key}`]);
181
187
  if (actual !== expectedValue) {
182
188
  throw new NotFoundError(`Asset "${ref}" exists but is out of scope (expected scope_${key}="${expectedValue}").`);
183
189
  }
@@ -189,18 +195,51 @@ function enforceScopeOrThrow(filePath, ref, scope) {
189
195
  */
190
196
  function recentShowCount(ref) {
191
197
  try {
192
- const { events } = readEvents({ type: "show", ref });
198
+ const { events } = readEvents({
199
+ type: "show",
200
+ ref,
201
+ since: new Date(Date.now() - 60 * 60 * 1000).toISOString(),
202
+ });
193
203
  return events.length;
194
204
  }
195
205
  catch {
196
206
  return 0;
197
207
  }
198
208
  }
199
- function logShowEvent(ref, existingDb) {
209
+ function logShowEvent(ref, existingDb, eventSource = "user") {
200
210
  // Emit a structured event to events.jsonl so workflow-trace consumers
201
211
  // detect akm show invocations without relying on stdout scraping.
202
212
  const parsed = parseAssetRef(ref);
203
213
  appendEvent({ eventType: "show", ref, metadata: { type: parsed.type, name: parsed.name } });
214
+ // Detect if this show is a selection from a recent search result.
215
+ try {
216
+ // D7: bound the query to the last 60 s so we never scan unbounded history
217
+ const { events: recentSearches } = readEvents({
218
+ type: "search",
219
+ since: new Date(Date.now() - 60_000).toISOString(),
220
+ });
221
+ const cutoffMs = Date.now() - 60_000;
222
+ const matchingSearch = [...recentSearches].reverse().find((e) => {
223
+ if (!e.ts || new Date(e.ts).getTime() < cutoffMs)
224
+ return false;
225
+ const refs = e.metadata?.resultRefs ?? [];
226
+ return refs.includes(ref);
227
+ });
228
+ if (matchingSearch) {
229
+ appendEvent({
230
+ eventType: "select",
231
+ ref,
232
+ metadata: {
233
+ query: matchingSearch.metadata?.query,
234
+ searchTs: matchingSearch.ts,
235
+ rankPosition: (matchingSearch.metadata?.resultRefs ?? []).indexOf(ref),
236
+ },
237
+ });
238
+ }
239
+ }
240
+ catch {
241
+ /* fire-and-forget — select is best-effort */
242
+ }
204
243
  try {
205
244
  const db = existingDb ?? openExistingDatabase();
206
245
  try {
@@ -208,6 +247,7 @@ function logShowEvent(ref, existingDb) {
208
247
  event_type: "show",
209
248
  entry_ref: ref,
210
249
  entry_id: findEntryIdByRef(db, ref),
250
+ source: eventSource,
211
251
  });
212
252
  }
213
253
  finally {
@@ -215,43 +255,10 @@ function logShowEvent(ref, existingDb) {
215
255
  closeDatabase(db);
216
256
  }
217
257
  }
218
- catch {
219
- /* fire-and-forget */
220
- }
221
- }
222
- /**
223
- * Resolve an asset path to a file via:
224
- * 1. `indexer.lookup(ref)` — the spec's primary path (§6.2).
225
- * 2. On-disk type-dir traversal — fallback for files not yet indexed.
226
- *
227
- * Returns `undefined` if neither path finds a match.
228
- */
229
- async function resolvePathViaIndexThenDisk(parsed, searchSourceDirs) {
230
- // Step 1: indexer
231
- try {
232
- const entry = await lookup(parsed);
233
- if (entry) {
234
- return { assetPath: entry.filePath };
235
- }
236
- }
237
258
  catch (err) {
238
- // Index unavailable (e.g. DB doesn't exist yet) — fall back to disk walk.
239
- if (!(err instanceof NotFoundError)) {
240
- // continue to disk fallback
241
- }
242
- }
243
- // Step 2: on-disk type-dir traversal
244
- let lastError;
245
- for (const dir of searchSourceDirs) {
246
- try {
247
- const assetPath = await resolveAssetPath(dir, parsed.type, parsed.name);
248
- return { assetPath, lastError };
249
- }
250
- catch (err) {
251
- lastError = err instanceof Error ? err : new Error(String(err));
252
- }
259
+ rethrowIfTestIsolationError(err);
260
+ /* fire-and-forget */
253
261
  }
254
- return lastError ? { assetPath: "", lastError } : undefined;
255
262
  }
256
263
  /** @internal Use akmShowUnified() for all external callers. */
257
264
  export async function showLocal(input) {
@@ -273,13 +280,11 @@ export async function showLocal(input) {
273
280
  }
274
281
  }
275
282
  if (!assetPath) {
276
- const resolved = await resolvePathViaIndexThenDisk(parsed, allSourceDirs);
277
- if (resolved?.assetPath) {
278
- assetPath = resolved.assetPath;
279
- }
280
- else if (resolved?.lastError) {
281
- lastError = resolved.lastError;
282
- }
283
+ const resolvedAssetPath = await resolveAssetPath(parsed, {
284
+ stashDir: input.stashDir,
285
+ mode: "index-first",
286
+ });
287
+ assetPath = resolvedAssetPath ?? undefined;
283
288
  }
284
289
  if (!assetPath && parsed.origin && searchSources.length === 0) {
285
290
  const installCmd = `akm add ${parsed.origin}`;
@@ -318,8 +323,24 @@ export async function showLocal(input) {
318
323
  origin: source?.registryId ?? null,
319
324
  editable,
320
325
  ...(!editable ? { editHint: buildEditHint(assetPath, parsed.type, parsed.name, source?.registryId) } : {}),
326
+ related: (() => {
327
+ let db;
328
+ try {
329
+ db = openExistingDatabase();
330
+ const related = listRelatedPathsForFile(sourceStashDir, assetPath, 5, db);
331
+ return { total: related.length, hits: related };
332
+ }
333
+ catch (err) {
334
+ rethrowIfTestIsolationError(err);
335
+ return { total: 0, hits: [] };
336
+ }
337
+ finally {
338
+ if (db)
339
+ closeDatabase(db);
340
+ }
341
+ })(),
321
342
  };
322
- const activeRun = getActiveWorkflowRun();
343
+ const activeRun = await getActiveWorkflowRun(getCurrentWorkflowScopeKey());
323
344
  if (activeRun) {
324
345
  fullResponse.activeRun = activeRun;
325
346
  }
@@ -379,7 +400,7 @@ function buildSummaryResponse(full, assetPath) {
379
400
  const textContent = full.content ?? full.template ?? full.prompt;
380
401
  if (textContent && !description) {
381
402
  const parsed = parseFrontmatter(textContent);
382
- description = toStringOrUndefined(parsed.data.description);
403
+ description = asNonEmptyString(parsed.data.description);
383
404
  }
384
405
  }
385
406
  const summary = {
@@ -396,3 +417,80 @@ function buildSummaryResponse(full, assetPath) {
396
417
  };
397
418
  return summary;
398
419
  }
420
+ // ── argv normalisation ───────────────────────────────────────────────────────
421
+ const SHOW_VIEW_MODES = new Set(["toc", "frontmatter", "full", "section", "lines"]);
422
+ /**
423
+ * Normalize argv so positional view-mode arguments after the asset ref
424
+ * are rewritten into internal flags that citty can parse.
425
+ *
426
+ * Converts:
427
+ * akm show knowledge:guide.md toc → akm show knowledge:guide.md --akmView toc
428
+ * akm show knowledge:guide.md section Auth → akm show knowledge:guide.md --akmView section --akmHeading Auth
429
+ * akm show knowledge:guide.md lines 1 50 → akm show knowledge:guide.md --akmView lines --akmStart 1 --akmEnd 50
430
+ *
431
+ * Legacy `--view` is intentionally unsupported.
432
+ * Returns a new array; the input is never modified.
433
+ */
434
+ export function normalizeShowArgv(argv) {
435
+ // argv[0]=bun argv[1]=script argv[2]=subcommand argv[3]=ref argv[4..]=rest
436
+ if (argv[2] !== "show")
437
+ return argv;
438
+ if (argv[3] === "proposal")
439
+ return argv;
440
+ if (argv.includes("--view") || argv.includes("--heading") || argv.includes("--start") || argv.includes("--end")) {
441
+ 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"`.');
442
+ }
443
+ // Separate global flags from positional/show-specific args
444
+ const prefix = argv.slice(0, 3); // [bun, script, show]
445
+ const rest = argv.slice(3);
446
+ const globalFlags = [];
447
+ const showArgs = [];
448
+ for (let i = 0; i < rest.length; i++) {
449
+ const arg = rest[i];
450
+ if (arg === "--quiet" ||
451
+ arg === "-q" ||
452
+ arg === "--verbose" ||
453
+ arg === "--for-agent" ||
454
+ arg === "--for-agent=true") {
455
+ globalFlags.push(arg);
456
+ continue;
457
+ }
458
+ if (arg.startsWith("--format=") || arg.startsWith("--detail=") || arg.startsWith("--shape=")) {
459
+ globalFlags.push(arg);
460
+ continue;
461
+ }
462
+ if (arg === "--format" || arg === "--detail" || arg === "--shape") {
463
+ globalFlags.push(arg);
464
+ if (rest[i + 1] !== undefined) {
465
+ globalFlags.push(rest[i + 1]);
466
+ i++;
467
+ }
468
+ continue;
469
+ }
470
+ showArgs.push(arg);
471
+ }
472
+ // showArgs[0] = ref, showArgs[1] = potential view mode, showArgs[2..] = view params
473
+ const ref = showArgs[0];
474
+ const viewMode = showArgs[1];
475
+ if (!ref || !viewMode || !SHOW_VIEW_MODES.has(viewMode)) {
476
+ return argv;
477
+ }
478
+ const result = [...prefix, ref, "--akmView", viewMode];
479
+ if (viewMode === "section") {
480
+ // Next arg is the heading name; pass empty string when missing so the
481
+ // show handler can produce a clear "section not found" error.
482
+ const heading = showArgs[2] ?? "";
483
+ result.push("--akmHeading", heading);
484
+ }
485
+ else if (viewMode === "lines") {
486
+ // Next two args are start and end
487
+ const start = showArgs[2];
488
+ const end = showArgs[3];
489
+ if (start)
490
+ result.push("--akmStart", start);
491
+ if (end)
492
+ result.push("--akmEnd", end);
493
+ }
494
+ result.push(...globalFlags);
495
+ return result;
496
+ }
@@ -1,9 +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 fs from "node:fs";
2
5
  import path from "node:path";
3
6
  import { isHttpUrl, resolveStashDir } from "../core/common";
4
- import { loadConfig, loadUserConfig, saveConfig } from "../core/config";
7
+ import { getSources, loadConfig, loadUserConfig, saveConfig } from "../core/config";
5
8
  import { ConfigError, UsageError } from "../core/errors";
6
- import { warn } from "../core/warn";
7
9
  import { akmIndex } from "../indexer/indexer";
8
10
  import { upsertLockEntry } from "../integrations/lockfile";
9
11
  import { parseRegistryRef } from "../registry/resolve";
@@ -11,7 +13,6 @@ import { detectStashRoot } from "../sources/providers/provider-utils";
11
13
  import { syncFromRef } from "../sources/providers/sync-from-ref";
12
14
  import { ensureWebsiteMirror, validateWebsiteInputUrl } from "../sources/website-ingest";
13
15
  import { ensureWikiNameAvailable, validateWikiName } from "../wiki/wiki";
14
- import { auditInstallCandidate, deriveRegistryLabels, enforceRegistryInstallPolicy, formatInstallAuditFailure, } from "./install-audit";
15
16
  const VALID_OVERRIDE_TYPES = new Set(["wiki"]);
16
17
  export async function akmAdd(input) {
17
18
  const ref = input.ref.trim();
@@ -38,16 +39,13 @@ export async function akmAdd(input) {
38
39
  try {
39
40
  const parsed = parseRegistryRef(ref);
40
41
  if (parsed.source === "local") {
41
- if (input.trustThisInstall) {
42
- warn("--trust has no effect on local directory sources; the install audit is not run for local paths.");
43
- }
44
42
  return addLocalSource(ref, parsed.sourcePath, stashDir, wikiName, input.name);
45
43
  }
46
44
  }
47
45
  catch {
48
46
  // Not a local ref — fall through to registry install
49
47
  }
50
- return addRegistryStash(ref, stashDir, input.trustThisInstall, input.writable, wikiName);
48
+ return addRegistryStash(ref, stashDir, input.writable, wikiName);
51
49
  }
52
50
  export async function registerWikiSource(input) {
53
51
  const stashDir = resolveStashDir();
@@ -59,7 +57,6 @@ export async function registerWikiSource(input) {
59
57
  name,
60
58
  overrideType: "wiki",
61
59
  options: input.options,
62
- trustThisInstall: input.trustThisInstall,
63
60
  writable: input.writable,
64
61
  });
65
62
  }
@@ -74,7 +71,7 @@ async function addLocalSource(ref, sourcePath, stashDir, wikiName, explicitName)
74
71
  // Derive the canonical name: explicit --name wins, then wiki name, then readable path.
75
72
  const derivedName = explicitName ?? wikiName ?? toReadableId(resolvedPath);
76
73
  // Check for duplicates in sources[]
77
- const sources = [...(config.sources ?? config.stashes ?? [])];
74
+ const sources = [...getSources(config)];
78
75
  const existing = sources.find((s) => s.type === "filesystem" && s.path && path.resolve(s.path) === resolvedPath);
79
76
  let persistedEntry;
80
77
  if (!existing) {
@@ -85,7 +82,7 @@ async function addLocalSource(ref, sourcePath, stashDir, wikiName, explicitName)
85
82
  ...(wikiName ? { wikiName } : {}),
86
83
  };
87
84
  sources.push(persistedEntry);
88
- saveConfig({ ...config, sources, stashes: undefined });
85
+ saveConfig({ ...config, sources });
89
86
  }
90
87
  else {
91
88
  let changed = false;
@@ -99,7 +96,7 @@ async function addLocalSource(ref, sourcePath, stashDir, wikiName, explicitName)
99
96
  changed = true;
100
97
  }
101
98
  if (changed)
102
- saveConfig({ ...config, sources, stashes: undefined });
99
+ saveConfig({ ...config, sources });
103
100
  persistedEntry = existing;
104
101
  }
105
102
  const index = await akmIndex({ stashDir });
@@ -116,7 +113,7 @@ async function addLocalSource(ref, sourcePath, stashDir, wikiName, explicitName)
116
113
  ...(persistedEntry.wikiName ? { wiki: persistedEntry.wikiName } : {}),
117
114
  },
118
115
  config: {
119
- sourceCount: (updatedConfig.sources ?? updatedConfig.stashes ?? []).length,
116
+ sourceCount: getSources(updatedConfig).length,
120
117
  installedKitCount: updatedConfig.installed?.length ?? 0,
121
118
  },
122
119
  index: {
@@ -131,7 +128,7 @@ async function addLocalSource(ref, sourcePath, stashDir, wikiName, explicitName)
131
128
  async function addWebsiteSource(ref, stashDir, name, options, wikiName) {
132
129
  const normalizedUrl = validateWebsiteInputUrl(ref);
133
130
  const config = loadUserConfig();
134
- const sources = [...(config.sources ?? config.stashes ?? [])];
131
+ const sources = [...getSources(config)];
135
132
  let entry = sources.find((stash) => stash.type === "website" && stash.url === normalizedUrl);
136
133
  if (!entry) {
137
134
  entry = {
@@ -142,7 +139,7 @@ async function addWebsiteSource(ref, stashDir, name, options, wikiName) {
142
139
  ...(wikiName ? { wikiName } : {}),
143
140
  };
144
141
  sources.push(entry);
145
- saveConfig({ ...config, sources, stashes: undefined });
142
+ saveConfig({ ...config, sources });
146
143
  }
147
144
  else {
148
145
  let changed = false;
@@ -155,7 +152,7 @@ async function addWebsiteSource(ref, stashDir, name, options, wikiName) {
155
152
  changed = true;
156
153
  }
157
154
  if (changed)
158
- saveConfig({ ...config, sources, stashes: undefined });
155
+ saveConfig({ ...config, sources });
159
156
  }
160
157
  const cachePaths = await ensureWebsiteMirror(entry, { requireStashDir: true });
161
158
  const index = await akmIndex({ stashDir });
@@ -172,7 +169,7 @@ async function addWebsiteSource(ref, stashDir, name, options, wikiName) {
172
169
  ...(entry.wikiName ? { wiki: entry.wikiName } : {}),
173
170
  },
174
171
  config: {
175
- sourceCount: (updatedConfig.sources ?? updatedConfig.stashes ?? []).length,
172
+ sourceCount: getSources(updatedConfig).length,
176
173
  installedKitCount: updatedConfig.installed?.length ?? 0,
177
174
  },
178
175
  index: {
@@ -186,38 +183,14 @@ async function addWebsiteSource(ref, stashDir, name, options, wikiName) {
186
183
  }
187
184
  /**
188
185
  * Install a stash from a registry (npm, github, git) by dispatching to the
189
- * matching syncable provider, then running the post-sync install audit and
190
- * persisting the lock entry.
186
+ * matching syncable provider and persisting the lock entry.
191
187
  */
192
- async function addRegistryStash(ref, stashDir, trustThisInstall, writable, wikiName) {
188
+ async function addRegistryStash(ref, stashDir, writable, wikiName) {
193
189
  const parsedRef = parseRegistryRef(ref);
194
190
  if (writable === true && parsedRef.source !== "git") {
195
191
  throw new ConfigError("writable: true is only supported on filesystem and git sources", "INVALID_CONFIG_FILE");
196
192
  }
197
- // Pre-sync registry-policy enforcement uses just the parsed ref (no fetch needed),
198
- // so we keep parity with the historical behavior where `enforceRegistryInstallPolicy`
199
- // ran before `extractTarGzSecure` etc.
200
- const config = loadConfig();
201
- const synced = await syncFromRef(ref, { trustThisInstall, writable });
202
- const registryLabels = deriveRegistryLabels({
203
- source: synced.source,
204
- ref: synced.ref,
205
- artifactUrl: synced.artifactUrl,
206
- });
207
- enforceRegistryInstallPolicy(registryLabels, config, ref);
208
- // Post-sync hook: install audit. Throws when blocked unless `--trust` is set
209
- // (in which case the audit report still surfaces in the response).
210
- const audit = auditInstallCandidate({
211
- rootDir: synced.extractedDir,
212
- source: synced.source,
213
- ref: synced.ref,
214
- registryLabels,
215
- config,
216
- trustThisInstall,
217
- });
218
- if (audit.blocked) {
219
- throw new Error(formatInstallAuditFailure(synced.ref, audit));
220
- }
193
+ const synced = await syncFromRef(ref, { writable });
221
194
  const replaced = (loadConfig().installed ?? []).find((entry) => entry.id === synced.id);
222
195
  const updatedConfig = upsertInstalledRegistryEntry({
223
196
  id: synced.id,
@@ -265,10 +238,9 @@ async function addRegistryStash(ref, stashDir, trustThisInstall, writable, wikiN
265
238
  cacheDir: synced.cacheDir,
266
239
  extractedDir: synced.extractedDir,
267
240
  installedAt: synced.syncedAt,
268
- audit,
269
241
  },
270
242
  config: {
271
- sourceCount: (updatedConfig.sources ?? updatedConfig.stashes ?? []).length,
243
+ sourceCount: getSources(updatedConfig).length,
272
244
  installedKitCount: updatedConfig.installed?.length ?? 0,
273
245
  },
274
246
  index: {
@@ -1,3 +1,6 @@
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
1
4
  import fs from "node:fs";
2
5
  import path from "node:path";
3
6
  import { makeAssetRef, parseAssetRef } from "../core/asset-ref";
@@ -1,5 +1,9 @@
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
- import { loadConfig, loadUserConfig, saveConfig } from "../core/config";
5
+ import { isRemoteUrl } from "../core/common";
6
+ import { getSources, loadConfig, loadUserConfig, saveConfig } from "../core/config";
3
7
  import { ConfigError, UsageError } from "../core/errors";
4
8
  import { resolveSourceEntries } from "../indexer/search-source";
5
9
  // ── Operations ──────────────────────────────────────────────────────────────
@@ -19,14 +23,9 @@ export function addStash(opts) {
19
23
  throw new ConfigError("writable: true is only supported on filesystem and git sources", "INVALID_CONFIG_FILE");
20
24
  }
21
25
  const config = loadUserConfig();
22
- const sources = [...(config.sources ?? config.stashes ?? [])];
23
- const isRemoteUrl = target.startsWith("http://") ||
24
- target.startsWith("https://") ||
25
- target.startsWith("git@") ||
26
- target.startsWith("ssh://") ||
27
- target.startsWith("git://");
26
+ const sources = [...getSources(config)];
28
27
  let entry;
29
- if (isRemoteUrl) {
28
+ if (isRemoteUrl(target)) {
30
29
  if (!providerType) {
31
30
  throw new UsageError("--provider is required for URL sources (e.g. --provider git --provider website)");
32
31
  }
@@ -53,7 +52,7 @@ export function addStash(opts) {
53
52
  entry.name = name;
54
53
  }
55
54
  sources.push(entry);
56
- saveConfig({ ...config, sources, stashes: undefined });
55
+ saveConfig({ ...config, sources });
57
56
  return { sources, added: true, entry };
58
57
  }
59
58
  /**
@@ -62,16 +61,12 @@ export function addStash(opts) {
62
61
  */
63
62
  export function removeStash(target) {
64
63
  const config = loadUserConfig();
65
- const sources = [...(config.sources ?? config.stashes ?? [])];
66
- const isUrl = target.startsWith("http://") ||
67
- target.startsWith("https://") ||
68
- target.startsWith("git@") ||
69
- target.startsWith("ssh://") ||
70
- target.startsWith("git://");
71
- const resolvedPath = !isUrl ? path.resolve(target) : undefined;
64
+ const sources = [...getSources(config)];
65
+ const isUrlTarget = isRemoteUrl(target);
66
+ const resolvedPath = !isUrlTarget ? path.resolve(target) : undefined;
72
67
  // Try URL match first, then path, then name (most specific → least specific)
73
68
  let idx = -1;
74
- if (isUrl) {
69
+ if (isUrlTarget) {
75
70
  idx = sources.findIndex((s) => s.url === target);
76
71
  }
77
72
  if (idx === -1 && resolvedPath) {
@@ -84,7 +79,7 @@ export function removeStash(target) {
84
79
  return { sources, removed: false, message: "No matching source found" };
85
80
  }
86
81
  const removed = sources.splice(idx, 1)[0];
87
- saveConfig({ ...config, sources, stashes: undefined });
82
+ saveConfig({ ...config, sources });
88
83
  return { sources, removed: true, entry: removed };
89
84
  }
90
85
  /**
@@ -93,6 +88,6 @@ export function removeStash(target) {
93
88
  export function listStashes() {
94
89
  const config = loadConfig();
95
90
  const localSources = resolveSourceEntries();
96
- const sources = config.sources ?? config.stashes ?? [];
91
+ const sources = getSources(config);
97
92
  return { localSources, sources };
98
93
  }