akm-cli 0.8.0 → 0.8.2
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 +118 -0
- package/dist/assets/profiles/default.json +15 -0
- package/dist/assets/profiles/graph-refresh.json +13 -0
- package/dist/assets/profiles/memory-focus.json +12 -0
- package/dist/assets/profiles/quick.json +15 -0
- package/dist/assets/profiles/thorough.json +15 -0
- package/dist/assets/stash-skeleton/README.md +76 -0
- package/dist/assets/tasks/graph-refresh-weekly.yml +10 -0
- package/dist/cli.js +8 -3
- package/dist/commands/consolidate.js +36 -15
- package/dist/commands/extract-prompt.js +14 -1
- package/dist/commands/health.js +89 -8
- package/dist/commands/improve-cli.js +2 -2
- package/dist/commands/improve-profiles.js +13 -59
- package/dist/commands/improve-result-file.js +9 -4
- package/dist/commands/improve.js +86 -65
- package/dist/commands/info.js +23 -28
- package/dist/commands/init.js +6 -1
- package/dist/commands/{proposal-drain-policies.js → proposal/drain-policies.js} +2 -2
- package/dist/commands/{proposal-drain.js → proposal/drain.js} +10 -10
- package/dist/commands/show.js +47 -0
- package/dist/commands/stash-skeleton.js +78 -0
- package/dist/{setup/ripgrep-install.js → core/ripgrep/install.js} +2 -2
- package/dist/{setup/ripgrep-resolve.js → core/ripgrep/resolve.js} +2 -2
- package/dist/core/stash-meta.js +110 -0
- package/dist/indexer/indexer.js +2 -2
- package/dist/llm/graph-extract.js +1 -1
- package/dist/output/cli-hints.js +2 -2
- package/dist/setup/detect.js +27 -0
- package/dist/setup/harness-config-import.js +170 -0
- package/dist/setup/registry-stash-loader.js +99 -0
- package/dist/setup/setup.js +229 -72
- package/dist/tasks/backends/launchd.js +1 -1
- package/dist/tasks/backends/schtasks.js +1 -1
- package/dist/wiki/wiki-templates.js +3 -3
- package/dist/wiki/wiki.js +1 -1
- package/dist/workflows/authoring.js +1 -1
- package/package.json +1 -1
- /package/dist/{tasks → assets}/backends/launchd-template.xml +0 -0
- /package/dist/{tasks → assets}/backends/schtasks-template.xml +0 -0
- /package/dist/{commands → assets}/help/help-accept.md +0 -0
- /package/dist/{commands → assets}/help/help-improve.md +0 -0
- /package/dist/{commands → assets}/help/help-proposals.md +0 -0
- /package/dist/{commands → assets}/help/help-propose.md +0 -0
- /package/dist/{commands → assets}/help/help-reject.md +0 -0
- /package/dist/{output → assets/hints}/cli-hints-full.md +0 -0
- /package/dist/{output → assets/hints}/cli-hints-short.md +0 -0
- /package/dist/{llm → assets}/prompts/extract-session.md +0 -0
- /package/dist/{llm → assets}/prompts/graph-extract-user-prompt.md +0 -0
- /package/dist/{wiki → assets/wiki}/index-template.md +0 -0
- /package/dist/{wiki → assets/wiki}/ingest-workflow-template.md +0 -0
- /package/dist/{wiki → assets/wiki}/log-template.md +0 -0
- /package/dist/{wiki → assets/wiki}/schema-template.md +0 -0
- /package/dist/{workflows → assets/workflows}/workflow-template.md +0 -0
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
2
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
import profileDefault from "../assets/profiles/default.json" with { type: "json" };
|
|
5
|
+
import profileGraphRefresh from "../assets/profiles/graph-refresh.json" with { type: "json" };
|
|
6
|
+
import profileMemoryFocus from "../assets/profiles/memory-focus.json" with { type: "json" };
|
|
7
|
+
import profileQuick from "../assets/profiles/quick.json" with { type: "json" };
|
|
8
|
+
import profileThorough from "../assets/profiles/thorough.json" with { type: "json" };
|
|
4
9
|
import { parseAssetRef } from "../core/asset-ref";
|
|
5
10
|
import { warn } from "../core/warn";
|
|
6
11
|
/** Profile name used as the final fallback when nothing else resolves. */
|
|
@@ -11,66 +16,15 @@ export const DEFAULT_ALLOWED_TYPES = {
|
|
|
11
16
|
distill: ["memory"],
|
|
12
17
|
consolidate: ["memory"],
|
|
13
18
|
};
|
|
19
|
+
// Built-in profiles are loaded from embedded JSON files in src/assets/profiles/.
|
|
20
|
+
// To add a new profile: create a new .json file there, import it above, and add
|
|
21
|
+
// it to this map. No code change needed beyond those two steps.
|
|
14
22
|
const BUILTIN_PROFILES = {
|
|
15
|
-
default:
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
consolidate: { enabled: true, allowedTypes: DEFAULT_ALLOWED_TYPES.consolidate },
|
|
21
|
-
memoryInference: { enabled: true },
|
|
22
|
-
graphExtraction: { enabled: true },
|
|
23
|
-
// validation: deliberately undefined — third-tier classifier is opt-in.
|
|
24
|
-
triage: { enabled: false, applyMode: "queue", policy: "personal-stash" },
|
|
25
|
-
},
|
|
26
|
-
sync: { enabled: true, push: true },
|
|
27
|
-
},
|
|
28
|
-
quick: {
|
|
29
|
-
description: "Reflect-only pass — no distill, consolidate, memoryInference, or graphExtraction.",
|
|
30
|
-
processes: {
|
|
31
|
-
reflect: { enabled: true, allowedTypes: DEFAULT_ALLOWED_TYPES.reflect },
|
|
32
|
-
distill: { enabled: false },
|
|
33
|
-
consolidate: { enabled: false },
|
|
34
|
-
memoryInference: { enabled: false },
|
|
35
|
-
graphExtraction: { enabled: false },
|
|
36
|
-
triage: { enabled: false },
|
|
37
|
-
},
|
|
38
|
-
// Lightweight passes opt out of end-of-run sync: a reflect-only `quick`
|
|
39
|
-
// run should not auto-commit/push the git-backed stash to its remote.
|
|
40
|
-
// (The auto-sync gate in improve.ts treats an absent sync block as
|
|
41
|
-
// ENABLED + push, so we set this explicitly to avoid a surprise push.)
|
|
42
|
-
sync: { enabled: false },
|
|
43
|
-
},
|
|
44
|
-
thorough: {
|
|
45
|
-
// Reserved for future divergence; for now behaviorally identical to
|
|
46
|
-
// `default`. Documented here so callers picking `--profile thorough` do
|
|
47
|
-
// not expect a different code path until we wire stricter limits in.
|
|
48
|
-
description: "All sub-processes enabled (currently identical to default; reserved for future divergence).",
|
|
49
|
-
processes: {
|
|
50
|
-
reflect: { enabled: true, allowedTypes: DEFAULT_ALLOWED_TYPES.reflect },
|
|
51
|
-
distill: { enabled: true, allowedTypes: DEFAULT_ALLOWED_TYPES.distill },
|
|
52
|
-
consolidate: { enabled: true, allowedTypes: DEFAULT_ALLOWED_TYPES.consolidate },
|
|
53
|
-
memoryInference: { enabled: true },
|
|
54
|
-
graphExtraction: { enabled: true },
|
|
55
|
-
triage: { enabled: true, applyMode: "queue" },
|
|
56
|
-
},
|
|
57
|
-
sync: { enabled: true, push: true },
|
|
58
|
-
},
|
|
59
|
-
"memory-focus": {
|
|
60
|
-
description: "Memory and lesson improvement only — no distill or consolidate.",
|
|
61
|
-
processes: {
|
|
62
|
-
reflect: { enabled: true, allowedTypes: ["memory", "lesson"] },
|
|
63
|
-
distill: { enabled: false },
|
|
64
|
-
consolidate: { enabled: false },
|
|
65
|
-
memoryInference: { enabled: true },
|
|
66
|
-
graphExtraction: { enabled: false },
|
|
67
|
-
triage: { enabled: false },
|
|
68
|
-
},
|
|
69
|
-
// Limited pass opts out of end-of-run sync for the same reason as `quick`:
|
|
70
|
-
// a memory/lesson-only run should not auto-commit/push the stash. Explicit
|
|
71
|
-
// here because improve.ts treats an absent sync block as ENABLED + push.
|
|
72
|
-
sync: { enabled: false },
|
|
73
|
-
},
|
|
23
|
+
default: profileDefault,
|
|
24
|
+
quick: profileQuick,
|
|
25
|
+
thorough: profileThorough,
|
|
26
|
+
"memory-focus": profileMemoryFocus,
|
|
27
|
+
"graph-refresh": profileGraphRefresh,
|
|
74
28
|
};
|
|
75
29
|
/**
|
|
76
30
|
* Default enabled-state for known improve processes when neither the user
|
|
@@ -73,14 +73,19 @@ export function relativeImproveResultPath(runId) {
|
|
|
73
73
|
* (closes the dry-run/real-run artifact-trap recorded in MEMORY.md
|
|
74
74
|
* `feedback_akm_dryrun_artifact_trap`).
|
|
75
75
|
*/
|
|
76
|
-
export function writeImproveResultFile(stashDir, runId, result) {
|
|
76
|
+
export function writeImproveResultFile(stashDir, runId, result, startedAt) {
|
|
77
77
|
const db = openStateDatabase();
|
|
78
78
|
try {
|
|
79
|
-
const
|
|
79
|
+
const completedAt = new Date().toISOString();
|
|
80
|
+
// startedAt is the ISO timestamp captured at process launch (passed from the
|
|
81
|
+
// CLI entry point). If omitted, fall back to the run-id's embedded timestamp
|
|
82
|
+
// so started_at != completed_at even on older call sites.
|
|
83
|
+
const resolvedStartedAt = startedAt ??
|
|
84
|
+
runId.slice(0, 24).replace(/^(\d{4}-\d{2}-\d{2}T)(\d{2})-(\d{2})-(\d{2})-(\d{3})Z$/, "$1$2:$3:$4.$5Z");
|
|
80
85
|
recordImproveRun(db, {
|
|
81
86
|
id: runId,
|
|
82
|
-
startedAt,
|
|
83
|
-
completedAt
|
|
87
|
+
startedAt: resolvedStartedAt,
|
|
88
|
+
completedAt,
|
|
84
89
|
stashDir,
|
|
85
90
|
dryRun: Boolean(result.dryRun),
|
|
86
91
|
profile: null,
|
package/dist/commands/improve.js
CHANGED
|
@@ -25,7 +25,7 @@ import { resolveAssetPath } from "../indexer/path-resolver";
|
|
|
25
25
|
import { getWritableStashDirs, resolveSourceEntries } from "../indexer/search-source";
|
|
26
26
|
import { runStalenessDetectionPass } from "../indexer/staleness-detect";
|
|
27
27
|
import { resolveImproveProcessRunnerFromProfile, resolveTriageJudgmentRunner } from "../integrations/agent/runner";
|
|
28
|
-
import { getAvailableHarnesses
|
|
28
|
+
import { getAvailableHarnesses } from "../integrations/session-logs";
|
|
29
29
|
import { isLlmFeatureEnabled, isProcessEnabled } from "../llm/feature-gate";
|
|
30
30
|
import { isGitBackedStash, resolveWritableOverride, saveGitStash } from "../sources/providers/git";
|
|
31
31
|
import { akmConsolidate } from "./consolidate";
|
|
@@ -36,8 +36,8 @@ import { akmExtract } from "./extract";
|
|
|
36
36
|
import { makeGateConfig, resolveExtractConfidence, runAutoAcceptGate } from "./improve-auto-accept";
|
|
37
37
|
import { isProfileFilteredForAllPasses, resolveImproveProfile, resolveProcessEnabled, shouldSkipRef, } from "./improve-profiles";
|
|
38
38
|
import { akmLint } from "./lint/index";
|
|
39
|
-
import { drainProposals } from "./proposal
|
|
40
|
-
import { resolveDrainPolicy } from "./proposal
|
|
39
|
+
import { drainProposals } from "./proposal/drain";
|
|
40
|
+
import { resolveDrainPolicy } from "./proposal/drain-policies";
|
|
41
41
|
import { akmReflect } from "./reflect";
|
|
42
42
|
import { runSchemaRepairPass } from "./schema-repair";
|
|
43
43
|
import { checkDeadUrls } from "./url-checker";
|
|
@@ -674,7 +674,13 @@ export async function akmImprove(options = {}) {
|
|
|
674
674
|
// lives in the outer scope. It is always assigned at the top of the try.
|
|
675
675
|
let eventsCtx = {};
|
|
676
676
|
try {
|
|
677
|
-
const budgetTimer = setTimeout(() =>
|
|
677
|
+
const budgetTimer = setTimeout(() => {
|
|
678
|
+
budgetAbortController.abort("improve budget exhausted");
|
|
679
|
+
// Grace period: let finally run to release improve.lock, then hard-exit
|
|
680
|
+
// to prevent the process outliving the task timeout window (lock-cascade fix).
|
|
681
|
+
// Exit 0: budget exhaustion is a normal scheduled-task condition, not an error.
|
|
682
|
+
setTimeout(() => process.exit(0), 5_000);
|
|
683
|
+
}, budgetMs);
|
|
678
684
|
// Clear the timer when the run ends to avoid keeping the event loop alive.
|
|
679
685
|
clearBudgetTimer = () => clearTimeout(budgetTimer);
|
|
680
686
|
try {
|
|
@@ -724,7 +730,7 @@ export async function akmImprove(options = {}) {
|
|
|
724
730
|
rejectedProposalsByRef.set(e.ref, e);
|
|
725
731
|
}
|
|
726
732
|
}
|
|
727
|
-
const { reflectsWithErrorContext, memoryRefsForInference, gateAutoAcceptedCount: loopGateCount, } = await runImproveLoopStage({
|
|
733
|
+
const { reflectsWithErrorContext, memoryRefsForInference, gateAutoAcceptedCount: loopGateCount, gateAutoAcceptFailedCount: loopGateFailedCount, } = await runImproveLoopStage({
|
|
728
734
|
scope,
|
|
729
735
|
options,
|
|
730
736
|
primaryStashDir,
|
|
@@ -743,7 +749,7 @@ export async function akmImprove(options = {}) {
|
|
|
743
749
|
eventsCtx,
|
|
744
750
|
improveProfile,
|
|
745
751
|
});
|
|
746
|
-
const { allWarnings, consolidation, deadUrls, memoryInference, graphExtraction, stalenessDetection, maintenanceActions, memoryInferenceDurationMs, graphExtractionDurationMs, orphansPurged, proposalsExpired, gateAutoAcceptedCount: postLoopGateCount, } = await runImprovePostLoopStage({
|
|
752
|
+
const { allWarnings, consolidation, deadUrls, memoryInference, graphExtraction, stalenessDetection, maintenanceActions, memoryInferenceDurationMs, graphExtractionDurationMs, orphansPurged, proposalsExpired, gateAutoAcceptedCount: postLoopGateCount, gateAutoAcceptFailedCount: postLoopGateFailedCount, } = await runImprovePostLoopStage({
|
|
747
753
|
scope,
|
|
748
754
|
options,
|
|
749
755
|
primaryStashDir,
|
|
@@ -798,9 +804,6 @@ export async function akmImprove(options = {}) {
|
|
|
798
804
|
...(preparation.lintSummary !== undefined ? { lintSummary: preparation.lintSummary } : {}),
|
|
799
805
|
...(preparation.memoryIndexHealth !== undefined ? { memoryIndexHealth: preparation.memoryIndexHealth } : {}),
|
|
800
806
|
...(preparation.coverageGaps.length > 0 ? { coverageGaps: preparation.coverageGaps } : {}),
|
|
801
|
-
...(preparation.executionLogCandidates.length > 0
|
|
802
|
-
? { executionLogCandidates: preparation.executionLogCandidates }
|
|
803
|
-
: {}),
|
|
804
807
|
...(preparation.extract && preparation.extract.length > 0 ? { extract: preparation.extract } : {}),
|
|
805
808
|
...(primaryStashDir !== undefined ? { evalCasesWritten: countEvalCases(primaryStashDir) } : {}),
|
|
806
809
|
...(deadUrls !== undefined && deadUrls.length > 0 ? { deadUrls } : {}),
|
|
@@ -831,6 +834,10 @@ export async function akmImprove(options = {}) {
|
|
|
831
834
|
const t = preparation.gateAutoAcceptedCount + loopGateCount + postLoopGateCount;
|
|
832
835
|
return t > 0 ? { gateAutoAcceptedCount: t } : {};
|
|
833
836
|
})(),
|
|
837
|
+
...(() => {
|
|
838
|
+
const f = preparation.gateAutoAcceptFailedCount + loopGateFailedCount + postLoopGateFailedCount;
|
|
839
|
+
return f > 0 ? { gateAutoAcceptFailedCount: f } : {};
|
|
840
|
+
})(),
|
|
834
841
|
};
|
|
835
842
|
if (!result.dryRun)
|
|
836
843
|
emitImproveCompletedEvent(result, {
|
|
@@ -990,7 +997,6 @@ function emitImproveCompletedEvent(result, durations, eventsCtx) {
|
|
|
990
997
|
reflectSkippedActions: actionCounts.reflectSkipped,
|
|
991
998
|
reflectsWithErrorContext: result.reflectsWithErrorContext ?? 0,
|
|
992
999
|
coverageGapCount: result.coverageGaps?.length ?? 0,
|
|
993
|
-
executionLogCandidateCount: result.executionLogCandidates?.length ?? 0,
|
|
994
1000
|
evalCasesWritten: result.evalCasesWritten ?? 0,
|
|
995
1001
|
deadUrlCount: result.deadUrls?.length ?? 0,
|
|
996
1002
|
memoryEligible: result.memorySummary.eligible,
|
|
@@ -1047,15 +1053,6 @@ async function runImprovePreparationStage(args) {
|
|
|
1047
1053
|
}
|
|
1048
1054
|
}
|
|
1049
1055
|
}
|
|
1050
|
-
// Phase 0 — execution log synthesis
|
|
1051
|
-
let executionLogCandidates = [];
|
|
1052
|
-
try {
|
|
1053
|
-
const logEntries = getExecutionLogCandidates(7);
|
|
1054
|
-
executionLogCandidates = logEntries.filter((e) => e.isFailurePattern).map((e) => e.topic);
|
|
1055
|
-
}
|
|
1056
|
-
catch {
|
|
1057
|
-
// best-effort
|
|
1058
|
-
}
|
|
1059
1056
|
// Phase 0.4 — session-extract pass.
|
|
1060
1057
|
//
|
|
1061
1058
|
// Reads native session files (claude-code JSONL, opencode storage tree)
|
|
@@ -1074,6 +1071,7 @@ async function runImprovePreparationStage(args) {
|
|
|
1074
1071
|
// The extract envelope's own `warnings` field surfaces what went wrong.
|
|
1075
1072
|
let extractResults;
|
|
1076
1073
|
let gateAutoAcceptedCount = 0;
|
|
1074
|
+
let gateAutoAcceptFailedCount = 0;
|
|
1077
1075
|
const extractConfig = options.config ?? loadConfig();
|
|
1078
1076
|
const extractGateCfg = makeGateConfig("extract", {
|
|
1079
1077
|
globalThreshold: options.autoAccept,
|
|
@@ -1095,12 +1093,16 @@ async function runImprovePreparationStage(args) {
|
|
|
1095
1093
|
dryRun: options.dryRun ?? false,
|
|
1096
1094
|
});
|
|
1097
1095
|
extractResults.push(result);
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1096
|
+
{
|
|
1097
|
+
const gr = await runAutoAcceptGate(primaryStashDir
|
|
1098
|
+
? result.proposals.map((proposalId) => {
|
|
1099
|
+
const proposal = getProposal(primaryStashDir, proposalId);
|
|
1100
|
+
return { proposalId, confidence: resolveExtractConfidence(proposal) };
|
|
1101
|
+
})
|
|
1102
|
+
: [], extractGateCfg);
|
|
1103
|
+
gateAutoAcceptedCount += gr.promoted.length;
|
|
1104
|
+
gateAutoAcceptFailedCount += gr.failed.length;
|
|
1105
|
+
}
|
|
1104
1106
|
}
|
|
1105
1107
|
catch (err) {
|
|
1106
1108
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -1126,7 +1128,9 @@ async function runImprovePreparationStage(args) {
|
|
|
1126
1128
|
proposalId: p.id,
|
|
1127
1129
|
confidence: resolveExtractConfidence(p),
|
|
1128
1130
|
}));
|
|
1129
|
-
|
|
1131
|
+
const backlogGr = await runAutoAcceptGate(backlogCandidates, extractGateCfg);
|
|
1132
|
+
gateAutoAcceptedCount += backlogGr.promoted.length;
|
|
1133
|
+
gateAutoAcceptFailedCount += backlogGr.failed.length;
|
|
1130
1134
|
}
|
|
1131
1135
|
}
|
|
1132
1136
|
// eligibleCount = raw pre-filter count (before cooldown/signal/cleanup filters).
|
|
@@ -1539,7 +1543,6 @@ async function runImprovePreparationStage(args) {
|
|
|
1539
1543
|
cleanupWarnings,
|
|
1540
1544
|
appliedCleanup,
|
|
1541
1545
|
memoryIndexHealth,
|
|
1542
|
-
executionLogCandidates,
|
|
1543
1546
|
extract: extractResults,
|
|
1544
1547
|
actionableRefs,
|
|
1545
1548
|
signalBearingSet,
|
|
@@ -1553,6 +1556,7 @@ async function runImprovePreparationStage(args) {
|
|
|
1553
1556
|
recentErrors,
|
|
1554
1557
|
utilityMap,
|
|
1555
1558
|
gateAutoAcceptedCount,
|
|
1559
|
+
gateAutoAcceptFailedCount,
|
|
1556
1560
|
};
|
|
1557
1561
|
}
|
|
1558
1562
|
// TODO(refactor): 13 args including `actions`/`recentErrors` mutation channels. Restructure into immutable plan + mutable context objects — deferred to dedicated refactor with isolated testing.
|
|
@@ -1636,6 +1640,7 @@ async function runImproveLoopStage(args) {
|
|
|
1636
1640
|
? listProposals(dedupeStashDirForProposals, { status: "pending" }).map((p) => p.ref)
|
|
1637
1641
|
: []);
|
|
1638
1642
|
let gateAutoAcceptedCount = 0;
|
|
1643
|
+
let gateAutoAcceptFailedCount = 0;
|
|
1639
1644
|
const reflectGateCfg = makeGateConfig("reflect", {
|
|
1640
1645
|
globalThreshold: options.autoAccept,
|
|
1641
1646
|
dryRun: options.dryRun ?? false,
|
|
@@ -1812,7 +1817,9 @@ async function runImproveLoopStage(args) {
|
|
|
1812
1817
|
},
|
|
1813
1818
|
}, eventsCtx);
|
|
1814
1819
|
if (reflectResult.ok) {
|
|
1815
|
-
|
|
1820
|
+
const reflectGr = await runAutoAcceptGate([{ proposalId: reflectResult.proposal.id, confidence: reflectResult.proposal.confidence }], reflectGateCfg);
|
|
1821
|
+
gateAutoAcceptedCount += reflectGr.promoted.length;
|
|
1822
|
+
gateAutoAcceptFailedCount += reflectGr.failed.length;
|
|
1816
1823
|
}
|
|
1817
1824
|
} // end else (reflect type/profile check)
|
|
1818
1825
|
}
|
|
@@ -1923,7 +1930,9 @@ async function runImproveLoopStage(args) {
|
|
|
1923
1930
|
});
|
|
1924
1931
|
actions.push({ ref: planned.ref, mode: "distill", result: distillResult });
|
|
1925
1932
|
if (distillResult.outcome === "queued" && distillResult.proposal) {
|
|
1926
|
-
|
|
1933
|
+
const distillGr = await runAutoAcceptGate([{ proposalId: distillResult.proposal.id, confidence: distillResult.proposal.confidence }], distillGateCfg);
|
|
1934
|
+
gateAutoAcceptedCount += distillGr.promoted.length;
|
|
1935
|
+
gateAutoAcceptFailedCount += distillGr.failed.length;
|
|
1927
1936
|
}
|
|
1928
1937
|
if (parsedPlannedRef.type === "memory") {
|
|
1929
1938
|
const promotedToKnowledge = distillResult.outcome === "queued" && distillResult.proposalKind === "knowledge";
|
|
@@ -1996,7 +2005,7 @@ async function runImproveLoopStage(args) {
|
|
|
1996
2005
|
completedCount++;
|
|
1997
2006
|
info(`[improve] ${completedCount}/${loopRefs.length} ${planned.ref}`);
|
|
1998
2007
|
}
|
|
1999
|
-
return { reflectsWithErrorContext, memoryRefsForInference, gateAutoAcceptedCount };
|
|
2008
|
+
return { reflectsWithErrorContext, memoryRefsForInference, gateAutoAcceptedCount, gateAutoAcceptFailedCount };
|
|
2000
2009
|
}
|
|
2001
2010
|
async function runImprovePostLoopStage(args) {
|
|
2002
2011
|
const { scope, options, primaryStashDir, actionableRefs, appliedCleanup, cleanupWarnings, memorySummary, memoryRefsForInference, reindexFn, eventsCtx, budgetSignal, improveProfile, } = args;
|
|
@@ -2092,6 +2101,7 @@ async function runImprovePostLoopStage(args) {
|
|
|
2092
2101
|
durationMs: 0,
|
|
2093
2102
|
};
|
|
2094
2103
|
let gateAutoAcceptedCount = 0;
|
|
2104
|
+
let gateAutoAcceptFailedCount = 0;
|
|
2095
2105
|
const consolidateGateCfg = makeGateConfig("consolidate", {
|
|
2096
2106
|
globalThreshold: options.autoAccept,
|
|
2097
2107
|
dryRun: options.dryRun ?? false,
|
|
@@ -2111,13 +2121,16 @@ async function runImprovePostLoopStage(args) {
|
|
|
2111
2121
|
// Tie consolidate proposals back to this improve invocation so
|
|
2112
2122
|
// accept-rate-per-run aggregation works. Mirrors reflect/propose/extract.
|
|
2113
2123
|
sourceRun: `consolidate-${Date.now()}`,
|
|
2114
|
-
// Incremental consolidation:
|
|
2115
|
-
//
|
|
2116
|
-
//
|
|
2117
|
-
//
|
|
2118
|
-
//
|
|
2119
|
-
//
|
|
2120
|
-
incrementalSince
|
|
2124
|
+
// Incremental consolidation: pass the last-consolidation timestamp so
|
|
2125
|
+
// akmConsolidate skips chunks with no memory changed since then. Converts
|
|
2126
|
+
// consolidation cost from O(pool) to O(changed clusters) — the fix for
|
|
2127
|
+
// the rising p95 tail where full-pool re-judging produced 5–10 min runs
|
|
2128
|
+
// that promoted ~0. undefined → full pass on first-ever run (bootstrap).
|
|
2129
|
+
// volumeTriggered correctly forces the run past cooldown but must NOT
|
|
2130
|
+
// override incrementalSince — the stash has ~1400 eligible memories so
|
|
2131
|
+
// volumeTriggered=true on every run, permanently forcing full 12-chunk
|
|
2132
|
+
// scans (~264s) instead of the intended 1-2 chunk incremental path (~44s).
|
|
2133
|
+
incrementalSince: lastConsolidateTs,
|
|
2121
2134
|
maxChunkSize: improveProfile?.processes?.consolidate?.maxChunkSize,
|
|
2122
2135
|
// Honor profile.autoAccept (already merged into options.autoAccept at the
|
|
2123
2136
|
// top of akmImprove). The CLI parser always supplies 90 when --auto-accept
|
|
@@ -2127,17 +2140,21 @@ async function runImprovePostLoopStage(args) {
|
|
|
2127
2140
|
// still wins because the spread above runs first.
|
|
2128
2141
|
autoAccept: options.consolidateOptions?.autoAccept ?? options.autoAccept,
|
|
2129
2142
|
});
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2143
|
+
{
|
|
2144
|
+
const consolidateGr = await runAutoAcceptGate(consolidation.promoted.map((proposalId) => {
|
|
2145
|
+
try {
|
|
2146
|
+
if (!primaryStashDir)
|
|
2147
|
+
return { proposalId, confidence: undefined };
|
|
2148
|
+
const proposal = getProposal(primaryStashDir, proposalId);
|
|
2149
|
+
return { proposalId, confidence: proposal.confidence };
|
|
2150
|
+
}
|
|
2151
|
+
catch {
|
|
2133
2152
|
return { proposalId, confidence: undefined };
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
}
|
|
2140
|
-
}), consolidateGateCfg)).promoted.length;
|
|
2153
|
+
}
|
|
2154
|
+
}), consolidateGateCfg);
|
|
2155
|
+
gateAutoAcceptedCount += consolidateGr.promoted.length;
|
|
2156
|
+
gateAutoAcceptFailedCount += consolidateGr.failed.length;
|
|
2157
|
+
}
|
|
2141
2158
|
if (consolidation.processed > 0) {
|
|
2142
2159
|
appendEvent({
|
|
2143
2160
|
eventType: "consolidate_completed",
|
|
@@ -2212,6 +2229,7 @@ async function runImprovePostLoopStage(args) {
|
|
|
2212
2229
|
orphansPurged: maintenanceResult.orphansPurged,
|
|
2213
2230
|
proposalsExpired: maintenanceResult.proposalsExpired,
|
|
2214
2231
|
gateAutoAcceptedCount,
|
|
2232
|
+
gateAutoAcceptFailedCount,
|
|
2215
2233
|
};
|
|
2216
2234
|
}
|
|
2217
2235
|
// TODO(refactor): mutates the passed-in `allWarnings` array as a hidden side channel. Return warnings in ImproveMaintenanceResult and merge in caller — invasive signature change deferred to next refactor pass.
|
|
@@ -2287,25 +2305,25 @@ async function runImproveMaintenancePasses(args) {
|
|
|
2287
2305
|
}
|
|
2288
2306
|
const graphEnabled = isProcessEnabled("index", "graph_extraction", config);
|
|
2289
2307
|
const graphExtractionDisabledByProfile = improveProfile?.processes?.graphExtraction?.enabled === false;
|
|
2308
|
+
const graphExtractionFullScan = improveProfile?.processes?.graphExtraction?.fullScan === true;
|
|
2290
2309
|
// Build the set of refs actually touched this run.
|
|
2291
2310
|
const touchedRefs = new Set();
|
|
2292
2311
|
for (const r of args.actionableRefs)
|
|
2293
2312
|
touchedRefs.add(r.ref);
|
|
2294
2313
|
for (const r of memoryRefsForInference)
|
|
2295
2314
|
touchedRefs.add(r);
|
|
2296
|
-
// INVARIANT: graph extraction
|
|
2297
|
-
//
|
|
2298
|
-
//
|
|
2299
|
-
//
|
|
2300
|
-
//
|
|
2301
|
-
//
|
|
2302
|
-
//
|
|
2303
|
-
// mock injection (graphExtractionFn) used by tests stays exercised.
|
|
2315
|
+
// INVARIANT: graph extraction normally runs only on files touched by
|
|
2316
|
+
// actionable refs (candidatePaths). Full-corpus scans are opt-in via
|
|
2317
|
+
// profile.processes.graphExtraction.fullScan = true (used by the
|
|
2318
|
+
// `graph-refresh` built-in profile and its weekly scheduled task).
|
|
2319
|
+
// The empty-Set fallback is intentional when no refs were touched —
|
|
2320
|
+
// the extractor's filter rejects every file and returns empty, keeping
|
|
2321
|
+
// the pass invoked so the action is recorded and tests stay exercised.
|
|
2304
2322
|
if (graphExtractionDisabledByProfile) {
|
|
2305
2323
|
info("[improve] graph extraction skipped (disabled by improve profile)");
|
|
2306
2324
|
}
|
|
2307
2325
|
else if (sources.length > 0 && graphEnabled) {
|
|
2308
|
-
info(
|
|
2326
|
+
info(`[improve] graph extraction starting${graphExtractionFullScan ? " (full-corpus scan)" : ""}`);
|
|
2309
2327
|
const extractionStart = Date.now();
|
|
2310
2328
|
try {
|
|
2311
2329
|
// D9: if consolidation ran but memory inference did not reindex, force a reindex
|
|
@@ -2325,15 +2343,18 @@ async function runImproveMaintenancePasses(args) {
|
|
|
2325
2343
|
closeDatabase(db);
|
|
2326
2344
|
db = openDatabase(getDbPath(), config.embedding?.dimension ? { embeddingDim: config.embedding.dimension } : undefined);
|
|
2327
2345
|
}
|
|
2328
|
-
// Resolve touched refs to absolute file paths.
|
|
2329
|
-
//
|
|
2330
|
-
|
|
2331
|
-
if (
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2346
|
+
// Resolve touched refs to absolute file paths. Skipped for fullScan
|
|
2347
|
+
// (candidatePaths stays undefined → extractor processes all files).
|
|
2348
|
+
let candidatePaths;
|
|
2349
|
+
if (!graphExtractionFullScan) {
|
|
2350
|
+
candidatePaths = new Set();
|
|
2351
|
+
if (primaryStashDir && touchedRefs.size > 0) {
|
|
2352
|
+
const writableDirSet = new Set(getWritableStashDirs(primaryStashDir).map((d) => path.resolve(d)));
|
|
2353
|
+
const resolved = await Promise.all([...touchedRefs].map((ref) => findAssetFilePath(ref, primaryStashDir, writableDirSet).catch(() => null)));
|
|
2354
|
+
for (const p of resolved) {
|
|
2355
|
+
if (typeof p === "string" && p.length > 0)
|
|
2356
|
+
candidatePaths.add(p);
|
|
2357
|
+
}
|
|
2337
2358
|
}
|
|
2338
2359
|
}
|
|
2339
2360
|
const progressHandler = (event) => {
|
package/dist/commands/info.js
CHANGED
|
@@ -46,8 +46,11 @@ export function assembleInfo(options) {
|
|
|
46
46
|
...(s.url ? { url: s.url } : {}),
|
|
47
47
|
...(s.enabled !== undefined ? { enabled: s.enabled } : {}),
|
|
48
48
|
}));
|
|
49
|
-
// Index stats
|
|
50
|
-
|
|
49
|
+
// Index stats — resolve the DB path from config so info reads the same
|
|
50
|
+
// database that health and search use, rather than a bare getDbPath() call
|
|
51
|
+
// that ignores XDG_DATA_HOME or per-config overrides.
|
|
52
|
+
const resolvedDbPath = options?.dbPath ?? getDbPath();
|
|
53
|
+
const indexStats = readIndexStats(resolvedDbPath);
|
|
51
54
|
return {
|
|
52
55
|
schemaVersion: 1,
|
|
53
56
|
version: pkgVersion,
|
|
@@ -64,38 +67,30 @@ export function assembleInfo(options) {
|
|
|
64
67
|
indexStats,
|
|
65
68
|
};
|
|
66
69
|
}
|
|
67
|
-
function readIndexStats(
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
};
|
|
77
|
-
}
|
|
70
|
+
function readIndexStats(resolvedPath) {
|
|
71
|
+
const EMPTY = {
|
|
72
|
+
entryCount: 0,
|
|
73
|
+
lastBuiltAt: null,
|
|
74
|
+
hasEmbeddings: false,
|
|
75
|
+
vecAvailable: false,
|
|
76
|
+
};
|
|
77
|
+
if (!fs.existsSync(resolvedPath))
|
|
78
|
+
return EMPTY;
|
|
78
79
|
let db;
|
|
79
80
|
try {
|
|
80
81
|
db = openExistingDatabase(resolvedPath);
|
|
81
|
-
const entryCount = getEntryCount(db);
|
|
82
|
-
const lastBuiltAt = getMeta(db, "builtAt") ?? null;
|
|
83
|
-
const vecAvailable = isVecAvailable(db);
|
|
84
|
-
const hasEmbeddings = getMeta(db, "hasEmbeddings") === "1";
|
|
85
82
|
return {
|
|
86
|
-
entryCount,
|
|
87
|
-
lastBuiltAt,
|
|
88
|
-
hasEmbeddings,
|
|
89
|
-
vecAvailable,
|
|
83
|
+
entryCount: getEntryCount(db),
|
|
84
|
+
lastBuiltAt: getMeta(db, "builtAt") ?? null,
|
|
85
|
+
hasEmbeddings: getMeta(db, "hasEmbeddings") === "1",
|
|
86
|
+
vecAvailable: isVecAvailable(db),
|
|
90
87
|
};
|
|
91
88
|
}
|
|
92
|
-
catch {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
vecAvailable: false,
|
|
98
|
-
};
|
|
89
|
+
catch (err) {
|
|
90
|
+
// Surface the error so operators can diagnose mismatches between
|
|
91
|
+
// `akm info` and `akm health` rather than silently returning zeros.
|
|
92
|
+
process.stderr.write(`[akm info] failed to read index stats from ${resolvedPath}: ${String(err)}\n`);
|
|
93
|
+
return EMPTY;
|
|
99
94
|
}
|
|
100
95
|
finally {
|
|
101
96
|
if (db) {
|
package/dist/commands/init.js
CHANGED
|
@@ -14,7 +14,8 @@ import { TYPE_DIRS } from "../core/asset-spec";
|
|
|
14
14
|
import { loadUserConfig, saveConfig } from "../core/config";
|
|
15
15
|
import { ConfigError } from "../core/errors";
|
|
16
16
|
import { assertSafeStashDir, getBinDir, getConfigPath, getDefaultStashDir } from "../core/paths";
|
|
17
|
-
import { ensureRg } from "../
|
|
17
|
+
import { ensureRg } from "../core/ripgrep/install";
|
|
18
|
+
import { copyStashSkeleton, scaffoldStashMeta } from "./stash-skeleton";
|
|
18
19
|
/**
|
|
19
20
|
* Refuse to persist a temporary-directory stashDir to the user's config when
|
|
20
21
|
* running under a test runner AND `--dir <tempdir>` was passed explicitly.
|
|
@@ -74,6 +75,10 @@ export async function akmInit(options) {
|
|
|
74
75
|
}
|
|
75
76
|
// Ensure the default stash is a local git repo (no remote required)
|
|
76
77
|
ensureGitRepo(stashDir);
|
|
78
|
+
if (created) {
|
|
79
|
+
copyStashSkeleton(stashDir);
|
|
80
|
+
scaffoldStashMeta(stashDir);
|
|
81
|
+
}
|
|
77
82
|
// Persist stashDir in config.json
|
|
78
83
|
const configPath = getConfigPath();
|
|
79
84
|
const existing = loadUserConfig();
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import fs from "node:fs";
|
|
18
18
|
import { z } from "zod";
|
|
19
|
-
import { UsageError } from "
|
|
20
|
-
import { PROPOSAL_SOURCES } from "
|
|
19
|
+
import { UsageError } from "../../core/errors";
|
|
20
|
+
import { PROPOSAL_SOURCES } from "../../core/proposals";
|
|
21
21
|
// Valid `generator` values for a drain rule are exactly the canonical proposal
|
|
22
22
|
// `source` values (see {@link PROPOSAL_SOURCES} in src/core/proposals.ts). The
|
|
23
23
|
// engine matches rules via `policy.accept.find(r => r.generator === proposal.source)`,
|
|
@@ -36,16 +36,16 @@
|
|
|
36
36
|
*/
|
|
37
37
|
import fs from "node:fs";
|
|
38
38
|
import path from "node:path";
|
|
39
|
-
import { parseAssetRef } from "
|
|
40
|
-
import { resolveAssetPathFromName, TYPE_DIRS } from "
|
|
41
|
-
import { appendEvent } from "
|
|
42
|
-
import { parseFrontmatter } from "
|
|
43
|
-
import { listProposals } from "
|
|
44
|
-
import { info, warn } from "
|
|
45
|
-
import { runAgent } from "
|
|
46
|
-
import { runOpencodeSdk } from "
|
|
47
|
-
import { chatCompletion, stripJsonFences } from "
|
|
48
|
-
import { akmProposalAccept, akmProposalReject } from "
|
|
39
|
+
import { parseAssetRef } from "../../core/asset-ref";
|
|
40
|
+
import { resolveAssetPathFromName, TYPE_DIRS } from "../../core/asset-spec";
|
|
41
|
+
import { appendEvent } from "../../core/events";
|
|
42
|
+
import { parseFrontmatter } from "../../core/frontmatter";
|
|
43
|
+
import { listProposals } from "../../core/proposals";
|
|
44
|
+
import { info, warn } from "../../core/warn";
|
|
45
|
+
import { runAgent } from "../../integrations/agent";
|
|
46
|
+
import { runOpencodeSdk } from "../../integrations/agent/sdk-runner";
|
|
47
|
+
import { chatCompletion, stripJsonFences } from "../../llm/client";
|
|
48
|
+
import { akmProposalAccept, akmProposalReject } from "../proposal";
|
|
49
49
|
// ---------------------------------------------------------------------------
|
|
50
50
|
// Content helpers
|
|
51
51
|
// ---------------------------------------------------------------------------
|
package/dist/commands/show.js
CHANGED
|
@@ -24,6 +24,7 @@ import { loadConfig } from "../core/config";
|
|
|
24
24
|
import { NotFoundError, rethrowIfTestIsolationError, UsageError } from "../core/errors";
|
|
25
25
|
import { appendEvent, readEvents } from "../core/events";
|
|
26
26
|
import { parseFrontmatter } from "../core/frontmatter";
|
|
27
|
+
import { META_DIR, parseMetaRef, resolveMetaFilePath } from "../core/stash-meta";
|
|
27
28
|
import { closeDatabase, findEntryIdByRef, openExistingDatabase } from "../indexer/db";
|
|
28
29
|
import { ensureIndex } from "../indexer/ensure-index";
|
|
29
30
|
import { buildFileContext, buildRenderContext, getRenderer, runMatchers } from "../indexer/file-context";
|
|
@@ -105,6 +106,16 @@ function resolveRegisteredWikiAssetPath(wikiRoot, wikiName, assetName) {
|
|
|
105
106
|
*/
|
|
106
107
|
export async function akmShowUnified(input) {
|
|
107
108
|
const ref = input.ref.trim();
|
|
109
|
+
// 0a. Stash `.meta/` convention: `[origin//]meta[:name]` direct-reads a
|
|
110
|
+
// human-authored orientation doc from the stash's `.meta/` directory.
|
|
111
|
+
// These files are not indexed (the walker skips dot-dirs), so they are
|
|
112
|
+
// resolved here before the index lookup and the `type:name` parser,
|
|
113
|
+
// which would otherwise reject the non-asset-type `meta`.
|
|
114
|
+
{
|
|
115
|
+
const metaRef = parseMetaRef(ref);
|
|
116
|
+
if (metaRef)
|
|
117
|
+
return showStashMeta(metaRef);
|
|
118
|
+
}
|
|
108
119
|
// 0. Wiki-root shortcut: `wiki:<name>` with no page path routes to the
|
|
109
120
|
// wiki summary (same payload as `akm wiki show <name>`). Honour
|
|
110
121
|
// `parsed.origin` by resolving against the matching stash source(s),
|
|
@@ -152,6 +163,42 @@ export async function akmShowUnified(input) {
|
|
|
152
163
|
}
|
|
153
164
|
return result;
|
|
154
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* Resolve a stash `.meta/` doc and return it as a lightweight ShowResponse.
|
|
168
|
+
*
|
|
169
|
+
* With no origin the working stash (and other configured sources, in order)
|
|
170
|
+
* is searched and the first hit wins. With an origin the lookup is narrowed
|
|
171
|
+
* to that stash; an uninstalled origin yields an actionable "not installed"
|
|
172
|
+
* error. The file is read directly from disk — `.meta/` is never indexed.
|
|
173
|
+
*/
|
|
174
|
+
async function showStashMeta(metaRef) {
|
|
175
|
+
const allSources = resolveSourceEntries();
|
|
176
|
+
const sources = resolveSourcesForOrigin(metaRef.origin, allSources);
|
|
177
|
+
if (metaRef.origin && sources.length === 0) {
|
|
178
|
+
throw new NotFoundError(`Stash "${metaRef.origin}" is not installed, so its ${META_DIR}/ docs are unavailable. ` +
|
|
179
|
+
`Run: akm add ${metaRef.origin}`);
|
|
180
|
+
}
|
|
181
|
+
const config = loadConfig();
|
|
182
|
+
for (const source of sources) {
|
|
183
|
+
const filePath = resolveMetaFilePath(source.path, metaRef.name);
|
|
184
|
+
if (!filePath)
|
|
185
|
+
continue;
|
|
186
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
187
|
+
const editable = isEditable(filePath, config);
|
|
188
|
+
appendEvent({ eventType: "show", ref: `meta:${metaRef.name}`, metadata: { type: "meta", name: metaRef.name } });
|
|
189
|
+
return {
|
|
190
|
+
type: "meta",
|
|
191
|
+
name: metaRef.name,
|
|
192
|
+
path: filePath,
|
|
193
|
+
content,
|
|
194
|
+
origin: source.registryId ?? null,
|
|
195
|
+
editable,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
throw new NotFoundError(`No ${META_DIR}/${metaRef.name} doc found${metaRef.origin ? ` in "${metaRef.origin}"` : ""}. ` +
|
|
199
|
+
`Stash maintainers can create ${META_DIR}/${metaRef.name}.md to describe this stash ` +
|
|
200
|
+
`(purpose, key assets, conventions, maintainer).`);
|
|
201
|
+
}
|
|
155
202
|
function hasAnyScopeKey(scope) {
|
|
156
203
|
return Boolean(scope.user || scope.agent || scope.run || scope.channel);
|
|
157
204
|
}
|