akm-cli 0.7.4 → 0.8.0-rc.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (300) hide show
  1. package/CHANGELOG.md +224 -1
  2. package/README.md +22 -6
  3. package/SECURITY.md +93 -0
  4. package/dist/cli/config-migrate.js +144 -0
  5. package/dist/cli/config-validate.js +39 -0
  6. package/dist/cli/confirm.js +73 -0
  7. package/dist/cli/parse-args.js +133 -0
  8. package/dist/cli/shared.js +129 -0
  9. package/dist/cli.js +2631 -1440
  10. package/dist/commands/add-cli.js +279 -0
  11. package/dist/commands/agent-dispatch.js +110 -0
  12. package/dist/commands/agent-support.js +68 -0
  13. package/dist/commands/completions.js +3 -0
  14. package/dist/commands/config-cli.js +130 -534
  15. package/dist/commands/consolidate.js +2122 -0
  16. package/dist/commands/curate.js +45 -3
  17. package/dist/commands/db-cli.js +23 -0
  18. package/dist/commands/distill-promotion-policy.js +660 -0
  19. package/dist/commands/distill.js +1081 -73
  20. package/dist/commands/env.js +213 -0
  21. package/dist/commands/eval-cases.js +43 -0
  22. package/dist/commands/events.js +15 -24
  23. package/dist/commands/extract-cli.js +127 -0
  24. package/dist/commands/extract-prompt.js +204 -0
  25. package/dist/commands/extract.js +477 -0
  26. package/dist/commands/feedback-cli.js +331 -0
  27. package/dist/commands/graph.js +477 -0
  28. package/dist/commands/health.js +1302 -0
  29. package/dist/commands/help/help-accept.md +12 -0
  30. package/dist/commands/help/help-improve.md +69 -0
  31. package/dist/commands/help/help-proposals.md +18 -0
  32. package/dist/commands/help/help-propose.md +17 -0
  33. package/dist/commands/help/help-reject.md +11 -0
  34. package/dist/commands/history.js +54 -46
  35. package/dist/commands/improve-auto-accept.js +97 -0
  36. package/dist/commands/improve-cli.js +217 -0
  37. package/dist/commands/improve-profiles.js +166 -0
  38. package/dist/commands/improve-result-file.js +167 -0
  39. package/dist/commands/improve.js +2373 -0
  40. package/dist/commands/info.js +5 -2
  41. package/dist/commands/init.js +50 -2
  42. package/dist/commands/installed-stashes.js +102 -139
  43. package/dist/commands/knowledge.js +136 -0
  44. package/dist/commands/lint/agent-linter.js +49 -0
  45. package/dist/commands/lint/base-linter.js +479 -0
  46. package/dist/commands/lint/command-linter.js +49 -0
  47. package/dist/commands/lint/default-linter.js +16 -0
  48. package/dist/commands/lint/env-key-rules.js +154 -0
  49. package/dist/commands/lint/index.js +196 -0
  50. package/dist/commands/lint/knowledge-linter.js +16 -0
  51. package/dist/commands/lint/markdown-insertion.js +343 -0
  52. package/dist/commands/lint/memory-linter.js +61 -0
  53. package/dist/commands/lint/registry.js +36 -0
  54. package/dist/commands/lint/skill-linter.js +45 -0
  55. package/dist/commands/lint/task-linter.js +50 -0
  56. package/dist/commands/lint/types.js +4 -0
  57. package/dist/commands/lint/workflow-linter.js +56 -0
  58. package/dist/commands/lint.js +4 -0
  59. package/dist/commands/migration-help.js +3 -0
  60. package/dist/commands/proposal.js +67 -12
  61. package/dist/commands/propose.js +120 -45
  62. package/dist/commands/reflect.js +1104 -60
  63. package/dist/commands/registry-cli.js +150 -0
  64. package/dist/commands/registry-search.js +5 -2
  65. package/dist/commands/remember-cli.js +257 -0
  66. package/dist/commands/remember.js +70 -7
  67. package/dist/commands/schema-repair.js +203 -0
  68. package/dist/commands/search.js +115 -14
  69. package/dist/commands/secret.js +173 -0
  70. package/dist/commands/self-update.js +3 -0
  71. package/dist/commands/show.js +158 -60
  72. package/dist/commands/source-add.js +17 -45
  73. package/dist/commands/source-clone.js +3 -0
  74. package/dist/commands/source-manage.js +14 -19
  75. package/dist/commands/tasks.js +437 -0
  76. package/dist/commands/url-checker.js +42 -0
  77. package/dist/core/action-contributors.js +28 -0
  78. package/dist/core/asset-ref.js +17 -2
  79. package/dist/core/asset-registry.js +12 -17
  80. package/dist/core/asset-serialize.js +88 -0
  81. package/dist/core/asset-spec.js +67 -1
  82. package/dist/core/common.js +182 -0
  83. package/dist/core/concurrent.js +25 -0
  84. package/dist/core/config-io.js +347 -0
  85. package/dist/core/config-migration.js +622 -0
  86. package/dist/core/config-schema.js +534 -0
  87. package/dist/core/config-sources.js +108 -0
  88. package/dist/core/config-types.js +4 -0
  89. package/dist/core/config-walker.js +337 -0
  90. package/dist/core/config.js +364 -968
  91. package/dist/core/errors.js +42 -20
  92. package/dist/core/events.js +105 -135
  93. package/dist/core/file-lock.js +104 -0
  94. package/dist/core/frontmatter.js +75 -8
  95. package/dist/core/lesson-lint.js +3 -0
  96. package/dist/core/markdown.js +20 -0
  97. package/dist/core/memory-belief.js +62 -0
  98. package/dist/core/memory-contradiction-detect.js +274 -0
  99. package/dist/core/memory-improve.js +806 -0
  100. package/dist/core/parse.js +158 -0
  101. package/dist/core/paths.js +280 -14
  102. package/dist/core/proposal-quality-validators.js +380 -0
  103. package/dist/core/proposal-validators.js +69 -0
  104. package/dist/core/proposals.js +512 -42
  105. package/dist/core/state-db.js +1068 -0
  106. package/dist/core/text-truncation.js +107 -0
  107. package/dist/core/time.js +54 -0
  108. package/dist/core/tty.js +59 -0
  109. package/dist/core/warn.js +64 -1
  110. package/dist/core/write-source.js +3 -0
  111. package/dist/indexer/db-backup.js +391 -0
  112. package/dist/indexer/db-search.js +198 -489
  113. package/dist/indexer/db.js +990 -108
  114. package/dist/indexer/ensure-index.js +136 -0
  115. package/dist/indexer/file-context.js +3 -0
  116. package/dist/indexer/graph-boost.js +376 -101
  117. package/dist/indexer/graph-db.js +391 -0
  118. package/dist/indexer/graph-dedup.js +95 -0
  119. package/dist/indexer/graph-extraction.js +550 -114
  120. package/dist/indexer/index-context.js +4 -0
  121. package/dist/indexer/indexer.js +547 -309
  122. package/dist/indexer/llm-cache.js +52 -0
  123. package/dist/indexer/manifest.js +3 -0
  124. package/dist/indexer/matchers.js +167 -160
  125. package/dist/indexer/memory-inference.js +152 -74
  126. package/dist/indexer/metadata-contributors.js +29 -0
  127. package/dist/indexer/metadata.js +275 -196
  128. package/dist/indexer/path-resolver.js +92 -0
  129. package/dist/indexer/project-context.js +192 -0
  130. package/dist/indexer/ranking-contributors.js +331 -0
  131. package/dist/indexer/ranking.js +81 -0
  132. package/dist/indexer/search-fields.js +5 -9
  133. package/dist/indexer/search-hit-enrichers.js +111 -0
  134. package/dist/indexer/search-source.js +44 -10
  135. package/dist/indexer/semantic-status.js +6 -17
  136. package/dist/indexer/staleness-detect.js +447 -0
  137. package/dist/indexer/usage-events.js +12 -9
  138. package/dist/indexer/walker.js +28 -0
  139. package/dist/integrations/agent/builders.js +135 -0
  140. package/dist/integrations/agent/config.js +122 -230
  141. package/dist/integrations/agent/detect.js +3 -0
  142. package/dist/integrations/agent/index.js +7 -13
  143. package/dist/integrations/agent/model-aliases.js +55 -0
  144. package/dist/integrations/agent/profiles.js +70 -5
  145. package/dist/integrations/agent/prompts.js +250 -36
  146. package/dist/integrations/agent/runner.js +151 -0
  147. package/dist/integrations/agent/sdk-runner.js +126 -0
  148. package/dist/integrations/agent/spawn.js +183 -35
  149. package/dist/integrations/github.js +3 -0
  150. package/dist/integrations/lockfile.js +32 -69
  151. package/dist/integrations/session-logs/index.js +69 -0
  152. package/dist/integrations/session-logs/inline-refs.js +35 -0
  153. package/dist/integrations/session-logs/pre-filter.js +152 -0
  154. package/dist/integrations/session-logs/providers/claude-code.js +282 -0
  155. package/dist/integrations/session-logs/providers/opencode.js +258 -0
  156. package/dist/integrations/session-logs/types.js +4 -0
  157. package/dist/llm/call-ai.js +62 -0
  158. package/dist/llm/client.js +79 -88
  159. package/dist/llm/embedder.js +20 -29
  160. package/dist/llm/embedders/cache.js +3 -7
  161. package/dist/llm/embedders/local.js +42 -1
  162. package/dist/llm/embedders/remote.js +20 -8
  163. package/dist/llm/embedders/types.js +3 -7
  164. package/dist/llm/feature-gate.js +95 -48
  165. package/dist/llm/graph-extract.js +676 -72
  166. package/dist/llm/index-passes.js +44 -29
  167. package/dist/llm/memory-infer.js +80 -71
  168. package/dist/llm/metadata-enhance.js +42 -29
  169. package/dist/llm/prompts/extract-session.md +80 -0
  170. package/dist/llm/prompts/graph-extract-user-prompt.md +35 -0
  171. package/dist/output/cli-hints-full.md +292 -0
  172. package/dist/output/cli-hints-short.md +66 -0
  173. package/dist/output/cli-hints.js +7 -311
  174. package/dist/output/context.js +60 -8
  175. package/dist/output/renderers.js +306 -258
  176. package/dist/output/shapes/curate.js +56 -0
  177. package/dist/output/shapes/distill.js +10 -0
  178. package/dist/output/shapes/env-list.js +19 -0
  179. package/dist/output/shapes/events.js +11 -0
  180. package/dist/output/shapes/helpers.js +424 -0
  181. package/dist/output/shapes/history.js +7 -0
  182. package/dist/output/shapes/passthrough.js +102 -0
  183. package/dist/output/shapes/proposal-accept.js +7 -0
  184. package/dist/output/shapes/proposal-diff.js +7 -0
  185. package/dist/output/shapes/proposal-list.js +7 -0
  186. package/dist/output/shapes/proposal-producer.js +11 -0
  187. package/dist/output/shapes/proposal-reject.js +7 -0
  188. package/dist/output/shapes/proposal-show.js +7 -0
  189. package/dist/output/shapes/registry-search.js +6 -0
  190. package/dist/output/shapes/registry.js +30 -0
  191. package/dist/output/shapes/search.js +6 -0
  192. package/dist/output/shapes/secret-list.js +19 -0
  193. package/dist/output/shapes/show.js +6 -0
  194. package/dist/output/shapes/vault-list.js +19 -0
  195. package/dist/output/shapes.js +51 -511
  196. package/dist/output/text/add.js +6 -0
  197. package/dist/output/text/clone.js +6 -0
  198. package/dist/output/text/config.js +6 -0
  199. package/dist/output/text/curate.js +6 -0
  200. package/dist/output/text/distill.js +7 -0
  201. package/dist/output/text/enable-disable.js +7 -0
  202. package/dist/output/text/events.js +10 -0
  203. package/dist/output/text/feedback.js +6 -0
  204. package/dist/output/text/helpers.js +1039 -0
  205. package/dist/output/text/history.js +7 -0
  206. package/dist/output/text/import.js +6 -0
  207. package/dist/output/text/index.js +6 -0
  208. package/dist/output/text/info.js +6 -0
  209. package/dist/output/text/init.js +6 -0
  210. package/dist/output/text/list.js +6 -0
  211. package/dist/output/text/proposal-producer.js +8 -0
  212. package/dist/output/text/proposal.js +11 -0
  213. package/dist/output/text/registry-commands.js +11 -0
  214. package/dist/output/text/registry.js +30 -0
  215. package/dist/output/text/remember.js +6 -0
  216. package/dist/output/text/remove.js +6 -0
  217. package/dist/output/text/save.js +6 -0
  218. package/dist/output/text/search.js +6 -0
  219. package/dist/output/text/show.js +6 -0
  220. package/dist/output/text/update.js +6 -0
  221. package/dist/output/text/upgrade.js +6 -0
  222. package/dist/output/text/vault.js +16 -0
  223. package/dist/output/text/wiki.js +15 -0
  224. package/dist/output/text/workflow.js +14 -0
  225. package/dist/output/text.js +44 -1093
  226. package/dist/registry/build-index.js +3 -0
  227. package/dist/registry/create-provider-registry.js +3 -0
  228. package/dist/registry/factory.js +4 -1
  229. package/dist/registry/origin-resolve.js +3 -0
  230. package/dist/registry/providers/index.js +3 -0
  231. package/dist/registry/providers/skills-sh.js +71 -50
  232. package/dist/registry/providers/static-index.js +53 -48
  233. package/dist/registry/providers/types.js +3 -24
  234. package/dist/registry/resolve.js +11 -16
  235. package/dist/registry/types.js +3 -0
  236. package/dist/scripts/migrate-storage.js +17750 -0
  237. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +9031 -0
  238. package/dist/scripts/migrations/v16-to-v17.js +141 -0
  239. package/dist/setup/detect.js +3 -0
  240. package/dist/setup/ripgrep-install.js +3 -0
  241. package/dist/setup/ripgrep-resolve.js +3 -0
  242. package/dist/setup/setup.js +775 -37
  243. package/dist/setup/steps.js +3 -15
  244. package/dist/sources/include.js +3 -0
  245. package/dist/sources/provider-factory.js +5 -12
  246. package/dist/sources/provider.js +3 -20
  247. package/dist/sources/providers/filesystem.js +19 -23
  248. package/dist/sources/providers/git.js +179 -20
  249. package/dist/sources/providers/index.js +3 -0
  250. package/dist/sources/providers/install-types.js +3 -13
  251. package/dist/sources/providers/npm.js +3 -4
  252. package/dist/sources/providers/provider-utils.js +3 -0
  253. package/dist/sources/providers/sync-from-ref.js +3 -11
  254. package/dist/sources/providers/tar-utils.js +3 -0
  255. package/dist/sources/providers/website.js +18 -22
  256. package/dist/sources/resolve.js +3 -0
  257. package/dist/sources/types.js +3 -0
  258. package/dist/sources/website-ingest.js +7 -0
  259. package/dist/tasks/backends/cron.js +203 -0
  260. package/dist/tasks/backends/exec-utils.js +28 -0
  261. package/dist/tasks/backends/index.js +24 -0
  262. package/dist/tasks/backends/launchd-template.xml +19 -0
  263. package/dist/tasks/backends/launchd.js +187 -0
  264. package/dist/tasks/backends/schtasks-template.xml +29 -0
  265. package/dist/tasks/backends/schtasks.js +215 -0
  266. package/dist/tasks/parser.js +211 -0
  267. package/dist/tasks/resolveAkmBin.js +87 -0
  268. package/dist/tasks/runner.js +458 -0
  269. package/dist/tasks/schedule.js +227 -0
  270. package/dist/tasks/schema.js +15 -0
  271. package/dist/tasks/validator.js +62 -0
  272. package/dist/version.js +3 -0
  273. package/dist/wiki/index-template.md +12 -0
  274. package/dist/wiki/ingest-workflow-template.md +54 -0
  275. package/dist/wiki/log-template.md +8 -0
  276. package/dist/wiki/schema-template.md +61 -0
  277. package/dist/wiki/wiki-templates.js +15 -0
  278. package/dist/wiki/wiki.js +13 -61
  279. package/dist/workflows/authoring.js +8 -25
  280. package/dist/workflows/cli.js +3 -0
  281. package/dist/workflows/db.js +141 -2
  282. package/dist/workflows/document-cache.js +3 -10
  283. package/dist/workflows/parser.js +3 -0
  284. package/dist/workflows/renderer.js +11 -3
  285. package/dist/workflows/runs.js +91 -89
  286. package/dist/workflows/schema.js +3 -0
  287. package/dist/workflows/scope-key.js +79 -0
  288. package/dist/workflows/validator.js +4 -8
  289. package/dist/workflows/workflow-template.md +24 -0
  290. package/docs/README.md +10 -2
  291. package/docs/data-and-telemetry.md +225 -0
  292. package/docs/migration/release-notes/0.7.0.md +1 -1
  293. package/docs/migration/release-notes/0.7.4.md +1 -1
  294. package/docs/migration/release-notes/0.7.5.md +20 -0
  295. package/docs/migration/release-notes/0.8.0.md +48 -0
  296. package/docs/migration/v0.7-to-v0.8.md +1307 -0
  297. package/package.json +29 -11
  298. package/dist/commands/install-audit.js +0 -381
  299. package/dist/commands/vault.js +0 -333
  300. package/dist/templates/wiki-templates.js +0 -100
@@ -0,0 +1,52 @@
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+ import { computeBodyHash, getLlmCacheEntry, upsertLlmCacheEntry } from "./db";
5
+ /**
6
+ * Generic LLM cache wrapper. Returns cached result if body unchanged,
7
+ * otherwise calls llmFn(), caches the result, and returns it.
8
+ * Returns undefined if llmFn() returns undefined or throws.
9
+ *
10
+ * @param db - SQLite database holding the LLM result cache.
11
+ * @param cacheKey - Stable identifier for this asset (typically its absolute path).
12
+ * @param body - The content being processed; its hash determines cache validity.
13
+ * @param reEnrich - When true the cache is bypassed and llmFn() is always called.
14
+ * @param llmFn - Async function that performs the actual LLM call.
15
+ * @param validate - Converts the raw parsed JSON back into the pass-specific type;
16
+ * returns undefined when the cached data is unusable.
17
+ * @param precomputedHash - Optional precomputed SHA-256 of `body`. When provided,
18
+ * the wrapper skips its internal hashing — callers that
19
+ * already hashed the body (e.g. to reuse it elsewhere)
20
+ * should pass this to avoid the redundant work.
21
+ * @param cacheVariant - Namespace token for the cache row (e.g. staleness-detect
22
+ * sets one so its rows do not collide with memory-inference).
23
+ * @param hooks - Optional event sink for telemetry (see {@link WithLlmCacheHooks}).
24
+ */
25
+ export async function withLlmCache(db, cacheKey, body, reEnrich, llmFn, validate, precomputedHash, cacheVariant = "", hooks) {
26
+ const bodyHash = precomputedHash ?? computeBodyHash(body);
27
+ if (!reEnrich) {
28
+ try {
29
+ const cached = getLlmCacheEntry(db, cacheKey, bodyHash, cacheVariant);
30
+ if (cached) {
31
+ const result = validate(JSON.parse(cached.resultJson));
32
+ if (result !== undefined) {
33
+ hooks?.onCacheHit?.();
34
+ return result;
35
+ }
36
+ }
37
+ }
38
+ catch {
39
+ // Cache corrupt — fall through
40
+ }
41
+ }
42
+ const result = await llmFn();
43
+ if (result !== undefined) {
44
+ try {
45
+ upsertLlmCacheEntry(db, cacheKey, bodyHash, JSON.stringify(result), cacheVariant);
46
+ }
47
+ catch {
48
+ // Cache write failure is non-fatal
49
+ }
50
+ }
51
+ return result;
52
+ }
@@ -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
  * Manifest: compact asset listing for cheap capability discovery.
3
6
  *
@@ -1,193 +1,171 @@
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 matchers for the akm file classification system.
3
6
  *
4
- * Five matchers are registered at module load time, each at a different
5
- * specificity level. Extension and content determine type; directories are
6
- * optional specificity boosts, not requirements.
7
- *
8
- * - `extensionMatcher` (3) -- classifies any file by extension alone.
9
- * Ensures every known file type is discoverable regardless of directory.
10
- * - `directoryMatcher` (10) -- boosts specificity when an ancestor
11
- * directory matches a known type name (e.g. `scripts/`, `agents/`).
12
- * - `parentDirHintMatcher` (15) -- boosts specificity based on the
13
- * immediate parent directory name.
14
- * - `smartMdMatcher` (20 / 18 / 8 / 5) -- inspects markdown frontmatter
15
- * and body content for agent/command signals; falls back to "knowledge"
16
- * at specificity 5 when no signals are found. Command signals (`agent`
17
- * frontmatter, `$ARGUMENTS`/`$1`-`$3` placeholders) return 18.
18
- * - `wikiMatcher` (20) -- classifies any `.md` under `wikis/<name>/…` as
19
- * `wiki`. Registered last so the later-wins tiebreaker beats agent at 20.
7
+ * Each private `classifyBy*` function encapsulates the classification logic for
8
+ * one heuristic. The public `*Matcher` exports compose those facts into the
9
+ * `MatchResult` shape expected by the rest of the indexer.
20
10
  */
11
+ import { defaultRendererRegistry } from "../core/asset-registry";
21
12
  import { SCRIPT_EXTENSIONS } from "../core/asset-spec";
22
13
  import { looksLikeWorkflow } from "../workflows/parser";
23
14
  import { registerMatcher } from "./file-context";
24
- // ── extensionMatcher (specificity: 3) ────────────────────────────────────────
25
- /**
26
- * Base-level matcher that classifies files purely by extension.
27
- *
28
- * This is the foundation of the classification system: every file with a
29
- * known extension gets a type, regardless of what directory it lives in.
30
- * Higher-specificity matchers (directory, content) can override this.
31
- *
32
- * .md files are NOT handled here -- smartMdMatcher provides richer
33
- * classification for markdown via frontmatter inspection.
34
- */
35
- export function extensionMatcher(ctx) {
36
- // SKILL.md is a skill regardless of location — high specificity beats
37
- // smartMdMatcher's knowledge fallback and all directory-based matchers.
38
- // Exception: files under wikis/<name>/… are always wiki pages; the wiki
39
- // directory is an authoritative signal that outranks the filename.
15
+ // ---------------------------------------------------------------------------
16
+ // Private data
17
+ // ---------------------------------------------------------------------------
18
+ const DIR_TYPE_MAP = [
19
+ {
20
+ dir: "scripts",
21
+ type: "script",
22
+ test: (ext) => SCRIPT_EXTENSIONS.has(ext),
23
+ },
24
+ {
25
+ dir: "commands",
26
+ type: "command",
27
+ test: (ext) => ext === ".md",
28
+ },
29
+ {
30
+ dir: "agents",
31
+ type: "agent",
32
+ test: (ext) => ext === ".md",
33
+ },
34
+ {
35
+ dir: "knowledge",
36
+ type: "knowledge",
37
+ test: (ext) => ext === ".md",
38
+ },
39
+ {
40
+ dir: "workflows",
41
+ type: "workflow",
42
+ test: (ext) => ext === ".md",
43
+ },
44
+ {
45
+ dir: "memories",
46
+ type: "memory",
47
+ test: (ext) => ext === ".md",
48
+ },
49
+ {
50
+ dir: "lessons",
51
+ type: "lesson",
52
+ test: (ext) => ext === ".md",
53
+ },
54
+ {
55
+ dir: "env",
56
+ type: "env",
57
+ test: (_, fileName) => fileName === ".env" || fileName.endsWith(".env"),
58
+ },
59
+ {
60
+ dir: "vaults",
61
+ type: "vault",
62
+ test: (_, fileName) => fileName === ".env" || fileName.endsWith(".env"),
63
+ },
64
+ {
65
+ dir: "secrets",
66
+ type: "secret",
67
+ // Any regular file under secrets/ is a secret value, except the lock and
68
+ // sensitive-marker sidecars. The whole file is the value (no extension or
69
+ // body parsing — see the secret-file renderer + indexer guards).
70
+ test: (_, fileName) => !fileName.endsWith(".lock") && !fileName.endsWith(".sensitive"),
71
+ },
72
+ {
73
+ dir: "tasks",
74
+ type: "task",
75
+ test: (ext) => ext === ".md",
76
+ },
77
+ ];
78
+ const COMMAND_PLACEHOLDER_RE = /\$ARGUMENTS|\$[123]\b/;
79
+ // Files that should never be treated as the typed asset for the surrounding
80
+ // directory (e.g. `workflows/README.md` is documentation, not a workflow).
81
+ // Lower-cased and matched case-insensitively against `ctx.fileName`. They are
82
+ // still indexable — falling through to `classifyBySmartMd` typically routes
83
+ // them to the generic `knowledge` type.
84
+ const TYPED_DIR_DOC_FILES = new Set(["readme.md"]);
85
+ function isTypedDirDocFile(fileName) {
86
+ return TYPED_DIR_DOC_FILES.has(fileName.toLowerCase());
87
+ }
88
+ // ---------------------------------------------------------------------------
89
+ // Private helpers
90
+ // ---------------------------------------------------------------------------
91
+ function matchDirectoryHint(dirName, ctx, specificity) {
92
+ if (dirName === "skills" && ctx.fileName === "SKILL.md") {
93
+ return { type: "skill", specificity };
94
+ }
95
+ for (const rule of DIR_TYPE_MAP) {
96
+ if (rule.dir === dirName && rule.test(ctx.ext, ctx.fileName)) {
97
+ // Skip `README.md` (case-insensitive) so `workflows/README.md`,
98
+ // `agents/README.md`, etc. are not parsed as the typed asset and don't
99
+ // trip the workflow/agent metadata validators. They still get indexed
100
+ // as `knowledge` via the smart-md matcher.
101
+ if (isTypedDirDocFile(ctx.fileName))
102
+ return null;
103
+ return { type: rule.type, specificity };
104
+ }
105
+ }
106
+ return null;
107
+ }
108
+ function classifyByExtension(ctx) {
40
109
  if (ctx.fileName === "SKILL.md" && !ctx.ancestorDirs.includes("wikis")) {
41
- return { type: "skill", specificity: 25, renderer: "skill-md" };
110
+ return { type: "skill", specificity: 25 };
42
111
  }
43
- // Known script extensions (excluding .md, handled by smartMdMatcher)
44
112
  if (SCRIPT_EXTENSIONS.has(ctx.ext)) {
45
- return { type: "script", specificity: 3, renderer: "script-source" };
113
+ return { type: "script", specificity: 3 };
46
114
  }
47
115
  return null;
48
116
  }
49
- // ── directoryMatcher (specificity: 10) ──────────────────────────────────────
50
- /**
51
- * Directory-based matcher that boosts specificity when an ancestor
52
- * directory segment from the stash root matches a known type name.
53
- *
54
- * The first matching type-like ancestor wins. This preserves intuitive
55
- * behavior for nested stash layouts such as `agent-stash/agents/blog/foo.md`
56
- * while still honoring earlier type roots like `commands/agents/foo.md`.
57
- */
58
- export function directoryMatcher(ctx) {
59
- const ext = ctx.ext;
117
+ function classifyByDirectory(ctx) {
60
118
  for (const dir of ctx.ancestorDirs) {
61
- if (dir === "scripts" && SCRIPT_EXTENSIONS.has(ext)) {
62
- return { type: "script", specificity: 10, renderer: "script-source" };
63
- }
64
- if (dir === "skills" && ctx.fileName === "SKILL.md") {
65
- return { type: "skill", specificity: 10, renderer: "skill-md" };
66
- }
67
- if (dir === "commands" && ext === ".md") {
68
- return { type: "command", specificity: 10, renderer: "command-md" };
69
- }
70
- if (dir === "agents" && ext === ".md") {
71
- return { type: "agent", specificity: 10, renderer: "agent-md" };
72
- }
73
- if (dir === "knowledge" && ext === ".md") {
74
- return { type: "knowledge", specificity: 10, renderer: "knowledge-md" };
75
- }
76
- if (dir === "workflows" && ext === ".md") {
77
- return { type: "workflow", specificity: 10, renderer: "workflow-md" };
78
- }
79
- if (dir === "memories" && ext === ".md") {
80
- return { type: "memory", specificity: 10, renderer: "memory-md" };
81
- }
82
- if (dir === "vaults" && (ctx.fileName === ".env" || ctx.fileName.endsWith(".env"))) {
83
- return { type: "vault", specificity: 10, renderer: "vault-env" };
84
- }
119
+ const result = matchDirectoryHint(dir, ctx, 10);
120
+ if (result)
121
+ return result;
85
122
  }
86
123
  return null;
87
124
  }
88
- // ── parentDirHintMatcher (specificity: 15) ──────────────────────────────────
89
- /**
90
- * Uses the immediate parent directory name as a hint. More specific than
91
- * the ancestor-based directory matcher because the file might be nested
92
- * several levels deep, yet its immediate parent can still carry strong
93
- * naming conventions (e.g. `my-project/agents/planning.md`).
94
- */
95
- export function parentDirHintMatcher(ctx) {
125
+ function classifyByParentDirHint(ctx) {
96
126
  const { parentDir, ext, fileName } = ctx;
97
- if (parentDir === "scripts" && SCRIPT_EXTENSIONS.has(ext)) {
98
- return { type: "script", specificity: 15, renderer: "script-source" };
99
- }
100
127
  if (parentDir === "skills" && (fileName === "SKILL.md" || ext === ".md")) {
101
- return { type: "skill", specificity: 15, renderer: "skill-md" };
102
- }
103
- if (parentDir === "agents" && ext === ".md") {
104
- return { type: "agent", specificity: 15, renderer: "agent-md" };
105
- }
106
- if (parentDir === "commands" && ext === ".md") {
107
- return { type: "command", specificity: 15, renderer: "command-md" };
108
- }
109
- if (parentDir === "knowledge" && ext === ".md") {
110
- return { type: "knowledge", specificity: 15, renderer: "knowledge-md" };
111
- }
112
- if (parentDir === "workflows" && ext === ".md") {
113
- return { type: "workflow", specificity: 15, renderer: "workflow-md" };
128
+ return { type: "skill", specificity: 15 };
114
129
  }
115
- if (parentDir === "memories" && ext === ".md") {
116
- return { type: "memory", specificity: 15, renderer: "memory-md" };
117
- }
118
- if (parentDir === "vaults" && (fileName === ".env" || fileName.endsWith(".env"))) {
119
- return { type: "vault", specificity: 15, renderer: "vault-env" };
120
- }
121
- return null;
130
+ return matchDirectoryHint(parentDir, ctx, 15);
122
131
  }
123
- // ── smartMdMatcher (specificity: 20 / 18 / 8 / 5) ──────────────────────────
124
- /** Pattern that matches OpenCode command placeholders in markdown body. */
125
- const COMMAND_PLACEHOLDER_RE = /\$ARGUMENTS|\$[123]\b/;
126
- /**
127
- * Content-based matcher for `.md` files. Inspects frontmatter keys and body
128
- * content to classify markdown as agent, command, or knowledge.
129
- *
130
- * Specificity levels:
131
- * 20 -- agent-exclusive signals (`tools`, `toolPolicy`)
132
- * 18 -- command content signals (`agent` frontmatter, `$ARGUMENTS`/`$1`-`$3`)
133
- * 8 -- weak agent signal (`model` alone)
134
- * 5 -- knowledge fallback (any unclassified `.md`)
135
- *
136
- * Command signals at 18 override directory hints (10/15) because the content
137
- * unambiguously identifies a command template. Agent-exclusive signals at 20
138
- * still win over command signals when both are present.
139
- */
140
- export function smartMdMatcher(ctx) {
132
+ function classifyBySmartMd(ctx) {
141
133
  if (ctx.ext !== ".md")
142
134
  return null;
135
+ // Never read the body of a file under secrets/ — the whole file is the
136
+ // secret value. The directory matcher classifies it as `secret` without
137
+ // touching content; bailing here keeps classifyBySmartMd from calling
138
+ // ctx.content()/frontmatter() on secret material.
139
+ if (ctx.ancestorDirs.includes("secrets"))
140
+ return null;
141
+ // README.md is documentation, never a workflow/agent/command even when the
142
+ // body shape would otherwise classify (e.g. step-list inside a project
143
+ // README under workflows/). Fall straight through to `knowledge`.
144
+ if (isTypedDirDocFile(ctx.fileName)) {
145
+ return { type: "knowledge", specificity: 5 };
146
+ }
143
147
  const body = ctx.content();
144
148
  if (looksLikeWorkflow(body)) {
145
- return { type: "workflow", specificity: 19, renderer: "workflow-md" };
149
+ return { type: "workflow", specificity: 19 };
146
150
  }
147
151
  const fm = ctx.frontmatter();
148
152
  if (fm) {
149
- // Agent-exclusive indicators: toolPolicy or tools
150
- // These return high specificity (20) to override everything else.
151
153
  if ("toolPolicy" in fm || "tools" in fm) {
152
- return { type: "agent", specificity: 20, renderer: "agent-md" };
154
+ return { type: "agent", specificity: 20 };
153
155
  }
154
- // Command signal: `agent` frontmatter key names a dispatch target.
155
- // This is an OpenCode convention specific to commands.
156
156
  if ("agent" in fm) {
157
- return { type: "command", specificity: 18, renderer: "command-md" };
157
+ return { type: "command", specificity: 18 };
158
158
  }
159
159
  }
160
- // Command signal: body contains $ARGUMENTS or $1/$2/$3 placeholders.
161
- // These are definitively command template patterns (OpenCode convention).
162
160
  if (COMMAND_PLACEHOLDER_RE.test(body)) {
163
- return { type: "command", specificity: 18, renderer: "command-md" };
161
+ return { type: "command", specificity: 18 };
164
162
  }
165
- if (fm) {
166
- // model alone is a weaker agent signal (specificity 8) -- it can appear
167
- // on commands too (OpenCode convention). Directory hints (10/15) win
168
- // when the file lives in commands/, but model still classifies an .md
169
- // as agent when no directory hint is present.
170
- if ("model" in fm) {
171
- return { type: "agent", specificity: 8, renderer: "agent-md" };
172
- }
163
+ if (fm && "model" in fm) {
164
+ return { type: "agent", specificity: 8 };
173
165
  }
174
- // Weak fallback: any .md file is assumed to be knowledge
175
- return { type: "knowledge", specificity: 5, renderer: "knowledge-md" };
166
+ return { type: "knowledge", specificity: 5 };
176
167
  }
177
- // ── wikiMatcher (specificity: 20) ──────────────────────────────────────────
178
- /**
179
- * Classify any `.md` file that lives under `wikis/<name>/…` as `wiki`.
180
- *
181
- * Registered AFTER `smartMdMatcher` so the registered-later-wins tiebreaker
182
- * puts wiki ahead of agent at specificity 20. That means a wiki page with
183
- * agent-style frontmatter (e.g. `tools:`) still classifies as a wiki page,
184
- * not an agent. That's intentional — the directory is the authoritative
185
- * signal: files under `wikis/` are wiki content.
186
- *
187
- * Requires at least one path segment after `wikis/` (the wiki name) — a
188
- * stray `.md` at the bare `wikis/` root is not a wiki page.
189
- */
190
- export function wikiMatcher(ctx) {
168
+ function classifyByWiki(ctx) {
191
169
  if (ctx.ext !== ".md")
192
170
  return null;
193
171
  const idx = ctx.ancestorDirs.indexOf("wikis");
@@ -195,10 +173,43 @@ export function wikiMatcher(ctx) {
195
173
  return null;
196
174
  if (idx + 1 >= ctx.ancestorDirs.length)
197
175
  return null;
198
- return { type: "wiki", specificity: 20, renderer: "wiki-md" };
176
+ return { type: "wiki", specificity: 20 };
177
+ }
178
+ // ---------------------------------------------------------------------------
179
+ // Adapter: MatchFact → MatchResult
180
+ // ---------------------------------------------------------------------------
181
+ function toMatchResult(ctx, classify) {
182
+ const fact = classify(ctx);
183
+ if (!fact)
184
+ return null;
185
+ const renderer = defaultRendererRegistry.rendererNameFor(fact.type);
186
+ if (!renderer)
187
+ return null;
188
+ return {
189
+ type: fact.type,
190
+ specificity: fact.specificity,
191
+ renderer,
192
+ ...(fact.meta ? { meta: fact.meta } : {}),
193
+ };
194
+ }
195
+ // ---------------------------------------------------------------------------
196
+ // Public matchers (API unchanged)
197
+ // ---------------------------------------------------------------------------
198
+ export function extensionMatcher(ctx) {
199
+ return toMatchResult(ctx, classifyByExtension);
200
+ }
201
+ export function directoryMatcher(ctx) {
202
+ return toMatchResult(ctx, classifyByDirectory);
203
+ }
204
+ export function parentDirHintMatcher(ctx) {
205
+ return toMatchResult(ctx, classifyByParentDirHint);
206
+ }
207
+ export function smartMdMatcher(ctx) {
208
+ return toMatchResult(ctx, classifyBySmartMd);
209
+ }
210
+ export function wikiMatcher(ctx) {
211
+ return toMatchResult(ctx, classifyByWiki);
199
212
  }
200
- // ── Registration ────────────────────────────────────────────────────────────
201
- /** All built-in matchers in registration order (later wins ties). */
202
213
  const builtinMatchers = [
203
214
  extensionMatcher,
204
215
  directoryMatcher,
@@ -206,10 +217,6 @@ const builtinMatchers = [
206
217
  smartMdMatcher,
207
218
  wikiMatcher,
208
219
  ];
209
- /**
210
- * Register all built-in matchers with the file-context registry.
211
- * Called once from the CLI entry point (or ensureBuiltinsRegistered).
212
- */
213
220
  export function registerBuiltinMatchers() {
214
221
  for (const matcher of builtinMatchers) {
215
222
  registerMatcher(matcher);