akm-cli 0.7.5 → 0.8.0-rc.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{.github/CHANGELOG.md → CHANGELOG.md} +113 -2
- package/README.md +20 -4
- 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.js +1995 -551
- 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 +1531 -0
- package/dist/commands/curate.js +44 -3
- package/dist/commands/db-cli.js +23 -0
- package/dist/commands/distill-promotion-policy.js +660 -0
- package/dist/commands/distill.js +990 -75
- package/dist/commands/eval-cases.js +43 -0
- package/dist/commands/events.js +5 -23
- package/dist/commands/graph.js +477 -0
- package/dist/commands/health.js +400 -0
- package/dist/commands/help/help-accept.md +9 -0
- package/dist/commands/help/help-improve.md +77 -0
- package/dist/commands/help/help-proposals.md +15 -0
- package/dist/commands/help/help-propose.md +17 -0
- package/dist/commands/help/help-reject.md +8 -0
- package/dist/commands/history.js +54 -46
- package/dist/commands/improve-profiles.js +146 -0
- package/dist/commands/improve-result-file.js +103 -0
- package/dist/commands/improve.js +2175 -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/index.js +183 -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/vault-key-rules.js +139 -0
- package/dist/commands/lint/workflow-linter.js +56 -0
- package/dist/commands/lint.js +4 -0
- package/dist/commands/migration-help.js +5 -2
- package/dist/commands/proposal.js +66 -12
- package/dist/commands/propose.js +86 -31
- package/dist/commands/reflect.js +1119 -73
- package/dist/commands/registry-search.js +5 -2
- package/dist/commands/remember.js +69 -6
- package/dist/commands/schema-repair.js +203 -0
- package/dist/commands/search.js +115 -14
- package/dist/commands/self-update.js +3 -0
- package/dist/commands/show.js +144 -25
- 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 +438 -0
- package/dist/commands/url-checker.js +42 -0
- package/dist/commands/vault.js +130 -77
- package/dist/core/action-contributors.js +28 -0
- package/dist/core/asset-ref.js +7 -0
- package/dist/core/asset-registry.js +7 -16
- package/dist/core/asset-serialize.js +88 -0
- package/dist/core/asset-spec.js +22 -0
- package/dist/core/common.js +157 -0
- package/dist/core/concurrent.js +25 -0
- package/dist/core/config-io.js +347 -0
- package/dist/core/config-migration.js +625 -0
- package/dist/core/config-schema.js +501 -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 +327 -987
- package/dist/core/errors.js +40 -19
- package/dist/core/events.js +91 -138
- package/dist/core/file-lock.js +104 -0
- package/dist/core/frontmatter.js +3 -6
- 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 +326 -14
- package/dist/core/proposal-quality-validators.js +364 -0
- package/dist/core/proposal-validators.js +69 -0
- package/dist/core/proposals.js +498 -42
- package/dist/core/state-db.js +927 -0
- package/dist/core/text-truncation.js +107 -0
- package/dist/core/time.js +54 -0
- package/dist/core/warn.js +62 -1
- package/dist/core/write-source.js +3 -0
- package/dist/indexer/db-backup.js +391 -0
- package/dist/indexer/db-search.js +152 -253
- package/dist/indexer/db.js +933 -103
- package/dist/indexer/ensure-index.js +64 -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 -124
- package/dist/indexer/index-context.js +4 -0
- package/dist/indexer/indexer.js +506 -291
- package/dist/indexer/llm-cache.js +47 -0
- package/dist/indexer/manifest.js +3 -0
- package/dist/indexer/matchers.js +148 -160
- package/dist/indexer/memory-inference.js +99 -74
- package/dist/indexer/metadata-contributors.js +29 -0
- package/dist/indexer/metadata.js +255 -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 +5 -16
- 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 +150 -74
- package/dist/integrations/agent/runner.js +151 -0
- package/dist/integrations/agent/sdk-runner.js +126 -0
- package/dist/integrations/agent/spawn.js +118 -23
- package/dist/integrations/github.js +3 -0
- package/dist/integrations/lockfile.js +32 -69
- package/dist/integrations/session-logs/index.js +68 -0
- package/dist/integrations/session-logs/providers/claude-code.js +59 -0
- package/dist/integrations/session-logs/providers/opencode.js +55 -0
- package/dist/integrations/session-logs/types.js +4 -0
- package/dist/llm/call-ai.js +62 -0
- package/dist/llm/client.js +72 -124
- package/dist/llm/embedder.js +3 -19
- package/dist/llm/embedders/cache.js +3 -7
- package/dist/llm/embedders/local.js +3 -0
- package/dist/llm/embedders/remote.js +20 -8
- package/dist/llm/embedders/types.js +3 -7
- package/dist/llm/feature-gate.js +89 -48
- package/dist/llm/graph-extract.js +676 -70
- package/dist/llm/index-passes.js +9 -23
- package/dist/llm/memory-infer.js +52 -71
- package/dist/llm/metadata-enhance.js +42 -29
- package/dist/llm/prompts/graph-extract-user-prompt.md +35 -0
- package/dist/output/cli-hints-full.md +281 -0
- package/dist/output/cli-hints-short.md +65 -0
- package/dist/output/cli-hints.js +5 -318
- package/dist/output/context.js +3 -0
- package/dist/output/renderers.js +223 -256
- package/dist/output/shapes.js +150 -105
- package/dist/output/text.js +318 -30
- package/dist/registry/build-index.js +3 -0
- package/dist/registry/create-provider-registry.js +3 -0
- package/dist/registry/factory.js +3 -0
- package/dist/registry/origin-resolve.js +3 -0
- package/dist/registry/providers/index.js +3 -0
- package/dist/registry/providers/skills-sh.js +70 -49
- 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 +17307 -0
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +8900 -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 +7 -5
- 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 +211 -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 +140 -10
- 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 +62 -91
- package/dist/workflows/schema.js +3 -0
- package/dist/workflows/scope-key.js +3 -0
- package/dist/workflows/validator.js +4 -8
- package/dist/workflows/workflow-template.md +24 -0
- package/docs/README.md +9 -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.5.md +2 -2
- 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 +20 -8
- package/.github/LICENSE +0 -374
- package/dist/commands/install-audit.js +0 -381
- package/dist/templates/wiki-templates.js +0 -100
|
@@ -0,0 +1,274 @@
|
|
|
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
|
+
/**
|
|
5
|
+
* LLM-based contradiction-detection pass for derived memories (M-1 / #367).
|
|
6
|
+
*
|
|
7
|
+
* Runs BEFORE `analyzeMemoryCleanup` to populate `contradictedBy` frontmatter
|
|
8
|
+
* edges so the existing `resolveFamilyContradictions` SCC resolver has real
|
|
9
|
+
* input to work on. Without this pass the SCC resolver operates on a nearly
|
|
10
|
+
* empty edge graph because no automated subsystem was previously generating
|
|
11
|
+
* contradiction edges — the elegant Tarjan implementation in memory-improve.ts
|
|
12
|
+
* had no input.
|
|
13
|
+
*
|
|
14
|
+
* # Algorithm
|
|
15
|
+
*
|
|
16
|
+
* 1. Collect all derived memories grouped by `parentRef` family.
|
|
17
|
+
* 2. For each family, enumerate candidate pairs (limited to MAX_FAMILY_SIZE).
|
|
18
|
+
* 3. For each pair, call the LLM to judge whether the two memories are in
|
|
19
|
+
* direct factual conflict.
|
|
20
|
+
* 4. For confirmed contradictions, write `contradictedBy` edges directly to
|
|
21
|
+
* the losing memory's frontmatter (same mechanism as `persistBeliefStateTransition`).
|
|
22
|
+
*
|
|
23
|
+
* # LLM Feature Gate
|
|
24
|
+
*
|
|
25
|
+
* The pass is gated behind `profiles.improve.default.processes.consolidate.contradictionDetection.enabled`.
|
|
26
|
+
* When the gate is disabled or no LLM is configured,
|
|
27
|
+
* the pass is a no-op and `analyzeMemoryCleanup` proceeds with only manually
|
|
28
|
+
* annotated edges.
|
|
29
|
+
*
|
|
30
|
+
* # References
|
|
31
|
+
*
|
|
32
|
+
* - Zep / Graphiti (arXiv:2501.13956): writes contradiction edges at detection time.
|
|
33
|
+
* - ATMS (de Kleer 1986): assumption-based truth maintenance via edge propagation.
|
|
34
|
+
* - mem0 contradiction probe (arXiv:2504.19413): pairwise LLM-judge pattern.
|
|
35
|
+
*/
|
|
36
|
+
import fs from "node:fs";
|
|
37
|
+
import path from "node:path";
|
|
38
|
+
import { chatCompletion, parseEmbeddedJsonResponse } from "../llm/client";
|
|
39
|
+
import { tryLlmFeature } from "../llm/feature-gate";
|
|
40
|
+
import { assembleAsset } from "./asset-serialize";
|
|
41
|
+
import { getDefaultLlmConfig } from "./config";
|
|
42
|
+
import { parseFrontmatter } from "./frontmatter";
|
|
43
|
+
// ── Constants ────────────────────────────────────────────────────────────────
|
|
44
|
+
/**
|
|
45
|
+
* Maximum family size for pairwise contradiction checking. Families larger
|
|
46
|
+
* than this are skipped to bound the LLM call count (O(n²) pairs).
|
|
47
|
+
*/
|
|
48
|
+
const MAX_FAMILY_SIZE = 8;
|
|
49
|
+
/**
|
|
50
|
+
* Maximum number of contradiction pairs to check per improve run, across all
|
|
51
|
+
* families. Prevents runaway LLM usage on stashes with many memories.
|
|
52
|
+
*/
|
|
53
|
+
const MAX_PAIRS_PER_RUN = 20;
|
|
54
|
+
/**
|
|
55
|
+
* Truncation limit for memory body content sent to the LLM judge.
|
|
56
|
+
* Keeps prompts compact while preserving the key factual claims.
|
|
57
|
+
*/
|
|
58
|
+
const BODY_TRUNCATION = 800;
|
|
59
|
+
// ── Prompt builder ────────────────────────────────────────────────────────────
|
|
60
|
+
function buildContradictionJudgePrompt(a, b) {
|
|
61
|
+
return [
|
|
62
|
+
"You are evaluating two derived memory entries to determine if they contain",
|
|
63
|
+
"directly contradictory factual claims about the same subject.",
|
|
64
|
+
"",
|
|
65
|
+
"Memory A:",
|
|
66
|
+
`Ref: ${a.ref}`,
|
|
67
|
+
`Description: ${a.description || "(none)"}`,
|
|
68
|
+
"Content:",
|
|
69
|
+
"```",
|
|
70
|
+
a.body.slice(0, BODY_TRUNCATION),
|
|
71
|
+
"```",
|
|
72
|
+
"",
|
|
73
|
+
"Memory B:",
|
|
74
|
+
`Ref: ${b.ref}`,
|
|
75
|
+
`Description: ${b.description || "(none)"}`,
|
|
76
|
+
"Content:",
|
|
77
|
+
"```",
|
|
78
|
+
b.body.slice(0, BODY_TRUNCATION),
|
|
79
|
+
"```",
|
|
80
|
+
"",
|
|
81
|
+
"Answer ONLY with valid JSON — no prose, no code fences:",
|
|
82
|
+
'{"contradicts": true|false, "reason": "<one sentence explaining why or why not>"}',
|
|
83
|
+
"",
|
|
84
|
+
"A contradiction means the memories make mutually exclusive factual claims about the",
|
|
85
|
+
"same topic (e.g. Memory A says 'always use VPN' while Memory B says 'VPN is optional').",
|
|
86
|
+
"If the memories are complementary, about different topics, or one supersedes the other",
|
|
87
|
+
"without direct conflict, return false.",
|
|
88
|
+
].join("\n");
|
|
89
|
+
}
|
|
90
|
+
// ── Filesystem helpers ────────────────────────────────────────────────────────
|
|
91
|
+
function* walkMarkdownFilesLocal(root) {
|
|
92
|
+
if (!fs.existsSync(root))
|
|
93
|
+
return;
|
|
94
|
+
for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
|
|
95
|
+
const full = path.join(root, entry.name);
|
|
96
|
+
if (entry.isDirectory())
|
|
97
|
+
yield* walkMarkdownFilesLocal(full);
|
|
98
|
+
else if (entry.isFile() && entry.name.endsWith(".md"))
|
|
99
|
+
yield full;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function toMemoryRef(memoriesDir, filePath) {
|
|
103
|
+
const rel = path.relative(memoriesDir, filePath);
|
|
104
|
+
if (!rel || rel.startsWith(".."))
|
|
105
|
+
return undefined;
|
|
106
|
+
const name = rel.replace(/\\/g, "/").replace(/\.md$/i, "");
|
|
107
|
+
return `memory:${name}`;
|
|
108
|
+
}
|
|
109
|
+
function isDerivedMemory(filePath, frontmatter) {
|
|
110
|
+
// Name-based guard (M-2): the .derived suffix is structural and immutable.
|
|
111
|
+
const base = path.basename(filePath, ".md");
|
|
112
|
+
if (base.endsWith(".derived"))
|
|
113
|
+
return true;
|
|
114
|
+
// Frontmatter-based guard: inferred: true marks explicit child memories.
|
|
115
|
+
return frontmatter.inferred === true;
|
|
116
|
+
}
|
|
117
|
+
function resolveParentRef(filePath, frontmatter, memoriesRootDir) {
|
|
118
|
+
// Prefer the explicit source: frontmatter.
|
|
119
|
+
const source = frontmatter.source;
|
|
120
|
+
if (typeof source === "string" && source.startsWith("memory:"))
|
|
121
|
+
return source;
|
|
122
|
+
// Fall back to deriving parent from the file name (strip .derived suffix).
|
|
123
|
+
const base = path.basename(filePath, ".md");
|
|
124
|
+
if (base.endsWith(".derived")) {
|
|
125
|
+
const parentName = base.slice(0, -".derived".length);
|
|
126
|
+
// Use the stash memories root so nested paths (e.g. memories/nested/foo.derived.md)
|
|
127
|
+
// resolve to the correct relative ref (memory:nested/foo, not memory:foo).
|
|
128
|
+
const rootDir = memoriesRootDir ?? path.dirname(filePath);
|
|
129
|
+
const rel = path.relative(rootDir, path.join(path.dirname(filePath), parentName));
|
|
130
|
+
return `memory:${rel.replace(/\\/g, "/")}`;
|
|
131
|
+
}
|
|
132
|
+
return undefined;
|
|
133
|
+
}
|
|
134
|
+
// ── Edge writing ─────────────────────────────────────────────────────────────
|
|
135
|
+
/**
|
|
136
|
+
* Write a `contradictedBy` edge to the losing memory's frontmatter file.
|
|
137
|
+
* Preserves all existing frontmatter keys; only adds/updates `contradictedBy`
|
|
138
|
+
* and `beliefState: contradicted`.
|
|
139
|
+
*/
|
|
140
|
+
/** Returns true if the edge was newly written, false if it already existed. */
|
|
141
|
+
function writeContradictedByEdge(filePath, contradictedByRef) {
|
|
142
|
+
const raw = fs.readFileSync(filePath, "utf8");
|
|
143
|
+
const parsed = parseFrontmatter(raw);
|
|
144
|
+
const existing = Array.isArray(parsed.data.contradictedBy) ? parsed.data.contradictedBy : [];
|
|
145
|
+
if (existing.includes(contradictedByRef))
|
|
146
|
+
return false; // Edge already written.
|
|
147
|
+
const updatedContradictedBy = [...new Set([...existing, contradictedByRef])].sort();
|
|
148
|
+
const nextFrontmatter = {
|
|
149
|
+
...parsed.data,
|
|
150
|
+
contradictedBy: updatedContradictedBy,
|
|
151
|
+
beliefState: "contradicted",
|
|
152
|
+
};
|
|
153
|
+
fs.writeFileSync(filePath, assembleAsset(nextFrontmatter, parsed.content), "utf8");
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
// ── Main entry point ──────────────────────────────────────────────────────────
|
|
157
|
+
/**
|
|
158
|
+
* Run the LLM-based contradiction-detection pass on derived memories in
|
|
159
|
+
* `<stashDir>/memories/`. Writes `contradictedBy` frontmatter edges for
|
|
160
|
+
* confirmed contradiction pairs so the subsequent `resolveFamilyContradictions`
|
|
161
|
+
* SCC pass has edges to work on.
|
|
162
|
+
*
|
|
163
|
+
* @param stashDir - Root stash directory.
|
|
164
|
+
* @param config - Loaded AKM config (used to access LLM settings).
|
|
165
|
+
* @param chat - Optional chat seam for testing (defaults to chatCompletion).
|
|
166
|
+
*/
|
|
167
|
+
export async function detectAndWriteContradictions(stashDir, config, chat = chatCompletion) {
|
|
168
|
+
const result = {
|
|
169
|
+
familiesExamined: 0,
|
|
170
|
+
pairsChecked: 0,
|
|
171
|
+
edgesWritten: 0,
|
|
172
|
+
warnings: [],
|
|
173
|
+
};
|
|
174
|
+
const contradictionLlm = getDefaultLlmConfig(config);
|
|
175
|
+
if (!contradictionLlm)
|
|
176
|
+
return result;
|
|
177
|
+
// Collect derived memories grouped by parent.
|
|
178
|
+
const memoriesDir = path.join(stashDir, "memories");
|
|
179
|
+
const byParent = new Map();
|
|
180
|
+
for (const filePath of walkMarkdownFilesLocal(memoriesDir)) {
|
|
181
|
+
let raw;
|
|
182
|
+
try {
|
|
183
|
+
raw = fs.readFileSync(filePath, "utf8");
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
const parsed = parseFrontmatter(raw);
|
|
189
|
+
if (!isDerivedMemory(filePath, parsed.data))
|
|
190
|
+
continue;
|
|
191
|
+
const parentRef = resolveParentRef(filePath, parsed.data, memoriesDir);
|
|
192
|
+
if (!parentRef)
|
|
193
|
+
continue;
|
|
194
|
+
const ref = toMemoryRef(memoriesDir, filePath);
|
|
195
|
+
if (!ref)
|
|
196
|
+
continue;
|
|
197
|
+
const entry = {
|
|
198
|
+
filePath,
|
|
199
|
+
ref,
|
|
200
|
+
parentRef,
|
|
201
|
+
body: parsed.content.trim(),
|
|
202
|
+
description: typeof parsed.data.description === "string" ? parsed.data.description : "",
|
|
203
|
+
};
|
|
204
|
+
const family = byParent.get(parentRef) ?? [];
|
|
205
|
+
family.push(entry);
|
|
206
|
+
byParent.set(parentRef, family);
|
|
207
|
+
}
|
|
208
|
+
let totalPairsChecked = 0;
|
|
209
|
+
for (const [, family] of byParent) {
|
|
210
|
+
if (family.length < 2)
|
|
211
|
+
continue;
|
|
212
|
+
if (family.length > MAX_FAMILY_SIZE) {
|
|
213
|
+
result.warnings.push(`Skipping contradiction check for family of ${family.length} members (exceeds MAX_FAMILY_SIZE=${MAX_FAMILY_SIZE})`);
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
result.familiesExamined++;
|
|
217
|
+
for (let i = 0; i < family.length - 1; i++) {
|
|
218
|
+
for (let j = i + 1; j < family.length; j++) {
|
|
219
|
+
if (totalPairsChecked >= MAX_PAIRS_PER_RUN)
|
|
220
|
+
break;
|
|
221
|
+
const a = family[i];
|
|
222
|
+
const b = family[j];
|
|
223
|
+
if (!a || !b)
|
|
224
|
+
continue;
|
|
225
|
+
// Skip pairs where edges already exist in BOTH directions (no new information).
|
|
226
|
+
const aRaw = fs.readFileSync(a.filePath, "utf8");
|
|
227
|
+
const aParsed = parseFrontmatter(aRaw);
|
|
228
|
+
const aCB = Array.isArray(aParsed.data.contradictedBy)
|
|
229
|
+
? aParsed.data.contradictedBy
|
|
230
|
+
: [];
|
|
231
|
+
const bRaw = fs.readFileSync(b.filePath, "utf8");
|
|
232
|
+
const bParsed = parseFrontmatter(bRaw);
|
|
233
|
+
const bCB = Array.isArray(bParsed.data.contradictedBy)
|
|
234
|
+
? bParsed.data.contradictedBy
|
|
235
|
+
: [];
|
|
236
|
+
if (aCB.includes(b.ref) && bCB.includes(a.ref))
|
|
237
|
+
continue;
|
|
238
|
+
const prompt = buildContradictionJudgePrompt(a, b);
|
|
239
|
+
const judgeResult = await tryLlmFeature("memory_contradiction_detection", config, async () => {
|
|
240
|
+
return chat(contradictionLlm, [
|
|
241
|
+
{ role: "system", content: "Return only valid JSON. No prose." },
|
|
242
|
+
{ role: "user", content: prompt },
|
|
243
|
+
]);
|
|
244
|
+
}, null);
|
|
245
|
+
totalPairsChecked++;
|
|
246
|
+
result.pairsChecked++;
|
|
247
|
+
if (!judgeResult)
|
|
248
|
+
continue; // Feature gate disabled or LLM call failed.
|
|
249
|
+
let parsed = null;
|
|
250
|
+
try {
|
|
251
|
+
parsed = parseEmbeddedJsonResponse(judgeResult);
|
|
252
|
+
}
|
|
253
|
+
catch {
|
|
254
|
+
result.warnings.push(`Could not parse contradiction judge response for pair ${a.ref} / ${b.ref}`);
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
if (!parsed?.contradicts)
|
|
258
|
+
continue;
|
|
259
|
+
// Write contradiction edges: both members get contradictedBy pointing to each other.
|
|
260
|
+
try {
|
|
261
|
+
const wroteA = writeContradictedByEdge(a.filePath, b.ref);
|
|
262
|
+
const wroteB = writeContradictedByEdge(b.filePath, a.ref);
|
|
263
|
+
result.edgesWritten += (wroteA ? 1 : 0) + (wroteB ? 1 : 0);
|
|
264
|
+
}
|
|
265
|
+
catch (err) {
|
|
266
|
+
result.warnings.push(`Failed to write contradiction edge ${a.ref} <-> ${b.ref}: ${err instanceof Error ? err.message : String(err)}`);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
if (totalPairsChecked >= MAX_PAIRS_PER_RUN)
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return result;
|
|
274
|
+
}
|