akm-cli 0.7.5 → 0.8.0-rc.6

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 (236) hide show
  1. package/{.github/CHANGELOG.md → CHANGELOG.md} +113 -2
  2. package/README.md +20 -4
  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.js +1995 -551
  9. package/dist/commands/agent-dispatch.js +110 -0
  10. package/dist/commands/agent-support.js +68 -0
  11. package/dist/commands/completions.js +3 -0
  12. package/dist/commands/config-cli.js +130 -534
  13. package/dist/commands/consolidate.js +1531 -0
  14. package/dist/commands/curate.js +44 -3
  15. package/dist/commands/db-cli.js +23 -0
  16. package/dist/commands/distill-promotion-policy.js +660 -0
  17. package/dist/commands/distill.js +990 -75
  18. package/dist/commands/eval-cases.js +43 -0
  19. package/dist/commands/events.js +5 -23
  20. package/dist/commands/graph.js +477 -0
  21. package/dist/commands/health.js +400 -0
  22. package/dist/commands/help/help-accept.md +9 -0
  23. package/dist/commands/help/help-improve.md +77 -0
  24. package/dist/commands/help/help-proposals.md +15 -0
  25. package/dist/commands/help/help-propose.md +17 -0
  26. package/dist/commands/help/help-reject.md +8 -0
  27. package/dist/commands/history.js +54 -46
  28. package/dist/commands/improve-profiles.js +146 -0
  29. package/dist/commands/improve-result-file.js +103 -0
  30. package/dist/commands/improve.js +2175 -0
  31. package/dist/commands/info.js +5 -2
  32. package/dist/commands/init.js +50 -2
  33. package/dist/commands/installed-stashes.js +102 -139
  34. package/dist/commands/knowledge.js +136 -0
  35. package/dist/commands/lint/agent-linter.js +49 -0
  36. package/dist/commands/lint/base-linter.js +479 -0
  37. package/dist/commands/lint/command-linter.js +49 -0
  38. package/dist/commands/lint/default-linter.js +16 -0
  39. package/dist/commands/lint/index.js +183 -0
  40. package/dist/commands/lint/knowledge-linter.js +16 -0
  41. package/dist/commands/lint/markdown-insertion.js +343 -0
  42. package/dist/commands/lint/memory-linter.js +61 -0
  43. package/dist/commands/lint/registry.js +36 -0
  44. package/dist/commands/lint/skill-linter.js +45 -0
  45. package/dist/commands/lint/task-linter.js +50 -0
  46. package/dist/commands/lint/types.js +4 -0
  47. package/dist/commands/lint/vault-key-rules.js +139 -0
  48. package/dist/commands/lint/workflow-linter.js +56 -0
  49. package/dist/commands/lint.js +4 -0
  50. package/dist/commands/migration-help.js +5 -2
  51. package/dist/commands/proposal.js +66 -12
  52. package/dist/commands/propose.js +86 -31
  53. package/dist/commands/reflect.js +1119 -73
  54. package/dist/commands/registry-search.js +5 -2
  55. package/dist/commands/remember.js +69 -6
  56. package/dist/commands/schema-repair.js +203 -0
  57. package/dist/commands/search.js +115 -14
  58. package/dist/commands/self-update.js +3 -0
  59. package/dist/commands/show.js +144 -25
  60. package/dist/commands/source-add.js +17 -45
  61. package/dist/commands/source-clone.js +3 -0
  62. package/dist/commands/source-manage.js +14 -19
  63. package/dist/commands/tasks.js +438 -0
  64. package/dist/commands/url-checker.js +42 -0
  65. package/dist/commands/vault.js +130 -77
  66. package/dist/core/action-contributors.js +28 -0
  67. package/dist/core/asset-ref.js +7 -0
  68. package/dist/core/asset-registry.js +7 -16
  69. package/dist/core/asset-serialize.js +88 -0
  70. package/dist/core/asset-spec.js +22 -0
  71. package/dist/core/common.js +157 -0
  72. package/dist/core/concurrent.js +25 -0
  73. package/dist/core/config-io.js +347 -0
  74. package/dist/core/config-migration.js +625 -0
  75. package/dist/core/config-schema.js +501 -0
  76. package/dist/core/config-sources.js +108 -0
  77. package/dist/core/config-types.js +4 -0
  78. package/dist/core/config-walker.js +337 -0
  79. package/dist/core/config.js +327 -987
  80. package/dist/core/errors.js +40 -19
  81. package/dist/core/events.js +91 -138
  82. package/dist/core/file-lock.js +104 -0
  83. package/dist/core/frontmatter.js +3 -6
  84. package/dist/core/lesson-lint.js +3 -0
  85. package/dist/core/markdown.js +20 -0
  86. package/dist/core/memory-belief.js +62 -0
  87. package/dist/core/memory-contradiction-detect.js +274 -0
  88. package/dist/core/memory-improve.js +806 -0
  89. package/dist/core/parse.js +158 -0
  90. package/dist/core/paths.js +326 -14
  91. package/dist/core/proposal-quality-validators.js +364 -0
  92. package/dist/core/proposal-validators.js +69 -0
  93. package/dist/core/proposals.js +498 -42
  94. package/dist/core/state-db.js +927 -0
  95. package/dist/core/text-truncation.js +107 -0
  96. package/dist/core/time.js +54 -0
  97. package/dist/core/warn.js +62 -1
  98. package/dist/core/write-source.js +3 -0
  99. package/dist/indexer/db-backup.js +391 -0
  100. package/dist/indexer/db-search.js +152 -253
  101. package/dist/indexer/db.js +933 -103
  102. package/dist/indexer/ensure-index.js +64 -0
  103. package/dist/indexer/file-context.js +3 -0
  104. package/dist/indexer/graph-boost.js +376 -101
  105. package/dist/indexer/graph-db.js +391 -0
  106. package/dist/indexer/graph-dedup.js +95 -0
  107. package/dist/indexer/graph-extraction.js +550 -124
  108. package/dist/indexer/index-context.js +4 -0
  109. package/dist/indexer/indexer.js +506 -291
  110. package/dist/indexer/llm-cache.js +47 -0
  111. package/dist/indexer/manifest.js +3 -0
  112. package/dist/indexer/matchers.js +148 -160
  113. package/dist/indexer/memory-inference.js +99 -74
  114. package/dist/indexer/metadata-contributors.js +29 -0
  115. package/dist/indexer/metadata.js +255 -196
  116. package/dist/indexer/path-resolver.js +92 -0
  117. package/dist/indexer/project-context.js +192 -0
  118. package/dist/indexer/ranking-contributors.js +331 -0
  119. package/dist/indexer/ranking.js +81 -0
  120. package/dist/indexer/search-fields.js +5 -9
  121. package/dist/indexer/search-hit-enrichers.js +111 -0
  122. package/dist/indexer/search-source.js +44 -10
  123. package/dist/indexer/semantic-status.js +5 -16
  124. package/dist/indexer/staleness-detect.js +447 -0
  125. package/dist/indexer/usage-events.js +12 -9
  126. package/dist/indexer/walker.js +28 -0
  127. package/dist/integrations/agent/builders.js +135 -0
  128. package/dist/integrations/agent/config.js +122 -230
  129. package/dist/integrations/agent/detect.js +3 -0
  130. package/dist/integrations/agent/index.js +7 -13
  131. package/dist/integrations/agent/model-aliases.js +55 -0
  132. package/dist/integrations/agent/profiles.js +70 -5
  133. package/dist/integrations/agent/prompts.js +150 -74
  134. package/dist/integrations/agent/runner.js +151 -0
  135. package/dist/integrations/agent/sdk-runner.js +126 -0
  136. package/dist/integrations/agent/spawn.js +118 -23
  137. package/dist/integrations/github.js +3 -0
  138. package/dist/integrations/lockfile.js +32 -69
  139. package/dist/integrations/session-logs/index.js +68 -0
  140. package/dist/integrations/session-logs/providers/claude-code.js +59 -0
  141. package/dist/integrations/session-logs/providers/opencode.js +55 -0
  142. package/dist/integrations/session-logs/types.js +4 -0
  143. package/dist/llm/call-ai.js +62 -0
  144. package/dist/llm/client.js +72 -124
  145. package/dist/llm/embedder.js +3 -19
  146. package/dist/llm/embedders/cache.js +3 -7
  147. package/dist/llm/embedders/local.js +3 -0
  148. package/dist/llm/embedders/remote.js +20 -8
  149. package/dist/llm/embedders/types.js +3 -7
  150. package/dist/llm/feature-gate.js +89 -48
  151. package/dist/llm/graph-extract.js +676 -70
  152. package/dist/llm/index-passes.js +9 -23
  153. package/dist/llm/memory-infer.js +52 -71
  154. package/dist/llm/metadata-enhance.js +42 -29
  155. package/dist/llm/prompts/graph-extract-user-prompt.md +35 -0
  156. package/dist/output/cli-hints-full.md +281 -0
  157. package/dist/output/cli-hints-short.md +65 -0
  158. package/dist/output/cli-hints.js +5 -318
  159. package/dist/output/context.js +3 -0
  160. package/dist/output/renderers.js +223 -256
  161. package/dist/output/shapes.js +150 -105
  162. package/dist/output/text.js +318 -30
  163. package/dist/registry/build-index.js +3 -0
  164. package/dist/registry/create-provider-registry.js +3 -0
  165. package/dist/registry/factory.js +3 -0
  166. package/dist/registry/origin-resolve.js +3 -0
  167. package/dist/registry/providers/index.js +3 -0
  168. package/dist/registry/providers/skills-sh.js +70 -49
  169. package/dist/registry/providers/static-index.js +53 -48
  170. package/dist/registry/providers/types.js +3 -24
  171. package/dist/registry/resolve.js +11 -16
  172. package/dist/registry/types.js +3 -0
  173. package/dist/scripts/migrate-storage.js +17307 -0
  174. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +8900 -0
  175. package/dist/scripts/migrations/v16-to-v17.js +141 -0
  176. package/dist/setup/detect.js +3 -0
  177. package/dist/setup/ripgrep-install.js +3 -0
  178. package/dist/setup/ripgrep-resolve.js +3 -0
  179. package/dist/setup/setup.js +775 -37
  180. package/dist/setup/steps.js +3 -15
  181. package/dist/sources/include.js +3 -0
  182. package/dist/sources/provider-factory.js +5 -12
  183. package/dist/sources/provider.js +3 -20
  184. package/dist/sources/providers/filesystem.js +19 -23
  185. package/dist/sources/providers/git.js +7 -5
  186. package/dist/sources/providers/index.js +3 -0
  187. package/dist/sources/providers/install-types.js +3 -13
  188. package/dist/sources/providers/npm.js +3 -4
  189. package/dist/sources/providers/provider-utils.js +3 -0
  190. package/dist/sources/providers/sync-from-ref.js +3 -11
  191. package/dist/sources/providers/tar-utils.js +3 -0
  192. package/dist/sources/providers/website.js +18 -22
  193. package/dist/sources/resolve.js +3 -0
  194. package/dist/sources/types.js +3 -0
  195. package/dist/sources/website-ingest.js +7 -0
  196. package/dist/tasks/backends/cron.js +203 -0
  197. package/dist/tasks/backends/exec-utils.js +28 -0
  198. package/dist/tasks/backends/index.js +24 -0
  199. package/dist/tasks/backends/launchd-template.xml +19 -0
  200. package/dist/tasks/backends/launchd.js +187 -0
  201. package/dist/tasks/backends/schtasks-template.xml +29 -0
  202. package/dist/tasks/backends/schtasks.js +215 -0
  203. package/dist/tasks/parser.js +211 -0
  204. package/dist/tasks/resolveAkmBin.js +87 -0
  205. package/dist/tasks/runner.js +458 -0
  206. package/dist/tasks/schedule.js +211 -0
  207. package/dist/tasks/schema.js +15 -0
  208. package/dist/tasks/validator.js +62 -0
  209. package/dist/version.js +3 -0
  210. package/dist/wiki/index-template.md +12 -0
  211. package/dist/wiki/ingest-workflow-template.md +54 -0
  212. package/dist/wiki/log-template.md +8 -0
  213. package/dist/wiki/schema-template.md +61 -0
  214. package/dist/wiki/wiki-templates.js +15 -0
  215. package/dist/wiki/wiki.js +13 -61
  216. package/dist/workflows/authoring.js +8 -25
  217. package/dist/workflows/cli.js +3 -0
  218. package/dist/workflows/db.js +140 -10
  219. package/dist/workflows/document-cache.js +3 -10
  220. package/dist/workflows/parser.js +3 -0
  221. package/dist/workflows/renderer.js +11 -3
  222. package/dist/workflows/runs.js +62 -91
  223. package/dist/workflows/schema.js +3 -0
  224. package/dist/workflows/scope-key.js +3 -0
  225. package/dist/workflows/validator.js +4 -8
  226. package/dist/workflows/workflow-template.md +24 -0
  227. package/docs/README.md +9 -2
  228. package/docs/data-and-telemetry.md +225 -0
  229. package/docs/migration/release-notes/0.7.0.md +1 -1
  230. package/docs/migration/release-notes/0.7.5.md +2 -2
  231. package/docs/migration/release-notes/0.8.0.md +48 -0
  232. package/docs/migration/v0.7-to-v0.8.md +1307 -0
  233. package/package.json +20 -8
  234. package/.github/LICENSE +0 -374
  235. package/dist/commands/install-audit.js +0 -381
  236. 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
  * Built-in asset renderers.
3
6
  *
@@ -9,11 +12,12 @@
9
12
  import fs from "node:fs";
10
13
  import path from "node:path";
11
14
  import { listKeys as listVaultKeys } from "../commands/vault";
12
- import { hasErrnoCode } from "../core/common";
13
- import { parseFrontmatter, toStringOrUndefined } from "../core/frontmatter";
15
+ import { asNonEmptyString, hasErrnoCode } from "../core/common";
16
+ import { parseFrontmatter } from "../core/frontmatter";
14
17
  import { extractFrontmatterOnly, extractLineRange, extractSection, formatToc, parseMarkdownToc, } from "../core/markdown";
15
18
  import { registerRenderer } from "../indexer/file-context";
16
19
  import { extractCommentMetadata, extractDescriptionFromComments } from "../indexer/metadata";
20
+ import { registerMetadataContributor } from "../indexer/metadata-contributors";
17
21
  import { buildWorkflowAction, workflowMdRenderer } from "../workflows/renderer";
18
22
  // ── Interpreter auto-detection map ───────────────────────────────────────────
19
23
  const INTERPRETER_MAP = {
@@ -165,7 +169,7 @@ const skillMdRenderer = {
165
169
  name,
166
170
  path: ctx.absPath,
167
171
  action: "Read and follow the instructions below",
168
- description: toStringOrUndefined(parsed.data.description),
172
+ description: asNonEmptyString(parsed.data.description),
169
173
  ...(tags ? { tags } : {}),
170
174
  content: parsed.content,
171
175
  };
@@ -184,11 +188,11 @@ const commandMdRenderer = {
184
188
  name,
185
189
  path: ctx.absPath,
186
190
  action: "Fill $ARGUMENTS placeholders in the template, then dispatch",
187
- description: toStringOrUndefined(parsedMd.data.description),
191
+ description: asNonEmptyString(parsedMd.data.description),
188
192
  ...(tags ? { tags } : {}),
189
193
  template,
190
194
  modelHint: typeof parsedMd.data.model === "string" ? parsedMd.data.model : undefined,
191
- agent: toStringOrUndefined(parsedMd.data.agent),
195
+ agent: asNonEmptyString(parsedMd.data.agent),
192
196
  parameters: extractParameters(template),
193
197
  };
194
198
  },
@@ -204,168 +208,67 @@ const agentMdRenderer = {
204
208
  name,
205
209
  path: ctx.absPath,
206
210
  action: "Dispatch using the prompt below verbatim. Use modelHint and toolPolicy if present.",
207
- description: toStringOrUndefined(parsedMd.data.description),
211
+ description: asNonEmptyString(parsedMd.data.description),
208
212
  prompt: parsedMd.content,
209
213
  toolPolicy: parsedMd.data.tools,
210
214
  modelHint: typeof parsedMd.data.model === "string" ? parsedMd.data.model : undefined,
211
215
  };
212
216
  },
213
217
  };
214
- // ── 4. knowledge-md ──────────────────────────────────────────────────────────
215
- const knowledgeMdRenderer = {
216
- name: "knowledge-md",
217
- buildShowResponse(ctx) {
218
- const name = deriveName(ctx);
219
- const v = ctx.matchResult.meta?.view ?? { mode: "full" };
220
- const content = ctx.content();
221
- switch (v.mode) {
222
- case "toc": {
223
- const toc = parseMarkdownToc(content);
224
- return {
225
- type: "knowledge",
226
- name,
227
- path: ctx.absPath,
228
- action: "Reference material - read the content below. Use 'toc' view for large documents.",
229
- content: formatToc(toc),
230
- };
231
- }
232
- case "frontmatter": {
233
- const fm = extractFrontmatterOnly(content);
234
- return {
235
- type: "knowledge",
236
- name,
237
- path: ctx.absPath,
238
- action: "Reference material - read the content below. Use 'toc' view for large documents.",
239
- content: fm ?? "(no frontmatter)",
240
- };
241
- }
242
- case "section": {
243
- const section = extractSection(content, v.heading);
244
- if (!section) {
245
- return {
246
- type: "knowledge",
247
- name,
248
- path: ctx.absPath,
249
- action: "Reference material - read the content below. Use 'toc' view for large documents.",
250
- content: `Section "${v.heading}" not found in ${name}. Try \`akm show <ref> toc\` to discover available headings.`,
251
- };
252
- }
253
- return {
254
- type: "knowledge",
255
- name,
256
- path: ctx.absPath,
257
- action: "Reference material - read the content below. Use 'toc' view for large documents.",
258
- content: section.content,
259
- };
260
- }
261
- case "lines": {
262
- return {
263
- type: "knowledge",
264
- name,
265
- path: ctx.absPath,
266
- action: "Reference material - read the content below. Use 'toc' view for large documents.",
267
- content: extractLineRange(content, v.start, v.end),
268
- };
269
- }
270
- default: {
271
- return {
272
- type: "knowledge",
273
- name,
274
- path: ctx.absPath,
275
- action: "Reference material - read the content below. Use 'toc' view for large documents.",
276
- content,
277
- };
218
+ // ── 4. knowledge-md / wiki-md shared helper ───────────────────────────────────
219
+ const KNOWLEDGE_ACTION = "Reference material - read the content below. Use 'toc' view for large documents.";
220
+ const WIKI_PAGE_ACTION = "Wiki page — read below. Use 'toc' to scan, 'section <heading>' for depth.";
221
+ /**
222
+ * Shared implementation for knowledge-md and wiki-md `buildShowResponse`.
223
+ *
224
+ * Both renderers handle the same set of view modes (toc, frontmatter, section,
225
+ * lines, full). The only differences are the `type` discriminant and the
226
+ * section-not-found message. Extracting this helper eliminates ~90 lines of
227
+ * byte-for-byte duplication.
228
+ */
229
+ function buildMarkdownViewResponse(ctx, type, action) {
230
+ const name = deriveName(ctx);
231
+ const v = ctx.matchResult.meta?.view ?? { mode: "full" };
232
+ const content = ctx.content();
233
+ switch (v.mode) {
234
+ case "toc": {
235
+ const toc = parseMarkdownToc(content);
236
+ return { type, name, path: ctx.absPath, action, content: formatToc(toc) };
237
+ }
238
+ case "frontmatter": {
239
+ const fm = extractFrontmatterOnly(content);
240
+ return { type, name, path: ctx.absPath, action, content: fm ?? "(no frontmatter)" };
241
+ }
242
+ case "section": {
243
+ const section = extractSection(content, v.heading);
244
+ if (!section) {
245
+ const notFoundMsg = type === "wiki"
246
+ ? `Section "${v.heading}" not found in ${name}. Try \`akm show wiki:${name} toc\` to discover available headings.`
247
+ : `Section "${v.heading}" not found in ${name}. Try \`akm show <ref> toc\` to discover available headings.`;
248
+ return { type, name, path: ctx.absPath, action, content: notFoundMsg };
278
249
  }
250
+ return { type, name, path: ctx.absPath, action, content: section.content };
279
251
  }
280
- },
281
- extractMetadata(entry, ctx) {
282
- try {
283
- const toc = parseMarkdownToc(ctx.content());
284
- if (toc.headings.length > 0)
285
- entry.toc = toc.headings;
252
+ case "lines": {
253
+ return { type, name, path: ctx.absPath, action, content: extractLineRange(content, v.start, v.end) };
286
254
  }
287
- catch {
288
- // Non-fatal: skip TOC if file can't be read
255
+ default: {
256
+ return { type, name, path: ctx.absPath, action, content };
289
257
  }
258
+ }
259
+ }
260
+ // ── 4. knowledge-md ──────────────────────────────────────────────────────────
261
+ const knowledgeMdRenderer = {
262
+ name: "knowledge-md",
263
+ buildShowResponse(ctx) {
264
+ return buildMarkdownViewResponse(ctx, "knowledge", KNOWLEDGE_ACTION);
290
265
  },
291
266
  };
292
267
  // ── 4b. wiki-md ──────────────────────────────────────────────────────────────
293
- const WIKI_PAGE_ACTION = "Wiki page — read below. Use 'toc' to scan, 'section <heading>' for depth.";
294
268
  const wikiMdRenderer = {
295
269
  name: "wiki-md",
296
270
  buildShowResponse(ctx) {
297
- const name = deriveName(ctx);
298
- const v = ctx.matchResult.meta?.view ?? { mode: "full" };
299
- const content = ctx.content();
300
- switch (v.mode) {
301
- case "toc": {
302
- const toc = parseMarkdownToc(content);
303
- return {
304
- type: "wiki",
305
- name,
306
- path: ctx.absPath,
307
- action: WIKI_PAGE_ACTION,
308
- content: formatToc(toc),
309
- };
310
- }
311
- case "frontmatter": {
312
- const fm = extractFrontmatterOnly(content);
313
- return {
314
- type: "wiki",
315
- name,
316
- path: ctx.absPath,
317
- action: WIKI_PAGE_ACTION,
318
- content: fm ?? "(no frontmatter)",
319
- };
320
- }
321
- case "section": {
322
- const section = extractSection(content, v.heading);
323
- if (!section) {
324
- return {
325
- type: "wiki",
326
- name,
327
- path: ctx.absPath,
328
- action: WIKI_PAGE_ACTION,
329
- content: `Section "${v.heading}" not found in ${name}. Try \`akm show wiki:${name} toc\` to discover available headings.`,
330
- };
331
- }
332
- return {
333
- type: "wiki",
334
- name,
335
- path: ctx.absPath,
336
- action: WIKI_PAGE_ACTION,
337
- content: section.content,
338
- };
339
- }
340
- case "lines": {
341
- return {
342
- type: "wiki",
343
- name,
344
- path: ctx.absPath,
345
- action: WIKI_PAGE_ACTION,
346
- content: extractLineRange(content, v.start, v.end),
347
- };
348
- }
349
- default: {
350
- return {
351
- type: "wiki",
352
- name,
353
- path: ctx.absPath,
354
- action: WIKI_PAGE_ACTION,
355
- content,
356
- };
357
- }
358
- }
359
- },
360
- extractMetadata(entry, ctx) {
361
- try {
362
- const toc = parseMarkdownToc(ctx.content());
363
- if (toc.headings.length > 0)
364
- entry.toc = toc.headings;
365
- }
366
- catch {
367
- // Non-fatal: skip TOC if file can't be read
368
- }
271
+ return buildMarkdownViewResponse(ctx, "wiki", WIKI_PAGE_ACTION);
369
272
  },
370
273
  };
371
274
  // ── 4c. lesson-md ────────────────────────────────────────────────────────────
@@ -384,8 +287,8 @@ const lessonMdRenderer = {
384
287
  buildShowResponse(ctx) {
385
288
  const name = deriveName(ctx);
386
289
  const parsed = parseFrontmatter(ctx.content());
387
- const description = toStringOrUndefined(parsed.data.description);
388
- const whenToUse = toStringOrUndefined(parsed.data.when_to_use);
290
+ const description = asNonEmptyString(parsed.data.description);
291
+ const whenToUse = asNonEmptyString(parsed.data.when_to_use);
389
292
  const action = whenToUse
390
293
  ? `Apply this lesson when: ${whenToUse}`
391
294
  : "Apply this lesson when its `when_to_use` trigger matches the current task.";
@@ -398,33 +301,6 @@ const lessonMdRenderer = {
398
301
  content: parsed.content,
399
302
  };
400
303
  },
401
- extractMetadata(entry, ctx) {
402
- try {
403
- const parsed = parseFrontmatter(ctx.content());
404
- const fm = parsed.data;
405
- const desc = toStringOrUndefined(fm.description);
406
- if (desc && !entry.description) {
407
- entry.description = desc;
408
- entry.source = "frontmatter";
409
- entry.confidence = 0.9;
410
- }
411
- const whenToUse = toStringOrUndefined(fm.when_to_use);
412
- if (whenToUse) {
413
- const hints = new Set(entry.searchHints ?? []);
414
- hints.add(`when_to_use:${whenToUse}`);
415
- entry.searchHints = Array.from(hints).filter(Boolean);
416
- }
417
- if (Array.isArray(fm.tags) && fm.tags.length > 0) {
418
- const fmTags = fm.tags.filter((t) => typeof t === "string" && t.trim().length > 0);
419
- if (fmTags.length > 0) {
420
- entry.tags = Array.from(new Set([...(entry.tags ?? []), ...fmTags]));
421
- }
422
- }
423
- }
424
- catch {
425
- // Non-fatal: skip metadata extraction on parse error
426
- }
427
- },
428
304
  };
429
305
  // ── 5. memory-md ─────────────────────────────────────────────────────────────
430
306
  const memoryMdRenderer = {
@@ -439,58 +315,6 @@ const memoryMdRenderer = {
439
315
  content: ctx.content(),
440
316
  };
441
317
  },
442
- extractMetadata(entry, ctx) {
443
- try {
444
- const parsed = parseFrontmatter(ctx.content());
445
- const fm = parsed.data;
446
- // Description from frontmatter
447
- const desc = toStringOrUndefined(fm.description);
448
- if (desc && !entry.description) {
449
- entry.description = desc;
450
- entry.source = "frontmatter";
451
- entry.confidence = 0.9;
452
- }
453
- // Tags from frontmatter
454
- if (Array.isArray(fm.tags) && fm.tags.length > 0) {
455
- const fmTags = fm.tags.filter((t) => typeof t === "string" && t.trim().length > 0);
456
- if (fmTags.length > 0) {
457
- entry.tags = Array.from(new Set([...(entry.tags ?? []), ...fmTags]));
458
- }
459
- }
460
- // Build searchHints from structured memory metadata fields
461
- const hints = new Set(entry.searchHints ?? []);
462
- const source = toStringOrUndefined(fm.source);
463
- if (source)
464
- hints.add(source);
465
- // observed_at: prefer frontmatter value, fall back to file mtime
466
- const fmObservedAt = toStringOrUndefined(fm.observed_at);
467
- if (fmObservedAt) {
468
- hints.add(`observed_at:${fmObservedAt}`);
469
- }
470
- else {
471
- // mtime fallback: format as ISO date (YYYY-MM-DD)
472
- try {
473
- const mtime = ctx.stat().mtime;
474
- const isoDate = mtime.toISOString().slice(0, 10);
475
- hints.add(`observed_at:${isoDate}`);
476
- }
477
- catch {
478
- // Non-fatal: skip mtime fallback on stat error
479
- }
480
- }
481
- const expires = toStringOrUndefined(fm.expires);
482
- if (expires)
483
- hints.add(`expires:${expires}`);
484
- if (fm.subjective === true)
485
- hints.add("subjective");
486
- if (hints.size > 0) {
487
- entry.searchHints = Array.from(hints).filter(Boolean);
488
- }
489
- }
490
- catch {
491
- // Non-fatal: skip metadata extraction on error
492
- }
493
- },
494
318
  };
495
319
  // ── 6. workflow-md ───────────────────────────────────────────────────────────
496
320
  // Defined in src/workflows/renderer.ts and imported above.
@@ -537,16 +361,6 @@ const scriptSourceRenderer = {
537
361
  throw error;
538
362
  }
539
363
  },
540
- extractMetadata(entry, ctx) {
541
- if (ctx.ext !== ".md") {
542
- const commentDesc = extractDescriptionFromComments(ctx.absPath);
543
- if (commentDesc && !entry.description) {
544
- entry.description = commentDesc;
545
- entry.source = "comments";
546
- entry.confidence = 0.7;
547
- }
548
- }
549
- },
550
364
  };
551
365
  // ── 8. vault-env ─────────────────────────────────────────────────────────────
552
366
  /**
@@ -574,22 +388,174 @@ const vaultEnvRenderer = {
574
388
  if (keys.length > 0)
575
389
  hit.keys = keys;
576
390
  },
577
- extractMetadata(entry, ctx) {
578
- // Re-derive from the file directly to guarantee no value ever transits
579
- // through any other code path. Caller already short-circuits in
580
- // generateMetadata{,Flat}, but this is defense in depth.
581
- const { keys, comments } = listVaultKeys(ctx.absPath);
582
- if (comments.length > 0 && !entry.description) {
583
- entry.description = comments.join(" ").slice(0, 500);
584
- entry.source = "comments";
585
- entry.confidence = 0.7;
586
- }
587
- if (keys.length > 0) {
588
- entry.searchHints = keys;
589
- }
590
- entry.tags = Array.from(new Set([...(entry.tags ?? []), "vault", "secrets"]));
391
+ };
392
+ // ── 7. task-md ───────────────────────────────────────────────────────────────
393
+ const TASK_PAGE_ACTION = "Scheduled task `akm tasks show <id>` for parsed details, `akm tasks run <id>` to invoke now.";
394
+ const taskMdRenderer = {
395
+ name: "task-yaml",
396
+ buildShowResponse(ctx) {
397
+ const name = deriveName(ctx);
398
+ return {
399
+ type: "task",
400
+ name,
401
+ path: ctx.absPath,
402
+ action: TASK_PAGE_ACTION,
403
+ content: ctx.content(),
404
+ };
591
405
  },
592
406
  };
407
+ function applyTocMetadata(entry, ctx) {
408
+ try {
409
+ const toc = parseMarkdownToc(ctx.content());
410
+ if (toc.headings.length > 0)
411
+ entry.toc = toc.headings;
412
+ }
413
+ catch {
414
+ // Non-fatal: skip TOC if file can't be read
415
+ }
416
+ }
417
+ /**
418
+ * Parse frontmatter, apply description (if not already set) and merge tags
419
+ * into `entry`. Returns the raw frontmatter data object so callers can access
420
+ * type-specific fields without re-parsing.
421
+ */
422
+ function applyFrontmatterDescriptionAndTags(entry, ctx) {
423
+ const parsed = parseFrontmatter(ctx.content());
424
+ const fm = parsed.data;
425
+ const desc = asNonEmptyString(fm.description);
426
+ if (desc && !entry.description) {
427
+ entry.description = desc;
428
+ entry.source = "frontmatter";
429
+ entry.confidence = 0.9;
430
+ }
431
+ if (Array.isArray(fm.tags) && fm.tags.length > 0) {
432
+ const fmTags = fm.tags.filter((t) => typeof t === "string" && t.trim().length > 0);
433
+ if (fmTags.length > 0) {
434
+ entry.tags = Array.from(new Set([...(entry.tags ?? []), ...fmTags]));
435
+ }
436
+ }
437
+ return fm;
438
+ }
439
+ function applyLessonMetadata(entry, ctx) {
440
+ try {
441
+ const fm = applyFrontmatterDescriptionAndTags(entry, ctx);
442
+ const whenToUse = asNonEmptyString(fm.when_to_use);
443
+ if (whenToUse) {
444
+ const hints = new Set(entry.searchHints ?? []);
445
+ hints.add(`when_to_use:${whenToUse}`);
446
+ entry.searchHints = Array.from(hints).filter(Boolean);
447
+ }
448
+ }
449
+ catch {
450
+ // Non-fatal: skip metadata extraction on parse error
451
+ }
452
+ }
453
+ function applyMemoryMetadata(entry, ctx) {
454
+ try {
455
+ const fm = applyFrontmatterDescriptionAndTags(entry, ctx);
456
+ const hints = new Set(entry.searchHints ?? []);
457
+ const source = asNonEmptyString(fm.source);
458
+ if (source)
459
+ hints.add(source);
460
+ const fmObservedAt = asNonEmptyString(fm.observed_at);
461
+ if (fmObservedAt) {
462
+ hints.add(`observed_at:${fmObservedAt}`);
463
+ }
464
+ else {
465
+ try {
466
+ const isoDate = ctx.stat().mtime.toISOString().slice(0, 10);
467
+ hints.add(`observed_at:${isoDate}`);
468
+ }
469
+ catch {
470
+ // Non-fatal: skip mtime fallback on stat error
471
+ }
472
+ }
473
+ const expires = asNonEmptyString(fm.expires);
474
+ if (expires)
475
+ hints.add(`expires:${expires}`);
476
+ if (fm.subjective === true)
477
+ hints.add("subjective");
478
+ if (hints.size > 0) {
479
+ entry.searchHints = Array.from(hints).filter(Boolean);
480
+ }
481
+ }
482
+ catch {
483
+ // Non-fatal: skip metadata extraction on error
484
+ }
485
+ }
486
+ function applyScriptMetadata(entry, ctx) {
487
+ if (ctx.ext === ".md")
488
+ return;
489
+ const commentDesc = extractDescriptionFromComments(ctx.absPath);
490
+ if (commentDesc && !entry.description) {
491
+ entry.description = commentDesc;
492
+ entry.source = "comments";
493
+ entry.confidence = 0.7;
494
+ }
495
+ }
496
+ function applyVaultMetadata(entry, ctx) {
497
+ const { keys, comments } = listVaultKeys(ctx.absPath);
498
+ if (comments.length > 0 && !entry.description) {
499
+ entry.description = comments.join(" ").slice(0, 500);
500
+ entry.source = "comments";
501
+ entry.confidence = 0.7;
502
+ }
503
+ if (keys.length > 0) {
504
+ entry.searchHints = keys;
505
+ }
506
+ entry.tags = Array.from(new Set([...(entry.tags ?? []), "vault", "secrets"]));
507
+ }
508
+ function applyTaskMetadata(entry, ctx) {
509
+ try {
510
+ const fm = applyFrontmatterDescriptionAndTags(entry, ctx);
511
+ entry.tags = Array.from(new Set([...(entry.tags ?? []), "task", "scheduled"]));
512
+ const hints = new Set(entry.searchHints ?? []);
513
+ const schedule = asNonEmptyString(fm.schedule);
514
+ if (schedule)
515
+ hints.add(`schedule:${schedule}`);
516
+ const workflow = asNonEmptyString(fm.workflow);
517
+ if (workflow)
518
+ hints.add(`workflow:${workflow}`);
519
+ const prompt = asNonEmptyString(fm.prompt);
520
+ if (prompt)
521
+ hints.add(`prompt:${prompt}`);
522
+ if (hints.size > 0)
523
+ entry.searchHints = Array.from(hints).filter(Boolean);
524
+ }
525
+ catch {
526
+ // Non-fatal: skip metadata extraction on error
527
+ }
528
+ }
529
+ registerMetadataContributor({
530
+ name: "toc-metadata",
531
+ appliesTo: ({ rendererName }) => rendererName === "knowledge-md" || rendererName === "wiki-md",
532
+ contribute: (entry, ctx) => applyTocMetadata(entry, ctx.renderContext),
533
+ });
534
+ registerMetadataContributor({
535
+ name: "lesson-frontmatter-metadata",
536
+ appliesTo: ({ rendererName }) => rendererName === "lesson-md",
537
+ contribute: (entry, ctx) => applyLessonMetadata(entry, ctx.renderContext),
538
+ });
539
+ registerMetadataContributor({
540
+ name: "memory-frontmatter-metadata",
541
+ appliesTo: ({ rendererName }) => rendererName === "memory-md",
542
+ contribute: (entry, ctx) => applyMemoryMetadata(entry, ctx.renderContext),
543
+ });
544
+ registerMetadataContributor({
545
+ name: "script-comment-metadata",
546
+ appliesTo: ({ rendererName }) => rendererName === "script-source",
547
+ contribute: (entry, ctx) => applyScriptMetadata(entry, ctx.renderContext),
548
+ });
549
+ registerMetadataContributor({
550
+ name: "vault-secret-metadata",
551
+ appliesTo: ({ rendererName }) => rendererName === "vault-env",
552
+ contribute: (entry, ctx) => applyVaultMetadata(entry, ctx.renderContext),
553
+ });
554
+ registerMetadataContributor({
555
+ name: "task-yaml-metadata",
556
+ appliesTo: ({ rendererName }) => rendererName === "task-yaml",
557
+ contribute: (entry, ctx) => applyTaskMetadata(entry, ctx.renderContext),
558
+ });
593
559
  // ── Registration ─────────────────────────────────────────────────────────────
594
560
  /** All built-in renderers. */
595
561
  const builtinRenderers = [
@@ -603,6 +569,7 @@ const builtinRenderers = [
603
569
  workflowMdRenderer,
604
570
  scriptSourceRenderer,
605
571
  vaultEnvRenderer,
572
+ taskMdRenderer,
606
573
  ];
607
574
  /**
608
575
  * Register all built-in renderers with the file-context registry.