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.
- package/CHANGELOG.md +224 -1
- package/README.md +22 -6
- package/SECURITY.md +93 -0
- package/dist/cli/config-migrate.js +144 -0
- package/dist/cli/config-validate.js +39 -0
- package/dist/cli/confirm.js +73 -0
- package/dist/cli/parse-args.js +133 -0
- package/dist/cli/shared.js +129 -0
- package/dist/cli.js +2631 -1440
- package/dist/commands/add-cli.js +279 -0
- package/dist/commands/agent-dispatch.js +110 -0
- package/dist/commands/agent-support.js +68 -0
- package/dist/commands/completions.js +3 -0
- package/dist/commands/config-cli.js +130 -534
- package/dist/commands/consolidate.js +2122 -0
- package/dist/commands/curate.js +45 -3
- package/dist/commands/db-cli.js +23 -0
- package/dist/commands/distill-promotion-policy.js +660 -0
- package/dist/commands/distill.js +1081 -73
- package/dist/commands/env.js +213 -0
- package/dist/commands/eval-cases.js +43 -0
- package/dist/commands/events.js +15 -24
- package/dist/commands/extract-cli.js +127 -0
- package/dist/commands/extract-prompt.js +204 -0
- package/dist/commands/extract.js +477 -0
- package/dist/commands/feedback-cli.js +331 -0
- package/dist/commands/graph.js +477 -0
- package/dist/commands/health.js +1302 -0
- package/dist/commands/help/help-accept.md +12 -0
- package/dist/commands/help/help-improve.md +69 -0
- package/dist/commands/help/help-proposals.md +18 -0
- package/dist/commands/help/help-propose.md +17 -0
- package/dist/commands/help/help-reject.md +11 -0
- package/dist/commands/history.js +54 -46
- package/dist/commands/improve-auto-accept.js +97 -0
- package/dist/commands/improve-cli.js +217 -0
- package/dist/commands/improve-profiles.js +166 -0
- package/dist/commands/improve-result-file.js +167 -0
- package/dist/commands/improve.js +2373 -0
- package/dist/commands/info.js +5 -2
- package/dist/commands/init.js +50 -2
- package/dist/commands/installed-stashes.js +102 -139
- package/dist/commands/knowledge.js +136 -0
- package/dist/commands/lint/agent-linter.js +49 -0
- package/dist/commands/lint/base-linter.js +479 -0
- package/dist/commands/lint/command-linter.js +49 -0
- package/dist/commands/lint/default-linter.js +16 -0
- package/dist/commands/lint/env-key-rules.js +154 -0
- package/dist/commands/lint/index.js +196 -0
- package/dist/commands/lint/knowledge-linter.js +16 -0
- package/dist/commands/lint/markdown-insertion.js +343 -0
- package/dist/commands/lint/memory-linter.js +61 -0
- package/dist/commands/lint/registry.js +36 -0
- package/dist/commands/lint/skill-linter.js +45 -0
- package/dist/commands/lint/task-linter.js +50 -0
- package/dist/commands/lint/types.js +4 -0
- package/dist/commands/lint/workflow-linter.js +56 -0
- package/dist/commands/lint.js +4 -0
- package/dist/commands/migration-help.js +3 -0
- package/dist/commands/proposal.js +67 -12
- package/dist/commands/propose.js +120 -45
- package/dist/commands/reflect.js +1104 -60
- package/dist/commands/registry-cli.js +150 -0
- package/dist/commands/registry-search.js +5 -2
- package/dist/commands/remember-cli.js +257 -0
- package/dist/commands/remember.js +70 -7
- package/dist/commands/schema-repair.js +203 -0
- package/dist/commands/search.js +115 -14
- package/dist/commands/secret.js +173 -0
- package/dist/commands/self-update.js +3 -0
- package/dist/commands/show.js +158 -60
- package/dist/commands/source-add.js +17 -45
- package/dist/commands/source-clone.js +3 -0
- package/dist/commands/source-manage.js +14 -19
- package/dist/commands/tasks.js +437 -0
- package/dist/commands/url-checker.js +42 -0
- package/dist/core/action-contributors.js +28 -0
- package/dist/core/asset-ref.js +17 -2
- package/dist/core/asset-registry.js +12 -17
- package/dist/core/asset-serialize.js +88 -0
- package/dist/core/asset-spec.js +67 -1
- package/dist/core/common.js +182 -0
- package/dist/core/concurrent.js +25 -0
- package/dist/core/config-io.js +347 -0
- package/dist/core/config-migration.js +622 -0
- package/dist/core/config-schema.js +534 -0
- package/dist/core/config-sources.js +108 -0
- package/dist/core/config-types.js +4 -0
- package/dist/core/config-walker.js +337 -0
- package/dist/core/config.js +364 -968
- package/dist/core/errors.js +42 -20
- package/dist/core/events.js +105 -135
- package/dist/core/file-lock.js +104 -0
- package/dist/core/frontmatter.js +75 -8
- package/dist/core/lesson-lint.js +3 -0
- package/dist/core/markdown.js +20 -0
- package/dist/core/memory-belief.js +62 -0
- package/dist/core/memory-contradiction-detect.js +274 -0
- package/dist/core/memory-improve.js +806 -0
- package/dist/core/parse.js +158 -0
- package/dist/core/paths.js +280 -14
- package/dist/core/proposal-quality-validators.js +380 -0
- package/dist/core/proposal-validators.js +69 -0
- package/dist/core/proposals.js +512 -42
- package/dist/core/state-db.js +1068 -0
- package/dist/core/text-truncation.js +107 -0
- package/dist/core/time.js +54 -0
- package/dist/core/tty.js +59 -0
- package/dist/core/warn.js +64 -1
- package/dist/core/write-source.js +3 -0
- package/dist/indexer/db-backup.js +391 -0
- package/dist/indexer/db-search.js +198 -489
- package/dist/indexer/db.js +990 -108
- package/dist/indexer/ensure-index.js +136 -0
- package/dist/indexer/file-context.js +3 -0
- package/dist/indexer/graph-boost.js +376 -101
- package/dist/indexer/graph-db.js +391 -0
- package/dist/indexer/graph-dedup.js +95 -0
- package/dist/indexer/graph-extraction.js +550 -114
- package/dist/indexer/index-context.js +4 -0
- package/dist/indexer/indexer.js +547 -309
- package/dist/indexer/llm-cache.js +52 -0
- package/dist/indexer/manifest.js +3 -0
- package/dist/indexer/matchers.js +167 -160
- package/dist/indexer/memory-inference.js +152 -74
- package/dist/indexer/metadata-contributors.js +29 -0
- package/dist/indexer/metadata.js +275 -196
- package/dist/indexer/path-resolver.js +92 -0
- package/dist/indexer/project-context.js +192 -0
- package/dist/indexer/ranking-contributors.js +331 -0
- package/dist/indexer/ranking.js +81 -0
- package/dist/indexer/search-fields.js +5 -9
- package/dist/indexer/search-hit-enrichers.js +111 -0
- package/dist/indexer/search-source.js +44 -10
- package/dist/indexer/semantic-status.js +6 -17
- package/dist/indexer/staleness-detect.js +447 -0
- package/dist/indexer/usage-events.js +12 -9
- package/dist/indexer/walker.js +28 -0
- package/dist/integrations/agent/builders.js +135 -0
- package/dist/integrations/agent/config.js +122 -230
- package/dist/integrations/agent/detect.js +3 -0
- package/dist/integrations/agent/index.js +7 -13
- package/dist/integrations/agent/model-aliases.js +55 -0
- package/dist/integrations/agent/profiles.js +70 -5
- package/dist/integrations/agent/prompts.js +250 -36
- package/dist/integrations/agent/runner.js +151 -0
- package/dist/integrations/agent/sdk-runner.js +126 -0
- package/dist/integrations/agent/spawn.js +183 -35
- package/dist/integrations/github.js +3 -0
- package/dist/integrations/lockfile.js +32 -69
- package/dist/integrations/session-logs/index.js +69 -0
- package/dist/integrations/session-logs/inline-refs.js +35 -0
- package/dist/integrations/session-logs/pre-filter.js +152 -0
- package/dist/integrations/session-logs/providers/claude-code.js +282 -0
- package/dist/integrations/session-logs/providers/opencode.js +258 -0
- package/dist/integrations/session-logs/types.js +4 -0
- package/dist/llm/call-ai.js +62 -0
- package/dist/llm/client.js +79 -88
- package/dist/llm/embedder.js +20 -29
- package/dist/llm/embedders/cache.js +3 -7
- package/dist/llm/embedders/local.js +42 -1
- package/dist/llm/embedders/remote.js +20 -8
- package/dist/llm/embedders/types.js +3 -7
- package/dist/llm/feature-gate.js +95 -48
- package/dist/llm/graph-extract.js +676 -72
- package/dist/llm/index-passes.js +44 -29
- package/dist/llm/memory-infer.js +80 -71
- package/dist/llm/metadata-enhance.js +42 -29
- package/dist/llm/prompts/extract-session.md +80 -0
- package/dist/llm/prompts/graph-extract-user-prompt.md +35 -0
- package/dist/output/cli-hints-full.md +292 -0
- package/dist/output/cli-hints-short.md +66 -0
- package/dist/output/cli-hints.js +7 -311
- package/dist/output/context.js +60 -8
- package/dist/output/renderers.js +306 -258
- package/dist/output/shapes/curate.js +56 -0
- package/dist/output/shapes/distill.js +10 -0
- package/dist/output/shapes/env-list.js +19 -0
- package/dist/output/shapes/events.js +11 -0
- package/dist/output/shapes/helpers.js +424 -0
- package/dist/output/shapes/history.js +7 -0
- package/dist/output/shapes/passthrough.js +102 -0
- package/dist/output/shapes/proposal-accept.js +7 -0
- package/dist/output/shapes/proposal-diff.js +7 -0
- package/dist/output/shapes/proposal-list.js +7 -0
- package/dist/output/shapes/proposal-producer.js +11 -0
- package/dist/output/shapes/proposal-reject.js +7 -0
- package/dist/output/shapes/proposal-show.js +7 -0
- package/dist/output/shapes/registry-search.js +6 -0
- package/dist/output/shapes/registry.js +30 -0
- package/dist/output/shapes/search.js +6 -0
- package/dist/output/shapes/secret-list.js +19 -0
- package/dist/output/shapes/show.js +6 -0
- package/dist/output/shapes/vault-list.js +19 -0
- package/dist/output/shapes.js +51 -511
- package/dist/output/text/add.js +6 -0
- package/dist/output/text/clone.js +6 -0
- package/dist/output/text/config.js +6 -0
- package/dist/output/text/curate.js +6 -0
- package/dist/output/text/distill.js +7 -0
- package/dist/output/text/enable-disable.js +7 -0
- package/dist/output/text/events.js +10 -0
- package/dist/output/text/feedback.js +6 -0
- package/dist/output/text/helpers.js +1039 -0
- package/dist/output/text/history.js +7 -0
- package/dist/output/text/import.js +6 -0
- package/dist/output/text/index.js +6 -0
- package/dist/output/text/info.js +6 -0
- package/dist/output/text/init.js +6 -0
- package/dist/output/text/list.js +6 -0
- package/dist/output/text/proposal-producer.js +8 -0
- package/dist/output/text/proposal.js +11 -0
- package/dist/output/text/registry-commands.js +11 -0
- package/dist/output/text/registry.js +30 -0
- package/dist/output/text/remember.js +6 -0
- package/dist/output/text/remove.js +6 -0
- package/dist/output/text/save.js +6 -0
- package/dist/output/text/search.js +6 -0
- package/dist/output/text/show.js +6 -0
- package/dist/output/text/update.js +6 -0
- package/dist/output/text/upgrade.js +6 -0
- package/dist/output/text/vault.js +16 -0
- package/dist/output/text/wiki.js +15 -0
- package/dist/output/text/workflow.js +14 -0
- package/dist/output/text.js +44 -1093
- package/dist/registry/build-index.js +3 -0
- package/dist/registry/create-provider-registry.js +3 -0
- package/dist/registry/factory.js +4 -1
- package/dist/registry/origin-resolve.js +3 -0
- package/dist/registry/providers/index.js +3 -0
- package/dist/registry/providers/skills-sh.js +71 -50
- package/dist/registry/providers/static-index.js +53 -48
- package/dist/registry/providers/types.js +3 -24
- package/dist/registry/resolve.js +11 -16
- package/dist/registry/types.js +3 -0
- package/dist/scripts/migrate-storage.js +17750 -0
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +9031 -0
- package/dist/scripts/migrations/v16-to-v17.js +141 -0
- package/dist/setup/detect.js +3 -0
- package/dist/setup/ripgrep-install.js +3 -0
- package/dist/setup/ripgrep-resolve.js +3 -0
- package/dist/setup/setup.js +775 -37
- package/dist/setup/steps.js +3 -15
- package/dist/sources/include.js +3 -0
- package/dist/sources/provider-factory.js +5 -12
- package/dist/sources/provider.js +3 -20
- package/dist/sources/providers/filesystem.js +19 -23
- package/dist/sources/providers/git.js +179 -20
- package/dist/sources/providers/index.js +3 -0
- package/dist/sources/providers/install-types.js +3 -13
- package/dist/sources/providers/npm.js +3 -4
- package/dist/sources/providers/provider-utils.js +3 -0
- package/dist/sources/providers/sync-from-ref.js +3 -11
- package/dist/sources/providers/tar-utils.js +3 -0
- package/dist/sources/providers/website.js +18 -22
- package/dist/sources/resolve.js +3 -0
- package/dist/sources/types.js +3 -0
- package/dist/sources/website-ingest.js +7 -0
- package/dist/tasks/backends/cron.js +203 -0
- package/dist/tasks/backends/exec-utils.js +28 -0
- package/dist/tasks/backends/index.js +24 -0
- package/dist/tasks/backends/launchd-template.xml +19 -0
- package/dist/tasks/backends/launchd.js +187 -0
- package/dist/tasks/backends/schtasks-template.xml +29 -0
- package/dist/tasks/backends/schtasks.js +215 -0
- package/dist/tasks/parser.js +211 -0
- package/dist/tasks/resolveAkmBin.js +87 -0
- package/dist/tasks/runner.js +458 -0
- package/dist/tasks/schedule.js +227 -0
- package/dist/tasks/schema.js +15 -0
- package/dist/tasks/validator.js +62 -0
- package/dist/version.js +3 -0
- package/dist/wiki/index-template.md +12 -0
- package/dist/wiki/ingest-workflow-template.md +54 -0
- package/dist/wiki/log-template.md +8 -0
- package/dist/wiki/schema-template.md +61 -0
- package/dist/wiki/wiki-templates.js +15 -0
- package/dist/wiki/wiki.js +13 -61
- package/dist/workflows/authoring.js +8 -25
- package/dist/workflows/cli.js +3 -0
- package/dist/workflows/db.js +141 -2
- package/dist/workflows/document-cache.js +3 -10
- package/dist/workflows/parser.js +3 -0
- package/dist/workflows/renderer.js +11 -3
- package/dist/workflows/runs.js +91 -89
- package/dist/workflows/schema.js +3 -0
- package/dist/workflows/scope-key.js +79 -0
- package/dist/workflows/validator.js +4 -8
- package/dist/workflows/workflow-template.md +24 -0
- package/docs/README.md +10 -2
- package/docs/data-and-telemetry.md +225 -0
- package/docs/migration/release-notes/0.7.0.md +1 -1
- package/docs/migration/release-notes/0.7.4.md +1 -1
- package/docs/migration/release-notes/0.7.5.md +20 -0
- package/docs/migration/release-notes/0.8.0.md +48 -0
- package/docs/migration/v0.7-to-v0.8.md +1307 -0
- package/package.json +29 -11
- package/dist/commands/install-audit.js +0 -381
- package/dist/commands/vault.js +0 -333
- package/dist/templates/wiki-templates.js +0 -100
|
@@ -1,43 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* Detects memories pending inference, asks the configured LLM to compress each
|
|
5
|
-
* into one higher-signal derived memory, and writes the result back as a new
|
|
6
|
-
* memory file with frontmatter `inferred: true` + a `source:` backref to the
|
|
7
|
-
* parent memory.
|
|
8
|
-
*
|
|
9
|
-
* Pending predicate (see {@link isPendingMemory}):
|
|
10
|
-
* - File lives under `<stashRoot>/memories/` and ends in `.md`.
|
|
11
|
-
* - Frontmatter does NOT have `inferenceProcessed: true` (parent already split).
|
|
12
|
-
* - Frontmatter does NOT have `inferred: true` (this is itself a child fact).
|
|
13
|
-
*
|
|
14
|
-
* Idempotency: after a successful split the parent's frontmatter is rewritten
|
|
15
|
-
* with `inferenceProcessed: true`. A subsequent `akm index` therefore skips
|
|
16
|
-
* the parent without re-running the LLM.
|
|
17
|
-
*
|
|
18
|
-
* Disabling — two orthogonal gates per v1 spec §14:
|
|
19
|
-
* 1. `llm.features.memory_inference = false` blocks the pass at the
|
|
20
|
-
* locked feature-flag layer (no network call may ever issue).
|
|
21
|
-
* 2. `index.memory.llm = false` (or no `akm.llm` block at all) opts the
|
|
22
|
-
* pass out at the per-pass layer (#208).
|
|
23
|
-
* A pass runs iff both layers allow it. Existing inferred children are
|
|
24
|
-
* NEVER deleted — the user keeps what was already produced.
|
|
25
|
-
*
|
|
26
|
-
* Locked v1 contract:
|
|
27
|
-
* - LLM access is exclusively via `resolveIndexPassLLM("memory", config)`.
|
|
28
|
-
* - All child memory writes go through `writeAssetToSource` in
|
|
29
|
-
* `src/core/write-source.ts`. The parent's frontmatter rewrite is an
|
|
30
|
-
* explicit narrow exception — see {@link markParentProcessed}.
|
|
31
|
-
*/
|
|
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/.
|
|
32
4
|
import fs from "node:fs";
|
|
33
5
|
import path from "node:path";
|
|
34
|
-
import { stringify as yamlStringify } from "yaml";
|
|
35
6
|
import { parseAssetRef } from "../core/asset-ref";
|
|
7
|
+
import { assembleAsset } from "../core/asset-serialize";
|
|
8
|
+
import { concurrentMap } from "../core/concurrent";
|
|
36
9
|
import { parseFrontmatter, parseFrontmatterBlock } from "../core/frontmatter";
|
|
37
10
|
import { warn } from "../core/warn";
|
|
38
11
|
import { writeAssetToSource } from "../core/write-source";
|
|
12
|
+
import { isProcessEnabled } from "../llm/feature-gate";
|
|
39
13
|
import { resolveIndexPassLLM } from "../llm/index-passes";
|
|
40
|
-
import
|
|
14
|
+
import * as memoryInfer from "../llm/memory-infer";
|
|
15
|
+
import { withLlmCache } from "./llm-cache";
|
|
16
|
+
import { walkMarkdownFiles } from "./walker";
|
|
41
17
|
/**
|
|
42
18
|
* Frontmatter keys this pass cares about. Constants so a future rename only
|
|
43
19
|
* needs to touch one site.
|
|
@@ -45,14 +21,15 @@ import { compressMemoryToDerivedMemory } from "../llm/memory-infer";
|
|
|
45
21
|
const FM_INFERRED = "inferred";
|
|
46
22
|
const FM_INFERENCE_PROCESSED = "inferenceProcessed";
|
|
47
23
|
const FM_SOURCE = "source";
|
|
24
|
+
const FM_CAPTURE_MODE = "captureMode";
|
|
48
25
|
/**
|
|
49
26
|
* Top-level entry point. Returns a no-op result when the pass is disabled.
|
|
50
27
|
*
|
|
51
|
-
* Two orthogonal gates
|
|
28
|
+
* Two orthogonal gates:
|
|
52
29
|
*
|
|
53
|
-
* 1. **Feature gate** — `
|
|
54
|
-
* `true`). When `false`, no network call may issue regardless
|
|
55
|
-
* per-pass settings.
|
|
30
|
+
* 1. **Feature gate** — `profiles.improve.default.processes.memoryInference.enabled`
|
|
31
|
+
* (defaults to `true`). When `false`, no network call may issue regardless
|
|
32
|
+
* of per-pass settings.
|
|
56
33
|
* 2. **Per-pass gate** — `resolveIndexPassLLM("memory", config)` (which
|
|
57
34
|
* reads `index.memory.llm`). When `false`, the indexer simply skips
|
|
58
35
|
* this pass for the current run.
|
|
@@ -60,16 +37,21 @@ const FM_SOURCE = "source";
|
|
|
60
37
|
* Both must allow the call for the pass to run. Either set to `false`
|
|
61
38
|
* short-circuits to a no-op result.
|
|
62
39
|
*/
|
|
63
|
-
export async function runMemoryInferencePass(config, sources, signal) {
|
|
40
|
+
export async function runMemoryInferencePass(config, sources, signal, db, reEnrich, onProgress, options = {}) {
|
|
64
41
|
const result = {
|
|
65
42
|
considered: 0,
|
|
43
|
+
cacheHits: 0,
|
|
66
44
|
splitParents: 0,
|
|
67
45
|
writtenFacts: 0,
|
|
68
46
|
skippedNoFacts: 0,
|
|
47
|
+
skippedChildExists: 0,
|
|
48
|
+
skippedAborted: 0,
|
|
49
|
+
unaccounted: 0,
|
|
69
50
|
};
|
|
70
|
-
// Gate 1 —
|
|
71
|
-
//
|
|
72
|
-
|
|
51
|
+
// Gate 1 — feature gate via isProcessEnabled, which reads the 0.8.0 path
|
|
52
|
+
// (profiles.improve.default.processes.memoryInference.enabled). Defaults to
|
|
53
|
+
// enabled when the key is absent.
|
|
54
|
+
if (!isProcessEnabled("index", "memory_inference", config))
|
|
73
55
|
return result;
|
|
74
56
|
// Gate 2 — per-pass opt-out (#208). Returns the resolved llm config or
|
|
75
57
|
// `undefined` when the pass should not run.
|
|
@@ -82,26 +64,124 @@ export async function runMemoryInferencePass(config, sources, signal) {
|
|
|
82
64
|
const primary = sources[0];
|
|
83
65
|
if (!primary)
|
|
84
66
|
return result;
|
|
85
|
-
const pending = collectPendingMemories(primary.path);
|
|
67
|
+
const pending = collectPendingMemories(primary.path).filter((record) => !options.candidateRefs || options.candidateRefs.has(record.ref));
|
|
86
68
|
result.considered = pending.length;
|
|
87
69
|
if (pending.length === 0)
|
|
88
70
|
return result;
|
|
89
|
-
|
|
71
|
+
let processed = 0;
|
|
72
|
+
const total = pending.length;
|
|
73
|
+
onProgress?.({ processed, total, writtenFacts: 0, skippedNoFacts: 0 });
|
|
74
|
+
const perRecordResults = await concurrentMap(pending, async (record) => {
|
|
75
|
+
// Aborted BEFORE a fresh LLM call. Returned as a typed outcome so the
|
|
76
|
+
// for-loop below increments `skippedAborted` instead of silently
|
|
77
|
+
// dropping the record (which historically inflated freshAttempts and
|
|
78
|
+
// dragged the health-reported yield rate down — see investigation
|
|
79
|
+
// 2026-05-26).
|
|
90
80
|
if (signal?.aborted)
|
|
91
|
-
return
|
|
92
|
-
|
|
81
|
+
return { aborted: true };
|
|
82
|
+
// Incremental cache: skip LLM call when body hash is unchanged and
|
|
83
|
+
// --re-enrich was not requested. The cache ref is the absolute file path.
|
|
84
|
+
const validate = (raw) => {
|
|
85
|
+
if (!raw || typeof raw !== "object")
|
|
86
|
+
return undefined;
|
|
87
|
+
const parsed = raw;
|
|
88
|
+
const title = typeof parsed.title === "string" ? parsed.title : "";
|
|
89
|
+
const description = typeof parsed.description === "string" ? parsed.description : "";
|
|
90
|
+
const content = typeof parsed.content === "string" ? parsed.content : "";
|
|
91
|
+
const tags = Array.isArray(parsed.tags) ? parsed.tags.filter((t) => typeof t === "string") : [];
|
|
92
|
+
const searchHints = Array.isArray(parsed.searchHints)
|
|
93
|
+
? parsed.searchHints.filter((h) => typeof h === "string")
|
|
94
|
+
: [];
|
|
95
|
+
if (title && description && content && tags.length > 0 && searchHints.length > 0) {
|
|
96
|
+
return { title, description, tags, searchHints, content };
|
|
97
|
+
}
|
|
98
|
+
return undefined;
|
|
99
|
+
};
|
|
100
|
+
// Track whether THIS candidate's result came from the body-hash
|
|
101
|
+
// cache vs. a fresh LLM call. The cache short-circuits when the
|
|
102
|
+
// parent body has not changed since a prior derived write — surfacing
|
|
103
|
+
// the hit count separately so the operational yield rate
|
|
104
|
+
// (writtenFacts / freshAttempts) is interpretable as the cache warms.
|
|
105
|
+
let fromCache = false;
|
|
106
|
+
const derived = db
|
|
107
|
+
? await withLlmCache(db, record.filePath, record.body, reEnrich ?? false, () => memoryInfer.compressMemoryToDerivedMemory(llmConfig, record.body, signal, config, (evt) => {
|
|
108
|
+
warn(`[akm] LLM fallback for ${evt.feature}: ${evt.reason}`);
|
|
109
|
+
}), validate, undefined, "", {
|
|
110
|
+
onCacheHit: () => {
|
|
111
|
+
fromCache = true;
|
|
112
|
+
},
|
|
113
|
+
})
|
|
114
|
+
: await memoryInfer.compressMemoryToDerivedMemory(llmConfig, record.body, signal, config, (evt) => {
|
|
115
|
+
warn(`[akm] LLM fallback for ${evt.feature}: ${evt.reason}`);
|
|
116
|
+
});
|
|
93
117
|
if (!derived) {
|
|
94
|
-
|
|
95
|
-
// Intentionally NOT marked processed — a transient LLM failure should
|
|
96
|
-
// be retried on the next index run.
|
|
97
|
-
continue;
|
|
118
|
+
return { skipped: true, fromCache };
|
|
98
119
|
}
|
|
99
120
|
const written = await writeDerivedMemory(record, derived);
|
|
100
121
|
if (written > 0) {
|
|
101
122
|
markParentProcessed(record);
|
|
123
|
+
return { skipped: false, splitParent: true, written, fromCache };
|
|
124
|
+
}
|
|
125
|
+
// LLM produced a valid derived draft but no file was written — either
|
|
126
|
+
// because `<parent>.derived.md` already exists on disk or
|
|
127
|
+
// `writeAssetToSource` threw. Categorise as `childExists` so the
|
|
128
|
+
// attempt is accounted for in health metrics rather than vanishing
|
|
129
|
+
// into the freshAttempts denominator.
|
|
130
|
+
return { skipped: false, splitParent: false, written: 0, fromCache, childExists: true };
|
|
131
|
+
},
|
|
132
|
+
// Default concurrency of 4 for cloud APIs. Set `llm.concurrency: 1`
|
|
133
|
+
// in config.json for local model servers (LM Studio, Ollama).
|
|
134
|
+
llmConfig.concurrency ?? 1);
|
|
135
|
+
for (let i = 0; i < perRecordResults.length; i++) {
|
|
136
|
+
const res = perRecordResults[i];
|
|
137
|
+
if (!res)
|
|
138
|
+
continue;
|
|
139
|
+
if ("aborted" in res && res.aborted) {
|
|
140
|
+
result.skippedAborted += 1;
|
|
141
|
+
processed++;
|
|
142
|
+
onProgress?.({
|
|
143
|
+
processed,
|
|
144
|
+
total,
|
|
145
|
+
writtenFacts: result.writtenFacts,
|
|
146
|
+
skippedNoFacts: result.skippedNoFacts,
|
|
147
|
+
currentRef: pending[i]?.ref,
|
|
148
|
+
});
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (res.fromCache) {
|
|
152
|
+
result.cacheHits += 1;
|
|
153
|
+
}
|
|
154
|
+
if (res.skipped) {
|
|
155
|
+
result.skippedNoFacts += 1;
|
|
156
|
+
// Intentionally NOT marked processed — a transient LLM failure should
|
|
157
|
+
// be retried on the next index run.
|
|
158
|
+
}
|
|
159
|
+
else if (res.splitParent) {
|
|
102
160
|
result.splitParents += 1;
|
|
103
|
-
result.writtenFacts += written;
|
|
161
|
+
result.writtenFacts += res.written;
|
|
162
|
+
}
|
|
163
|
+
else if ("childExists" in res && res.childExists) {
|
|
164
|
+
// LLM call was consumed but the derived file already existed (or the
|
|
165
|
+
// write threw). Track separately so this category is observable in
|
|
166
|
+
// health output and stops bleeding into the freshAttempts denominator.
|
|
167
|
+
result.skippedChildExists += 1;
|
|
168
|
+
warn(`memory inference: derived child for ${pending[i]?.ref ?? "<unknown>"} already existed or write failed; counted as skippedChildExists`);
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
// The per-record state machine should cover every outcome. A hit here
|
|
172
|
+
// means a new code path slipped past the categorisation — surface it
|
|
173
|
+
// loudly so health metrics stay honest and we get a signal to fix.
|
|
174
|
+
result.unaccounted += 1;
|
|
175
|
+
warn(`memory inference: unaccounted per-record outcome for ${pending[i]?.ref ?? "<unknown>"}`);
|
|
104
176
|
}
|
|
177
|
+
processed++;
|
|
178
|
+
onProgress?.({
|
|
179
|
+
processed,
|
|
180
|
+
total,
|
|
181
|
+
writtenFacts: result.writtenFacts,
|
|
182
|
+
skippedNoFacts: result.skippedNoFacts,
|
|
183
|
+
currentRef: pending[i]?.ref,
|
|
184
|
+
});
|
|
105
185
|
}
|
|
106
186
|
return result;
|
|
107
187
|
}
|
|
@@ -125,7 +205,7 @@ export function collectPendingMemories(stashRoot) {
|
|
|
125
205
|
continue;
|
|
126
206
|
}
|
|
127
207
|
const parsed = parseFrontmatter(raw);
|
|
128
|
-
if (!isPendingMemory(parsed.data))
|
|
208
|
+
if (!isPendingMemory(parsed.data, filePath))
|
|
129
209
|
continue;
|
|
130
210
|
const relName = toMemoryName(memoriesDir, filePath);
|
|
131
211
|
if (!relName)
|
|
@@ -145,34 +225,33 @@ export function collectPendingMemories(stashRoot) {
|
|
|
145
225
|
* Predicate: true when the parsed frontmatter indicates the memory has not
|
|
146
226
|
* yet been split AND is not itself an inferred child.
|
|
147
227
|
*
|
|
228
|
+
* Also guards against `.derived` files whose `inferred:` frontmatter key has
|
|
229
|
+
* been dropped by a manual edit or schema-repair rewrite. The file name suffix
|
|
230
|
+
* is structural and immutable; frontmatter flags are mutable. A file whose
|
|
231
|
+
* path contains `.derived` is always treated as a derived child regardless of
|
|
232
|
+
* its frontmatter state — this prevents `<name>.derived.derived.md` chains.
|
|
233
|
+
*
|
|
234
|
+
* @param frontmatter - Parsed YAML frontmatter from the memory file.
|
|
235
|
+
* @param filePath - Optional absolute path to the memory file. When
|
|
236
|
+
* supplied, the name-based guard is applied.
|
|
237
|
+
*
|
|
148
238
|
* Exported for direct unit testing — keeping the predicate in one place
|
|
149
239
|
* avoids drift between the walker, tests, and any future consumers.
|
|
150
240
|
*/
|
|
151
|
-
export function isPendingMemory(frontmatter) {
|
|
241
|
+
export function isPendingMemory(frontmatter, filePath) {
|
|
242
|
+
// Name-based guard: a `.derived` suffix in the path means this file is a
|
|
243
|
+
// derived child regardless of what its frontmatter currently says.
|
|
244
|
+
if (filePath !== undefined) {
|
|
245
|
+
const base = path.basename(filePath, ".md");
|
|
246
|
+
if (base.endsWith(".derived"))
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
152
249
|
if (frontmatter[FM_INFERRED] === true)
|
|
153
250
|
return false;
|
|
154
251
|
if (frontmatter[FM_INFERENCE_PROCESSED] === true)
|
|
155
252
|
return false;
|
|
156
253
|
return true;
|
|
157
254
|
}
|
|
158
|
-
function* walkMarkdownFiles(root) {
|
|
159
|
-
let entries;
|
|
160
|
-
try {
|
|
161
|
-
entries = fs.readdirSync(root, { withFileTypes: true });
|
|
162
|
-
}
|
|
163
|
-
catch {
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
for (const entry of entries) {
|
|
167
|
-
const full = path.join(root, entry.name);
|
|
168
|
-
if (entry.isDirectory()) {
|
|
169
|
-
yield* walkMarkdownFiles(full);
|
|
170
|
-
}
|
|
171
|
-
else if (entry.isFile() && entry.name.toLowerCase().endsWith(".md")) {
|
|
172
|
-
yield full;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
255
|
function toMemoryName(memoriesDir, filePath) {
|
|
177
256
|
const rel = path.relative(memoriesDir, filePath);
|
|
178
257
|
if (!rel || rel.startsWith(".."))
|
|
@@ -214,6 +293,7 @@ async function writeDerivedMemory(parent, derived) {
|
|
|
214
293
|
function renderDerivedMemory(parent, derived) {
|
|
215
294
|
const fm = {
|
|
216
295
|
[FM_INFERRED]: true,
|
|
296
|
+
[FM_CAPTURE_MODE]: "background",
|
|
217
297
|
[FM_SOURCE]: parent.ref,
|
|
218
298
|
description: derived.description,
|
|
219
299
|
tags: derived.tags,
|
|
@@ -221,8 +301,7 @@ function renderDerivedMemory(parent, derived) {
|
|
|
221
301
|
title: derived.title,
|
|
222
302
|
derivedFrom: parent.name,
|
|
223
303
|
};
|
|
224
|
-
|
|
225
|
-
return `---\n${yaml}\n---\n\n# ${derived.title.trim()}\n\n${derived.content.trim()}\n`;
|
|
304
|
+
return assembleAsset(fm, `# ${derived.title.trim()}\n\n${derived.content.trim()}\n`);
|
|
226
305
|
}
|
|
227
306
|
function markParentProcessed(parent) {
|
|
228
307
|
// Frontmatter-only rewrite of an existing asset: not a new asset write,
|
|
@@ -239,10 +318,9 @@ function markParentProcessed(parent) {
|
|
|
239
318
|
return;
|
|
240
319
|
}
|
|
241
320
|
const updatedFm = { ...parent.data, [FM_INFERENCE_PROCESSED]: true };
|
|
242
|
-
const yaml = yamlStringify(updatedFm).trimEnd();
|
|
243
321
|
const block = parseFrontmatterBlock(raw);
|
|
244
322
|
const body = block?.content ?? raw;
|
|
245
|
-
const next =
|
|
323
|
+
const next = assembleAsset(updatedFm, body);
|
|
246
324
|
try {
|
|
247
325
|
fs.writeFileSync(parent.filePath, next, "utf8");
|
|
248
326
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
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
|
+
const contributors = [];
|
|
5
|
+
let builtinsPromise;
|
|
6
|
+
async function ensureBuiltinMetadataContributorsRegistered() {
|
|
7
|
+
if (!builtinsPromise) {
|
|
8
|
+
builtinsPromise = (async () => {
|
|
9
|
+
await import("../output/renderers.js");
|
|
10
|
+
await import("../workflows/renderer.js");
|
|
11
|
+
})();
|
|
12
|
+
}
|
|
13
|
+
return builtinsPromise;
|
|
14
|
+
}
|
|
15
|
+
export function registerMetadataContributor(contributor) {
|
|
16
|
+
contributors.push(contributor);
|
|
17
|
+
}
|
|
18
|
+
export async function getMetadataContributors() {
|
|
19
|
+
await ensureBuiltinMetadataContributorsRegistered();
|
|
20
|
+
return [...contributors];
|
|
21
|
+
}
|
|
22
|
+
export async function applyMetadataContributors(entry, ctx) {
|
|
23
|
+
const activeContributors = await getMetadataContributors();
|
|
24
|
+
for (const contributor of activeContributors) {
|
|
25
|
+
if (!contributor.appliesTo(ctx))
|
|
26
|
+
continue;
|
|
27
|
+
contributor.contribute(entry, ctx);
|
|
28
|
+
}
|
|
29
|
+
}
|