akm-cli 0.6.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +66 -0
- package/dist/{cli.js → src/cli.js} +712 -34
- package/dist/{commands → src/commands}/config-cli.js +47 -4
- package/dist/src/commands/distill.js +283 -0
- package/dist/src/commands/events.js +108 -0
- package/dist/src/commands/history.js +191 -0
- package/dist/{commands → src/commands}/installed-stashes.js +1 -1
- package/dist/src/commands/proposal.js +119 -0
- package/dist/src/commands/propose.js +171 -0
- package/dist/src/commands/reflect.js +193 -0
- package/dist/{commands → src/commands}/registry-search.js +71 -7
- package/dist/{commands → src/commands}/remember.js +12 -0
- package/dist/{commands → src/commands}/search.js +104 -4
- package/dist/{commands → src/commands}/self-update.js +4 -3
- package/dist/{commands → src/commands}/show.js +73 -0
- package/dist/{commands → src/commands}/source-add.js +5 -1
- package/dist/{commands → src/commands}/source-manage.js +7 -1
- package/dist/{core → src/core}/asset-ref.js +5 -5
- package/dist/{core → src/core}/asset-spec.js +12 -0
- package/dist/{core → src/core}/common.js +1 -1
- package/dist/{core → src/core}/config.js +203 -121
- package/dist/{core → src/core}/errors.js +4 -0
- package/dist/src/core/events.js +239 -0
- package/dist/src/core/lesson-lint.js +86 -0
- package/dist/src/core/proposals.js +406 -0
- package/dist/src/core/warn.js +72 -0
- package/dist/{core → src/core}/write-source.js +80 -5
- package/dist/{indexer → src/indexer}/db-search.js +114 -24
- package/dist/{indexer → src/indexer}/db.js +76 -23
- package/dist/{indexer → src/indexer}/file-context.js +0 -3
- package/dist/src/indexer/graph-boost.js +179 -0
- package/dist/src/indexer/graph-extraction.js +212 -0
- package/dist/{indexer → src/indexer}/indexer.js +88 -7
- package/dist/{indexer → src/indexer}/matchers.js +1 -1
- package/dist/src/indexer/memory-inference.js +263 -0
- package/dist/{indexer → src/indexer}/metadata.js +111 -3
- package/dist/{indexer → src/indexer}/search-source.js +4 -2
- package/dist/src/integrations/agent/config.js +292 -0
- package/dist/src/integrations/agent/detect.js +94 -0
- package/dist/src/integrations/agent/index.js +17 -0
- package/dist/src/integrations/agent/profiles.js +65 -0
- package/dist/src/integrations/agent/prompts.js +167 -0
- package/dist/src/integrations/agent/spawn.js +272 -0
- package/dist/{integrations → src/integrations}/github.js +9 -3
- package/dist/{integrations → src/integrations}/lockfile.js +0 -26
- package/dist/{llm → src/llm}/client.js +33 -2
- package/dist/{llm → src/llm}/embedders/remote.js +37 -3
- package/dist/src/llm/feature-gate.js +108 -0
- package/dist/src/llm/graph-extract.js +107 -0
- package/dist/src/llm/index-passes.js +35 -0
- package/dist/src/llm/memory-infer.js +86 -0
- package/dist/{output → src/output}/cli-hints.js +15 -2
- package/dist/{output → src/output}/renderers.js +63 -2
- package/dist/src/output/shapes.js +523 -0
- package/dist/src/output/text.js +1116 -0
- package/dist/{registry → src/registry}/build-index.js +19 -8
- package/dist/{registry → src/registry}/factory.js +0 -8
- package/dist/{registry → src/registry}/providers/static-index.js +6 -3
- package/dist/{registry → src/registry}/resolve.js +68 -2
- package/dist/{setup → src/setup}/setup.js +52 -5
- package/dist/{sources → src/sources}/providers/git.js +7 -15
- package/dist/{wiki → src/wiki}/wiki.js +54 -6
- package/dist/{workflows → src/workflows}/runs.js +37 -3
- package/dist/tests/add-website-source.test.js +119 -0
- package/dist/tests/agent/agent-config-loader.test.js +70 -0
- package/dist/tests/agent/agent-config.test.js +221 -0
- package/dist/tests/agent/agent-detect.test.js +100 -0
- package/dist/tests/agent/agent-spawn.test.js +234 -0
- package/dist/tests/agent-output.test.js +186 -0
- package/dist/tests/architecture/agent-no-llm-sdk-guard.test.js +103 -0
- package/dist/tests/architecture/agent-spawn-seam.test.js +193 -0
- package/dist/tests/architecture/llm-stateless-seam.test.js +112 -0
- package/dist/tests/asset-ref.test.js +192 -0
- package/dist/tests/asset-registry.test.js +103 -0
- package/dist/tests/asset-spec.test.js +241 -0
- package/dist/tests/bench/attribution.test.js +996 -0
- package/dist/tests/bench/cleanup-sigint.test.js +83 -0
- package/dist/tests/bench/cleanup.js +234 -0
- package/dist/tests/bench/cleanup.test.js +166 -0
- package/dist/tests/bench/cli.js +1018 -0
- package/dist/tests/bench/cli.test.js +445 -0
- package/dist/tests/bench/compare.test.js +556 -0
- package/dist/tests/bench/corpus.js +317 -0
- package/dist/tests/bench/corpus.test.js +258 -0
- package/dist/tests/bench/doctor.js +525 -0
- package/dist/tests/bench/driver.js +401 -0
- package/dist/tests/bench/driver.test.js +584 -0
- package/dist/tests/bench/environment.js +233 -0
- package/dist/tests/bench/environment.test.js +199 -0
- package/dist/tests/bench/evolve-metrics.js +179 -0
- package/dist/tests/bench/evolve-metrics.test.js +187 -0
- package/dist/tests/bench/evolve.js +647 -0
- package/dist/tests/bench/evolve.test.js +624 -0
- package/dist/tests/bench/failure-modes.test.js +349 -0
- package/dist/tests/bench/feedback-integrity.test.js +457 -0
- package/dist/tests/bench/leakage.test.js +228 -0
- package/dist/tests/bench/learning-curve.test.js +134 -0
- package/dist/tests/bench/metrics.js +2395 -0
- package/dist/tests/bench/metrics.test.js +1150 -0
- package/dist/tests/bench/no-os-tmpdir-invariant.test.js +43 -0
- package/dist/tests/bench/opencode-config.js +194 -0
- package/dist/tests/bench/opencode-config.test.js +370 -0
- package/dist/tests/bench/report.js +1885 -0
- package/dist/tests/bench/report.test.js +1038 -0
- package/dist/tests/bench/run-config.js +355 -0
- package/dist/tests/bench/run-config.test.js +298 -0
- package/dist/tests/bench/run-curate-test.js +32 -0
- package/dist/tests/bench/run-failing-tasks.js +56 -0
- package/dist/tests/bench/run-full-bench.js +51 -0
- package/dist/tests/bench/run-items36-targeted.js +69 -0
- package/dist/tests/bench/run-nano-quick.js +42 -0
- package/dist/tests/bench/run-waveg-targeted.js +62 -0
- package/dist/tests/bench/runner.js +699 -0
- package/dist/tests/bench/runner.test.js +958 -0
- package/dist/tests/bench/search-bridge.test.js +331 -0
- package/dist/tests/bench/tmp.js +131 -0
- package/dist/tests/bench/trajectory.js +116 -0
- package/dist/tests/bench/trajectory.test.js +127 -0
- package/dist/tests/bench/verifier.js +114 -0
- package/dist/tests/bench/verifier.test.js +118 -0
- package/dist/tests/bench/workflow-evaluator.js +557 -0
- package/dist/tests/bench/workflow-evaluator.test.js +421 -0
- package/dist/tests/bench/workflow-spec.js +345 -0
- package/dist/tests/bench/workflow-spec.test.js +363 -0
- package/dist/tests/bench/workflow-trace.js +472 -0
- package/dist/tests/bench/workflow-trace.test.js +254 -0
- package/dist/tests/benchmark-search-quality.js +536 -0
- package/dist/tests/benchmark-suite.js +1441 -0
- package/dist/tests/capture-cli.test.js +112 -0
- package/dist/tests/cli-errors.test.js +204 -0
- package/dist/tests/commands/events.test.js +370 -0
- package/dist/tests/commands/history.test.js +418 -0
- package/dist/tests/commands/import.test.js +103 -0
- package/dist/tests/commands/proposal-cli.test.js +209 -0
- package/dist/tests/commands/reflect-propose-cli.test.js +333 -0
- package/dist/tests/commands/remember.test.js +97 -0
- package/dist/tests/commands/scope-flags.test.js +300 -0
- package/dist/tests/commands/search.test.js +537 -0
- package/dist/tests/commands/show-indexer-parity.test.js +117 -0
- package/dist/tests/commands/show.test.js +294 -0
- package/dist/tests/common.test.js +266 -0
- package/dist/tests/completions.test.js +142 -0
- package/dist/tests/config-cli.test.js +193 -0
- package/dist/tests/config-llm-features.test.js +139 -0
- package/dist/tests/config.test.js +569 -0
- package/dist/tests/contracts/migration-baseline.test.js +43 -0
- package/dist/tests/contracts/reflect-propose-envelope.test.js +139 -0
- package/dist/tests/contracts/spec-helpers.js +46 -0
- package/dist/tests/contracts/v1-spec-section-11-proposal-queue.test.js +228 -0
- package/dist/tests/contracts/v1-spec-section-12-agent-config.test.js +56 -0
- package/dist/tests/contracts/v1-spec-section-13-lesson-type.test.js +34 -0
- package/dist/tests/contracts/v1-spec-section-14-llm-features.test.js +94 -0
- package/dist/tests/contracts/v1-spec-section-4-1-asset-types.test.js +39 -0
- package/dist/tests/contracts/v1-spec-section-4-2-quality-rules.test.js +44 -0
- package/dist/tests/contracts/v1-spec-section-5-configuration.test.js +47 -0
- package/dist/tests/contracts/v1-spec-section-6-orchestration.test.js +40 -0
- package/dist/tests/contracts/v1-spec-section-7-module-layout.test.js +58 -0
- package/dist/tests/contracts/v1-spec-section-8-extension-points.test.js +34 -0
- package/dist/tests/contracts/v1-spec-section-9-4-cli-surface.test.js +75 -0
- package/dist/tests/contracts/v1-spec-section-9-7-llm-agent-boundary.test.js +36 -0
- package/dist/tests/core/write-source.test.js +366 -0
- package/dist/tests/curate-command.test.js +87 -0
- package/dist/tests/db-scoring.test.js +201 -0
- package/dist/tests/db.test.js +654 -0
- package/dist/tests/distill-cli-flag.test.js +208 -0
- package/dist/tests/distill.test.js +515 -0
- package/dist/tests/docker-install.test.js +120 -0
- package/dist/tests/e2e.test.js +1419 -0
- package/dist/tests/embedder.test.js +340 -0
- package/dist/tests/embedding-model-config.test.js +379 -0
- package/dist/tests/feedback-command.test.js +172 -0
- package/dist/tests/file-context.test.js +552 -0
- package/dist/tests/fixtures/scripts/git/summarize-diff.js +9 -0
- package/dist/tests/fixtures/scripts/lint/eslint-check.js +7 -0
- package/dist/tests/fixtures/stashes/load.js +166 -0
- package/dist/tests/fixtures/stashes/load.test.js +97 -0
- package/dist/tests/fixtures/stashes/ranking-baseline/scripts/mem0-search.js +12 -0
- package/dist/tests/frontmatter.test.js +190 -0
- package/dist/tests/fts-field-weighting.test.js +254 -0
- package/dist/tests/fuzzy-search.test.js +230 -0
- package/dist/tests/git-provider-clone.test.js +45 -0
- package/dist/tests/github.test.js +161 -0
- package/dist/tests/graph-boost-ranking.test.js +305 -0
- package/dist/tests/graph-extraction.test.js +282 -0
- package/dist/tests/helpers/usage-events.js +8 -0
- package/dist/tests/index-pass-llm.test.js +161 -0
- package/dist/tests/indexer.test.js +570 -0
- package/dist/tests/info-command.test.js +166 -0
- package/dist/tests/init.test.js +69 -0
- package/dist/tests/install-script.test.js +246 -0
- package/dist/tests/integration/agent-real-profile.test.js +94 -0
- package/dist/tests/issue-36-repro.test.js +304 -0
- package/dist/tests/issues-191-194.test.js +160 -0
- package/dist/tests/lesson-lint.test.js +111 -0
- package/dist/tests/llm-client.test.js +115 -0
- package/dist/tests/llm-feature-gate.test.js +151 -0
- package/dist/tests/llm.test.js +139 -0
- package/dist/tests/lockfile.test.js +216 -0
- package/dist/tests/manifest.test.js +205 -0
- package/dist/tests/markdown.test.js +126 -0
- package/dist/tests/matchers-unit.test.js +189 -0
- package/dist/tests/memory-inference.test.js +299 -0
- package/dist/tests/merge-scoring.test.js +136 -0
- package/dist/tests/metadata.test.js +313 -0
- package/dist/tests/migration-help.test.js +89 -0
- package/dist/tests/origin-resolve.test.js +124 -0
- package/dist/tests/output-baseline.test.js +218 -0
- package/dist/tests/output-shapes-unit.test.js +478 -0
- package/dist/tests/parallel-search.test.js +272 -0
- package/dist/tests/parameter-metadata.test.js +365 -0
- package/dist/tests/paths.test.js +177 -0
- package/dist/tests/progressive-disclosure.test.js +280 -0
- package/dist/tests/proposals.test.js +279 -0
- package/dist/tests/proposed-quality.test.js +271 -0
- package/dist/tests/provider-registry.test.js +32 -0
- package/dist/tests/ranking-regression.test.js +548 -0
- package/dist/tests/reflect-propose.test.js +455 -0
- package/dist/tests/registry-build-index.test.js +394 -0
- package/dist/tests/registry-cli.test.js +290 -0
- package/dist/tests/registry-index-v2.test.js +430 -0
- package/dist/tests/registry-install.test.js +728 -0
- package/dist/tests/registry-providers/parity.test.js +189 -0
- package/dist/tests/registry-providers/skills-sh.test.js +309 -0
- package/dist/tests/registry-providers/static-index.test.js +238 -0
- package/dist/tests/registry-resolve.test.js +126 -0
- package/dist/tests/registry-search.test.js +923 -0
- package/dist/tests/remember-frontmatter.test.js +378 -0
- package/dist/tests/remember-unit.test.js +123 -0
- package/dist/tests/ripgrep-install.test.js +251 -0
- package/dist/tests/ripgrep-resolve.test.js +108 -0
- package/dist/tests/ripgrep.test.js +163 -0
- package/dist/tests/save-command.test.js +94 -0
- package/dist/tests/save-trust-qa-fixes.test.js +270 -0
- package/dist/tests/scoring-pipeline.test.js +648 -0
- package/dist/tests/search-include-proposed-cli.test.js +118 -0
- package/dist/tests/self-update.test.js +442 -0
- package/dist/tests/semantic-search-e2e.test.js +512 -0
- package/dist/tests/semantic-status.test.js +471 -0
- package/dist/tests/setup-run.integration.js +877 -0
- package/dist/tests/setup-wizard.test.js +198 -0
- package/dist/tests/setup.test.js +131 -0
- package/dist/tests/source-add.test.js +11 -0
- package/dist/tests/source-clone.test.js +254 -0
- package/dist/tests/source-manage.test.js +366 -0
- package/dist/tests/source-providers/filesystem.test.js +82 -0
- package/dist/tests/source-providers/git.test.js +252 -0
- package/dist/tests/source-providers/website.test.js +128 -0
- package/dist/tests/source-qa-fixes.test.js +286 -0
- package/dist/tests/source-registry.test.js +350 -0
- package/dist/tests/source-resolve.test.js +100 -0
- package/dist/tests/source-source.test.js +281 -0
- package/dist/tests/source.test.js +533 -0
- package/dist/tests/tar-utils-scan.test.js +73 -0
- package/dist/tests/toggle-components.test.js +73 -0
- package/dist/tests/usage-telemetry.test.js +265 -0
- package/dist/tests/utility-scoring.test.js +558 -0
- package/dist/tests/vault-load-error.test.js +78 -0
- package/dist/tests/vault-qa-fixes.test.js +194 -0
- package/dist/tests/vault.test.js +429 -0
- package/dist/tests/vector-search.test.js +608 -0
- package/dist/tests/walker.test.js +252 -0
- package/dist/tests/wave2-cluster-bc.test.js +228 -0
- package/dist/tests/wave2-cluster-d.test.js +180 -0
- package/dist/tests/wave2-cluster-e.test.js +179 -0
- package/dist/tests/wiki-qa-fixes.test.js +270 -0
- package/dist/tests/wiki.test.js +529 -0
- package/dist/tests/workflow-cli.test.js +271 -0
- package/dist/tests/workflow-markdown.test.js +171 -0
- package/dist/tests/workflow-path-escape.test.js +132 -0
- package/dist/tests/workflow-qa-fixes.test.js +395 -0
- package/dist/tests/workflows/indexer-rejection.test.js +213 -0
- package/docs/README.md +8 -0
- package/docs/migration/release-notes/0.7.0.md +244 -0
- package/package.json +2 -2
- package/dist/core/warn.js +0 -27
- package/dist/output/shapes.js +0 -212
- package/dist/output/text.js +0 -520
- /package/dist/{commands → src/commands}/completions.js +0 -0
- /package/dist/{commands → src/commands}/curate.js +0 -0
- /package/dist/{commands → src/commands}/info.js +0 -0
- /package/dist/{commands → src/commands}/init.js +0 -0
- /package/dist/{commands → src/commands}/install-audit.js +0 -0
- /package/dist/{commands → src/commands}/migration-help.js +0 -0
- /package/dist/{commands → src/commands}/source-clone.js +0 -0
- /package/dist/{commands → src/commands}/vault.js +0 -0
- /package/dist/{core → src/core}/asset-registry.js +0 -0
- /package/dist/{core → src/core}/frontmatter.js +0 -0
- /package/dist/{core → src/core}/markdown.js +0 -0
- /package/dist/{core → src/core}/paths.js +0 -0
- /package/dist/{indexer → src/indexer}/manifest.js +0 -0
- /package/dist/{indexer → src/indexer}/search-fields.js +0 -0
- /package/dist/{indexer → src/indexer}/semantic-status.js +0 -0
- /package/dist/{indexer → src/indexer}/usage-events.js +0 -0
- /package/dist/{indexer → src/indexer}/walker.js +0 -0
- /package/dist/{llm → src/llm}/embedder.js +0 -0
- /package/dist/{llm → src/llm}/embedders/cache.js +0 -0
- /package/dist/{llm → src/llm}/embedders/local.js +0 -0
- /package/dist/{llm → src/llm}/embedders/types.js +0 -0
- /package/dist/{llm → src/llm}/metadata-enhance.js +0 -0
- /package/dist/{output → src/output}/context.js +0 -0
- /package/dist/{registry → src/registry}/create-provider-registry.js +0 -0
- /package/dist/{registry → src/registry}/origin-resolve.js +0 -0
- /package/dist/{registry → src/registry}/providers/index.js +0 -0
- /package/dist/{registry → src/registry}/providers/skills-sh.js +0 -0
- /package/dist/{registry → src/registry}/providers/types.js +0 -0
- /package/dist/{registry → src/registry}/types.js +0 -0
- /package/dist/{setup → src/setup}/detect.js +0 -0
- /package/dist/{setup → src/setup}/ripgrep-install.js +0 -0
- /package/dist/{setup → src/setup}/ripgrep-resolve.js +0 -0
- /package/dist/{setup → src/setup}/steps.js +0 -0
- /package/dist/{sources → src/sources}/include.js +0 -0
- /package/dist/{sources → src/sources}/provider-factory.js +0 -0
- /package/dist/{sources → src/sources}/provider.js +0 -0
- /package/dist/{sources → src/sources}/providers/filesystem.js +0 -0
- /package/dist/{sources → src/sources}/providers/index.js +0 -0
- /package/dist/{sources → src/sources}/providers/install-types.js +0 -0
- /package/dist/{sources → src/sources}/providers/npm.js +0 -0
- /package/dist/{sources → src/sources}/providers/provider-utils.js +0 -0
- /package/dist/{sources → src/sources}/providers/sync-from-ref.js +0 -0
- /package/dist/{sources → src/sources}/providers/tar-utils.js +0 -0
- /package/dist/{sources → src/sources}/providers/website.js +0 -0
- /package/dist/{sources → src/sources}/resolve.js +0 -0
- /package/dist/{sources → src/sources}/types.js +0 -0
- /package/dist/{templates → src/templates}/wiki-templates.js +0 -0
- /package/dist/{version.js → src/version.js} +0 -0
- /package/dist/{workflows → src/workflows}/authoring.js +0 -0
- /package/dist/{workflows → src/workflows}/cli.js +0 -0
- /package/dist/{workflows → src/workflows}/db.js +0 -0
- /package/dist/{workflows → src/workflows}/document-cache.js +0 -0
- /package/dist/{workflows → src/workflows}/parser.js +0 -0
- /package/dist/{workflows → src/workflows}/renderer.js +0 -0
- /package/dist/{workflows → src/workflows}/schema.js +0 -0
- /package/dist/{workflows → src/workflows}/validator.js +0 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-feature LLM gates (v1 spec §14).
|
|
3
|
+
*
|
|
4
|
+
* Every bounded in-tree LLM call site in akm is addressed by exactly one
|
|
5
|
+
* feature key under `llm.features.*`. This module is the single seam call
|
|
6
|
+
* sites use to ask "should I run?" and "if I run and fail, what do I return?"
|
|
7
|
+
*
|
|
8
|
+
* The seam is intentionally tiny:
|
|
9
|
+
*
|
|
10
|
+
* - `isLlmFeatureEnabled(config, feature)` — pure predicate, no side
|
|
11
|
+
* effects, no I/O. Returns `true` only when the feature flag is the
|
|
12
|
+
* literal boolean `true` in config. Defaults are `false` per v1
|
|
13
|
+
* spec §14 — adding a flag to the schema is a non-event until the user
|
|
14
|
+
* opts in.
|
|
15
|
+
* - `tryLlmFeature(feature, config, fn, fallback, opts?)` — single-call
|
|
16
|
+
* wrapper that runs `fn()` only when the gate is open, enforces a hard
|
|
17
|
+
* timeout (default 30s — overridable per call), and returns `fallback`
|
|
18
|
+
* on disablement, throw, or timeout. The wrapper is referentially
|
|
19
|
+
* transparent for any given (gate-state, fn-result) pair: no module
|
|
20
|
+
* state is mutated.
|
|
21
|
+
*
|
|
22
|
+
* Statelessness invariant (v1 spec §14.4): nothing in this module holds
|
|
23
|
+
* state across calls. There are no caches, no module-level singletons, no
|
|
24
|
+
* persistent connections. The architecture seam test
|
|
25
|
+
* (`tests/architecture/llm-stateless-seam.test.ts`) does not currently
|
|
26
|
+
* inspect this file but the same rule applies — keep all exports as pure
|
|
27
|
+
* functions.
|
|
28
|
+
*/
|
|
29
|
+
/**
|
|
30
|
+
* Pure predicate: is the named feature gate explicitly enabled in `config`?
|
|
31
|
+
*
|
|
32
|
+
* Returns `false` when:
|
|
33
|
+
* - the LLM block is missing,
|
|
34
|
+
* - the `features` block is missing,
|
|
35
|
+
* - the key is absent (defaults are `false`),
|
|
36
|
+
* - the key is set to `false`.
|
|
37
|
+
*/
|
|
38
|
+
export function isLlmFeatureEnabled(config, feature) {
|
|
39
|
+
if (!config?.llm?.features)
|
|
40
|
+
return false;
|
|
41
|
+
return config.llm.features[feature] === true;
|
|
42
|
+
}
|
|
43
|
+
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
44
|
+
/**
|
|
45
|
+
* Run `fn()` only if `isLlmFeatureEnabled(config, feature)` is `true`. On
|
|
46
|
+
* disablement, throw, or timeout, return `fallback` (or — if it is a
|
|
47
|
+
* thunk — the value produced by calling it).
|
|
48
|
+
*
|
|
49
|
+
* The fallback may be a value or a synchronous/async function returning a
|
|
50
|
+
* value. The thunk form lets call sites encode "run the deterministic
|
|
51
|
+
* pipeline" without paying for it in the success path:
|
|
52
|
+
*
|
|
53
|
+
* ```ts
|
|
54
|
+
* const ranked = await tryLlmFeature(
|
|
55
|
+
* "curate_rerank",
|
|
56
|
+
* config,
|
|
57
|
+
* () => llmRerank(candidates),
|
|
58
|
+
* () => deterministicRerank(candidates),
|
|
59
|
+
* );
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export async function tryLlmFeature(feature, config, fn, fallback, opts) {
|
|
63
|
+
const resolveFallback = async () => typeof fallback === "function" ? await fallback() : fallback;
|
|
64
|
+
if (!isLlmFeatureEnabled(config, feature)) {
|
|
65
|
+
opts?.onFallback?.({ feature, reason: "disabled" });
|
|
66
|
+
return resolveFallback();
|
|
67
|
+
}
|
|
68
|
+
const timeoutMs = opts?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
69
|
+
try {
|
|
70
|
+
if (timeoutMs <= 0) {
|
|
71
|
+
return await fn();
|
|
72
|
+
}
|
|
73
|
+
return await runWithTimeout(fn, timeoutMs, feature);
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
77
|
+
const reason = error instanceof LlmFeatureTimeoutError ? "timeout" : "error";
|
|
78
|
+
opts?.onFallback?.({ feature, reason, error });
|
|
79
|
+
return resolveFallback();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/** Specific error class so call sites and the wrapper can tell timeouts apart from generic throws. */
|
|
83
|
+
export class LlmFeatureTimeoutError extends Error {
|
|
84
|
+
feature;
|
|
85
|
+
timeoutMs;
|
|
86
|
+
constructor(feature, timeoutMs) {
|
|
87
|
+
super(`LLM feature "${feature}" timed out after ${timeoutMs}ms.`);
|
|
88
|
+
this.name = "LlmFeatureTimeoutError";
|
|
89
|
+
this.feature = feature;
|
|
90
|
+
this.timeoutMs = timeoutMs;
|
|
91
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
async function runWithTimeout(fn, timeoutMs, feature) {
|
|
95
|
+
let timer;
|
|
96
|
+
try {
|
|
97
|
+
return await new Promise((resolve, reject) => {
|
|
98
|
+
timer = setTimeout(() => reject(new LlmFeatureTimeoutError(feature, timeoutMs)), timeoutMs);
|
|
99
|
+
Promise.resolve()
|
|
100
|
+
.then(() => fn())
|
|
101
|
+
.then(resolve, reject);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
finally {
|
|
105
|
+
if (timer)
|
|
106
|
+
clearTimeout(timer);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LLM helper for the `akm index` graph-extraction pass (#207).
|
|
3
|
+
*
|
|
4
|
+
* Given a single asset body (typically a `memory:` or `knowledge:` file),
|
|
5
|
+
* asks the configured LLM to surface the entities mentioned in it and the
|
|
6
|
+
* relations between them. The pass itself
|
|
7
|
+
* (`src/indexer/graph-extraction.ts`) is responsible for deciding which
|
|
8
|
+
* files to extract, persisting the resulting nodes/edges to a stash-local
|
|
9
|
+
* `graph.json` artifact, and feeding the artifact into the FTS5+boosts
|
|
10
|
+
* search pipeline as a single boost component.
|
|
11
|
+
*
|
|
12
|
+
* This module is intentionally tiny and stateless so tests can stub it via
|
|
13
|
+
* `mock.module("../src/llm/graph-extract", ...)` without hitting a network.
|
|
14
|
+
*
|
|
15
|
+
* Locked v1 contract (#208): the LLM connection always comes from the
|
|
16
|
+
* shared `akm.llm` block — never from a per-pass override. Callers obtain
|
|
17
|
+
* the connection via `resolveIndexPassLLM("graph", config)` and pass it
|
|
18
|
+
* straight through.
|
|
19
|
+
*/
|
|
20
|
+
import { toErrorMessage } from "../core/common";
|
|
21
|
+
import { warn } from "../core/warn";
|
|
22
|
+
import { chatCompletion, parseJsonResponse } from "./client";
|
|
23
|
+
/** Hard cap on body chars sent to the model. */
|
|
24
|
+
const MAX_BODY_CHARS = 4000;
|
|
25
|
+
/** Hard cap on entities returned per asset — guards against runaway LLM output. */
|
|
26
|
+
const MAX_ENTITIES_PER_ASSET = 32;
|
|
27
|
+
/** Hard cap on relations returned per asset. */
|
|
28
|
+
const MAX_RELATIONS_PER_ASSET = 32;
|
|
29
|
+
/** Hard timeout for the LLM call; an `akm index` run must not hang on a misbehaving endpoint. */
|
|
30
|
+
const LLM_TIMEOUT_MS = 30_000;
|
|
31
|
+
const SYSTEM_PROMPT = "You extract a knowledge graph from developer notes. Return only valid JSON. " + "No prose, no markdown fences.";
|
|
32
|
+
const USER_PROMPT_PREFIX = `Extract entities and relations from the asset body below.
|
|
33
|
+
|
|
34
|
+
Rules:
|
|
35
|
+
- Output ONLY a JSON object: {"entities": ["Entity One", ...], "relations": [{"from": "A", "to": "B", "type": "uses"}, ...]}.
|
|
36
|
+
- Entities are short, canonical noun phrases (project names, services, tools, people, file/dir names, technical concepts).
|
|
37
|
+
- Relations connect two entities that both appear in the entities array.
|
|
38
|
+
- "type" is a short verb phrase (e.g. "uses", "depends on", "owns", "documents"). Optional; omit when unsure.
|
|
39
|
+
- Drop pleasantries, meta-commentary, and timestamps.
|
|
40
|
+
- Limit to at most ${MAX_ENTITIES_PER_ASSET} entities and ${MAX_RELATIONS_PER_ASSET} relations per asset.
|
|
41
|
+
- Return {"entities": [], "relations": []} if the body has no extractable graph content.
|
|
42
|
+
|
|
43
|
+
Asset body:
|
|
44
|
+
`;
|
|
45
|
+
/**
|
|
46
|
+
* Extract entities and relations from a single asset body via the configured LLM.
|
|
47
|
+
*
|
|
48
|
+
* Returns `{entities: [], relations: []}` on any failure (timeout, invalid
|
|
49
|
+
* JSON, empty response). Errors are logged via `warn()` but never thrown — a
|
|
50
|
+
* failed extraction for one asset must not abort the rest of the index pass.
|
|
51
|
+
*/
|
|
52
|
+
export async function extractGraphFromBody(llmConfig, body) {
|
|
53
|
+
const empty = { entities: [], relations: [] };
|
|
54
|
+
const trimmedBody = body.trim();
|
|
55
|
+
if (!trimmedBody)
|
|
56
|
+
return empty;
|
|
57
|
+
const userPrompt = `${USER_PROMPT_PREFIX}${trimmedBody.slice(0, MAX_BODY_CHARS)}`;
|
|
58
|
+
let timeoutHandle;
|
|
59
|
+
try {
|
|
60
|
+
const raw = await Promise.race([
|
|
61
|
+
chatCompletion(llmConfig, [
|
|
62
|
+
{ role: "system", content: SYSTEM_PROMPT },
|
|
63
|
+
{ role: "user", content: userPrompt },
|
|
64
|
+
], { maxTokens: 1024, temperature: 0.1 }),
|
|
65
|
+
new Promise((_, reject) => {
|
|
66
|
+
timeoutHandle = setTimeout(() => reject(new Error("graph extraction timed out")), LLM_TIMEOUT_MS);
|
|
67
|
+
}),
|
|
68
|
+
]);
|
|
69
|
+
if (!raw)
|
|
70
|
+
return empty;
|
|
71
|
+
const parsed = parseJsonResponse(raw);
|
|
72
|
+
if (!parsed) {
|
|
73
|
+
warn("graph extraction: invalid JSON response from LLM; skipping asset.");
|
|
74
|
+
return empty;
|
|
75
|
+
}
|
|
76
|
+
const entities = Array.isArray(parsed.entities)
|
|
77
|
+
? parsed.entities
|
|
78
|
+
.filter((e) => typeof e === "string")
|
|
79
|
+
.map((e) => e.trim())
|
|
80
|
+
.filter((e) => e.length > 0)
|
|
81
|
+
.slice(0, MAX_ENTITIES_PER_ASSET)
|
|
82
|
+
: [];
|
|
83
|
+
const entitySet = new Set(entities);
|
|
84
|
+
const relations = Array.isArray(parsed.relations)
|
|
85
|
+
? parsed.relations
|
|
86
|
+
.filter((r) => typeof r === "object" && r !== null && !Array.isArray(r))
|
|
87
|
+
.map((r) => ({
|
|
88
|
+
from: typeof r.from === "string" ? r.from.trim() : "",
|
|
89
|
+
to: typeof r.to === "string" ? r.to.trim() : "",
|
|
90
|
+
type: typeof r.type === "string" && r.type.trim() ? r.type.trim() : undefined,
|
|
91
|
+
}))
|
|
92
|
+
// Both endpoints must be non-empty AND mentioned in entities[];
|
|
93
|
+
// dangling relations are noise and inflate the boost component.
|
|
94
|
+
.filter((r) => r.from && r.to && entitySet.has(r.from) && entitySet.has(r.to))
|
|
95
|
+
.slice(0, MAX_RELATIONS_PER_ASSET)
|
|
96
|
+
: [];
|
|
97
|
+
return { entities, relations };
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
warn(`graph extraction failed: ${toErrorMessage(err)}`);
|
|
101
|
+
return empty;
|
|
102
|
+
}
|
|
103
|
+
finally {
|
|
104
|
+
if (timeoutHandle !== undefined)
|
|
105
|
+
clearTimeout(timeoutHandle);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-pass LLM config resolution for `akm index`.
|
|
3
|
+
*
|
|
4
|
+
* Locked v1 contract (#208):
|
|
5
|
+
* - There is exactly one provider/model configuration: `akm.llm`.
|
|
6
|
+
* - Every LLM-using pass inside `akm index` defaults to that block.
|
|
7
|
+
* - A pass can be opted out individually with `index.<passName>.llm = false`.
|
|
8
|
+
* - Any attempt to supply provider/model fields under `index.<passName>` is
|
|
9
|
+
* rejected at config-load time by `parseIndexConfig` in
|
|
10
|
+
* {@link ../core/config.ts} (`ConfigError("INVALID_CONFIG_FILE")`).
|
|
11
|
+
*
|
|
12
|
+
* Passes plug in by calling {@link resolveIndexPassLLM} with their pass
|
|
13
|
+
* name (e.g. `"memory"` for #201's memory-inference pass, `"graph"` for
|
|
14
|
+
* #207's graph-extraction pass). They do not read `config.llm` directly.
|
|
15
|
+
* This keeps the config surface small and the wiring uniform.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Resolve the {@link LlmConnectionConfig} a single index pass should use, or
|
|
19
|
+
* `undefined` when the pass should run without an LLM.
|
|
20
|
+
*
|
|
21
|
+
* Returns `undefined` if any of:
|
|
22
|
+
* - No top-level `akm.llm` block is configured.
|
|
23
|
+
* - The pass is explicitly opted out (`index.<passName>.llm === false`).
|
|
24
|
+
*
|
|
25
|
+
* Otherwise returns the shared `akm.llm` config. There is no per-pass
|
|
26
|
+
* provider override; that decision is locked by §9 of the v1 spec.
|
|
27
|
+
*/
|
|
28
|
+
export function resolveIndexPassLLM(passName, config) {
|
|
29
|
+
if (!config.llm)
|
|
30
|
+
return undefined;
|
|
31
|
+
const passConfig = config.index?.[passName];
|
|
32
|
+
if (passConfig?.llm === false)
|
|
33
|
+
return undefined;
|
|
34
|
+
return config.llm;
|
|
35
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LLM helper for the `akm index` memory-inference pass (#201).
|
|
3
|
+
*
|
|
4
|
+
* Splits a single memory body into a list of atomic facts. The pass itself
|
|
5
|
+
* (in `src/indexer/memory-inference.ts`) is responsible for deciding which
|
|
6
|
+
* memories are pending, persisting the resulting atomic memories with the
|
|
7
|
+
* correct frontmatter (`inferred: true`, `source: <parent-ref>`), and
|
|
8
|
+
* marking the parent as processed for idempotency.
|
|
9
|
+
*
|
|
10
|
+
* This module is intentionally tiny and stateless so tests can stub it via
|
|
11
|
+
* `mock.module("../src/llm/memory-infer", ...)` without hitting a network.
|
|
12
|
+
*
|
|
13
|
+
* Locked v1 contract (#208): the LLM connection always comes from the
|
|
14
|
+
* shared `akm.llm` block — never from a per-pass override. Callers obtain
|
|
15
|
+
* the connection via `resolveIndexPassLLM("memory", config)` and pass it
|
|
16
|
+
* straight through.
|
|
17
|
+
*/
|
|
18
|
+
import { toErrorMessage } from "../core/common";
|
|
19
|
+
import { warn } from "../core/warn";
|
|
20
|
+
import { chatCompletion, parseJsonResponse } from "./client";
|
|
21
|
+
/** Hard cap on body chars sent to the model — pragmatic and matches `runLlmEnrich`. */
|
|
22
|
+
const MAX_BODY_CHARS = 4000;
|
|
23
|
+
/** Hard cap on the number of atomic facts returned per memory. */
|
|
24
|
+
const MAX_FACTS_PER_MEMORY = 16;
|
|
25
|
+
/** Hard timeout for the LLM call. The index run must not hang on a misbehaving endpoint. */
|
|
26
|
+
const LLM_TIMEOUT_MS = 30_000;
|
|
27
|
+
const SYSTEM_PROMPT = "You split a developer memory into atomic, self-contained facts. " +
|
|
28
|
+
"Return only valid JSON. No prose, no markdown fences.";
|
|
29
|
+
const USER_PROMPT_PREFIX = `Split the memory below into a JSON array of short, self-contained atomic facts.
|
|
30
|
+
|
|
31
|
+
Rules:
|
|
32
|
+
- Output ONLY a JSON object: {"facts": ["fact one", "fact two", ...]}.
|
|
33
|
+
- Each fact is a single complete sentence, decontextualized so it stands alone.
|
|
34
|
+
- Drop pleasantries, meta-commentary, and timestamps.
|
|
35
|
+
- Preserve technical specifics (names, versions, identifiers) verbatim.
|
|
36
|
+
- If the memory is already a single atomic fact, return it as the only entry.
|
|
37
|
+
- Limit to at most ${MAX_FACTS_PER_MEMORY} facts.
|
|
38
|
+
|
|
39
|
+
Memory:
|
|
40
|
+
`;
|
|
41
|
+
/**
|
|
42
|
+
* Split a single memory body into atomic facts via the configured LLM.
|
|
43
|
+
*
|
|
44
|
+
* Returns `[]` on any failure (timeout, invalid JSON, empty response). Errors
|
|
45
|
+
* are logged via `warn()` but never thrown — a failed split for one memory
|
|
46
|
+
* must not abort the rest of the index pass.
|
|
47
|
+
*/
|
|
48
|
+
export async function splitMemoryIntoAtomicFacts(llmConfig, body) {
|
|
49
|
+
const trimmedBody = body.trim();
|
|
50
|
+
if (!trimmedBody)
|
|
51
|
+
return [];
|
|
52
|
+
const userPrompt = `${USER_PROMPT_PREFIX}${trimmedBody.slice(0, MAX_BODY_CHARS)}`;
|
|
53
|
+
let timeoutHandle;
|
|
54
|
+
try {
|
|
55
|
+
const raw = await Promise.race([
|
|
56
|
+
chatCompletion(llmConfig, [
|
|
57
|
+
{ role: "system", content: SYSTEM_PROMPT },
|
|
58
|
+
{ role: "user", content: userPrompt },
|
|
59
|
+
], { maxTokens: 768, temperature: 0.1 }),
|
|
60
|
+
new Promise((_, reject) => {
|
|
61
|
+
timeoutHandle = setTimeout(() => reject(new Error("memory inference timed out")), LLM_TIMEOUT_MS);
|
|
62
|
+
}),
|
|
63
|
+
]);
|
|
64
|
+
if (!raw)
|
|
65
|
+
return [];
|
|
66
|
+
const parsed = parseJsonResponse(raw);
|
|
67
|
+
if (!parsed || !Array.isArray(parsed.facts)) {
|
|
68
|
+
warn("memory inference: invalid JSON response from LLM; skipping memory.");
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
const facts = parsed.facts
|
|
72
|
+
.filter((f) => typeof f === "string")
|
|
73
|
+
.map((f) => f.trim())
|
|
74
|
+
.filter((f) => f.length > 0)
|
|
75
|
+
.slice(0, MAX_FACTS_PER_MEMORY);
|
|
76
|
+
return facts;
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
warn(`memory inference failed: ${toErrorMessage(err)}`);
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
82
|
+
finally {
|
|
83
|
+
if (timeoutHandle !== undefined)
|
|
84
|
+
clearTimeout(timeoutHandle);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -11,6 +11,19 @@ const EMBEDDED_HINTS = `# akm CLI
|
|
|
11
11
|
|
|
12
12
|
You have access to a searchable library of scripts, skills, commands, agents, knowledge documents, workflows, wikis, and memories via \`akm\`. Search your sources first before writing something from scratch.
|
|
13
13
|
|
|
14
|
+
## Agent Task Loop
|
|
15
|
+
|
|
16
|
+
For any task, follow this loop:
|
|
17
|
+
1. \`akm curate "<task>"\` — find the best matching asset
|
|
18
|
+
2. \`akm show <ref>\` — read the schema (field names and structure)
|
|
19
|
+
3. Edit the workspace file using schema field names + task-specific values from your README
|
|
20
|
+
4. \`akm feedback <ref> --positive\` — record success
|
|
21
|
+
|
|
22
|
+
For workflow tasks:
|
|
23
|
+
1. \`akm workflow next workflow:<name>\` — get current step instructions
|
|
24
|
+
2. Do the step work in your workspace
|
|
25
|
+
3. \`akm workflow complete <run-id> --step <step-id>\` — mark done, get next step
|
|
26
|
+
|
|
14
27
|
## Quick Reference
|
|
15
28
|
|
|
16
29
|
\`\`\`sh
|
|
@@ -144,7 +157,7 @@ akm wiki list # List wikis (name, pages, raws,
|
|
|
144
157
|
akm wiki create research # Scaffold a new wiki
|
|
145
158
|
akm wiki register ics-docs ~/code/ics-documentation # Register an external wiki
|
|
146
159
|
akm wiki show research # Path, description, counts, last 3 log entries
|
|
147
|
-
akm wiki pages research # Page refs + descriptions (excludes schema/index/log/
|
|
160
|
+
akm wiki pages research # Page refs + descriptions (excludes schema/index/log; includes raw/)
|
|
148
161
|
akm wiki search research "attention" # Scoped search (equivalent to --type wiki --wiki research)
|
|
149
162
|
akm wiki stash research ./paper.md # Copy source into raw/<slug>.md (never overwrites)
|
|
150
163
|
echo "..." | akm wiki stash research - # stdin form
|
|
@@ -254,7 +267,7 @@ akm registry add <url> --provider skills-sh # Specify provider type
|
|
|
254
267
|
akm registry remove <url-or-name> # Remove a registry
|
|
255
268
|
akm registry search "<query>" # Search all registries
|
|
256
269
|
akm registry search "<query>" --assets # Include asset-level results
|
|
257
|
-
akm registry build-index # Build
|
|
270
|
+
akm registry build-index # Build the default cache-backed index.json
|
|
258
271
|
akm registry build-index --out dist/index.json # Build to a custom path
|
|
259
272
|
\`\`\`
|
|
260
273
|
|
|
@@ -187,12 +187,14 @@ const skillMdRenderer = {
|
|
|
187
187
|
name: "skill-md",
|
|
188
188
|
buildShowResponse(ctx) {
|
|
189
189
|
const name = deriveName(ctx);
|
|
190
|
+
const parsed = parseFrontmatter(ctx.content());
|
|
190
191
|
return {
|
|
191
192
|
type: "skill",
|
|
192
193
|
name,
|
|
193
194
|
path: ctx.absPath,
|
|
194
195
|
action: "Read and follow the instructions below",
|
|
195
|
-
|
|
196
|
+
description: toStringOrUndefined(parsed.data.description),
|
|
197
|
+
content: parsed.content,
|
|
196
198
|
};
|
|
197
199
|
},
|
|
198
200
|
};
|
|
@@ -391,6 +393,64 @@ const wikiMdRenderer = {
|
|
|
391
393
|
}
|
|
392
394
|
},
|
|
393
395
|
};
|
|
396
|
+
// ── 4c. lesson-md ────────────────────────────────────────────────────────────
|
|
397
|
+
/**
|
|
398
|
+
* Renderer for the `lesson` asset type (v1 spec §13).
|
|
399
|
+
*
|
|
400
|
+
* Lessons are markdown files with required `description` and `when_to_use`
|
|
401
|
+
* frontmatter. The renderer projects both fields explicitly so consumers can
|
|
402
|
+
* decide whether to apply a lesson without reading the full body. Lint
|
|
403
|
+
* (see `src/core/lesson-lint.ts`) is the contract enforcer; the renderer is
|
|
404
|
+
* intentionally tolerant — a lesson missing required fields will still render
|
|
405
|
+
* its body so the user has something to work with while they fix the file.
|
|
406
|
+
*/
|
|
407
|
+
const lessonMdRenderer = {
|
|
408
|
+
name: "lesson-md",
|
|
409
|
+
buildShowResponse(ctx) {
|
|
410
|
+
const name = deriveName(ctx);
|
|
411
|
+
const parsed = parseFrontmatter(ctx.content());
|
|
412
|
+
const description = toStringOrUndefined(parsed.data.description);
|
|
413
|
+
const whenToUse = toStringOrUndefined(parsed.data.when_to_use);
|
|
414
|
+
const action = whenToUse
|
|
415
|
+
? `Apply this lesson when: ${whenToUse}`
|
|
416
|
+
: "Apply this lesson when its `when_to_use` trigger matches the current task.";
|
|
417
|
+
return {
|
|
418
|
+
type: "lesson",
|
|
419
|
+
name,
|
|
420
|
+
path: ctx.absPath,
|
|
421
|
+
action,
|
|
422
|
+
description,
|
|
423
|
+
content: parsed.content,
|
|
424
|
+
};
|
|
425
|
+
},
|
|
426
|
+
extractMetadata(entry, ctx) {
|
|
427
|
+
try {
|
|
428
|
+
const parsed = parseFrontmatter(ctx.content());
|
|
429
|
+
const fm = parsed.data;
|
|
430
|
+
const desc = toStringOrUndefined(fm.description);
|
|
431
|
+
if (desc && !entry.description) {
|
|
432
|
+
entry.description = desc;
|
|
433
|
+
entry.source = "frontmatter";
|
|
434
|
+
entry.confidence = 0.9;
|
|
435
|
+
}
|
|
436
|
+
const whenToUse = toStringOrUndefined(fm.when_to_use);
|
|
437
|
+
if (whenToUse) {
|
|
438
|
+
const hints = new Set(entry.searchHints ?? []);
|
|
439
|
+
hints.add(`when_to_use:${whenToUse}`);
|
|
440
|
+
entry.searchHints = Array.from(hints).filter(Boolean);
|
|
441
|
+
}
|
|
442
|
+
if (Array.isArray(fm.tags) && fm.tags.length > 0) {
|
|
443
|
+
const fmTags = fm.tags.filter((t) => typeof t === "string" && t.trim().length > 0);
|
|
444
|
+
if (fmTags.length > 0) {
|
|
445
|
+
entry.tags = Array.from(new Set([...(entry.tags ?? []), ...fmTags]));
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
catch {
|
|
450
|
+
// Non-fatal: skip metadata extraction on parse error
|
|
451
|
+
}
|
|
452
|
+
},
|
|
453
|
+
};
|
|
394
454
|
// ── 5. memory-md ─────────────────────────────────────────────────────────────
|
|
395
455
|
const memoryMdRenderer = {
|
|
396
456
|
name: "memory-md",
|
|
@@ -560,6 +620,7 @@ const builtinRenderers = [
|
|
|
560
620
|
agentMdRenderer,
|
|
561
621
|
knowledgeMdRenderer,
|
|
562
622
|
wikiMdRenderer,
|
|
623
|
+
lessonMdRenderer,
|
|
563
624
|
memoryMdRenderer,
|
|
564
625
|
workflowMdRenderer,
|
|
565
626
|
scriptSourceRenderer,
|
|
@@ -575,4 +636,4 @@ export function registerBuiltinRenderers() {
|
|
|
575
636
|
}
|
|
576
637
|
}
|
|
577
638
|
// ── Named exports for testing ────────────────────────────────────────────────
|
|
578
|
-
export { agentMdRenderer, commandMdRenderer, INTERPRETER_MAP, knowledgeMdRenderer, memoryMdRenderer, SETUP_SIGNALS, scriptSourceRenderer, skillMdRenderer, vaultEnvRenderer, wikiMdRenderer, workflowMdRenderer, };
|
|
639
|
+
export { agentMdRenderer, commandMdRenderer, INTERPRETER_MAP, knowledgeMdRenderer, lessonMdRenderer, memoryMdRenderer, SETUP_SIGNALS, scriptSourceRenderer, skillMdRenderer, vaultEnvRenderer, wikiMdRenderer, workflowMdRenderer, };
|