akm-cli 0.9.16 → 0.9.17-alpha.1
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 +478 -0
- package/dist/assets/prompts/consolidate-system.md +4 -11
- package/dist/assets/prompts/graph-extract-user-prompt.md +5 -5
- package/dist/commands/health/accept-rate.js +6 -0
- package/dist/commands/health/checks.js +54 -0
- package/dist/commands/health/improve-metrics.js +1 -5
- package/dist/commands/health/report-view-model.js +0 -1
- package/dist/commands/health.js +10 -0
- package/dist/commands/improve/consolidate/chunking.js +19 -35
- package/dist/commands/improve/consolidate/merge.js +6 -9
- package/dist/commands/improve/consolidate.js +104 -91
- package/dist/commands/improve/distill/promote-memory.js +40 -2
- package/dist/commands/improve/distill/quality-gate.js +186 -23
- package/dist/commands/improve/distill.js +42 -8
- package/dist/commands/improve/eligibility.js +13 -3
- package/dist/commands/improve/improve-cli.js +32 -9
- package/dist/commands/improve/improve-strategies.js +23 -1
- package/dist/commands/improve/improve.js +121 -84
- package/dist/commands/improve/loop-stages.js +241 -108
- package/dist/commands/improve/preparation.js +50 -17
- package/dist/commands/improve/reflect.js +16 -5
- package/dist/commands/improve/shared.js +0 -10
- package/dist/commands/proposal/drain.js +79 -10
- package/dist/commands/proposal/proposal-types.js +21 -0
- package/dist/commands/proposal/repository.js +108 -29
- package/dist/core/asset/frontmatter.js +106 -1
- package/dist/core/config/schema/improve-processes.js +29 -2
- package/dist/core/improve-result.js +9 -0
- package/dist/core/paths.js +7 -0
- package/dist/indexer/ensure-index.js +52 -7
- package/dist/indexer/graph/graph-extraction.js +82 -8
- package/dist/indexer/passes/memory-inference.js +16 -1
- package/dist/llm/client.js +16 -2
- package/dist/llm/graph-extract.js +162 -18
- package/dist/scripts/akm-migrate-node.js +20 -4
- package/dist/scripts/akm-migrate.js +20 -4
- package/dist/storage/repositories/index-entries-repository.js +43 -0
- package/dist/storage/repositories/proposals-repository.js +4 -1
- package/dist/storage/state-db-integrity.js +123 -0
- package/dist/workflows/program/schema.js +1 -0
- package/docs/reference/cli.md +4 -3
- package/docs/reference/data-and-telemetry.md +1 -0
- package/package.json +1 -1
- package/schemas/akm-config.json +44 -0
- package/schemas/akm-workflow.json +1 -0
- package/dist/commands/improve/eval-cases.js +0 -52
|
@@ -12792,6 +12792,7 @@ var init_schema = __esm(() => {
|
|
|
12792
12792
|
content_policy_reject: true,
|
|
12793
12793
|
unsupported_type: true,
|
|
12794
12794
|
no_change: true,
|
|
12795
|
+
quality_rejected: true,
|
|
12795
12796
|
aborted: true
|
|
12796
12797
|
};
|
|
12797
12798
|
PROGRAM_RETRY_REASONS = Object.keys(RETRY_REASON_SET);
|
|
@@ -76098,6 +76099,7 @@ var IMPROVE_PROCESS_BASE_FIELDS = {
|
|
|
76098
76099
|
timeoutMs: exports_external.union([positiveInt, exports_external.null()]).optional()
|
|
76099
76100
|
};
|
|
76100
76101
|
var allowedTypesField = exports_external.array(exports_external.string().min(1)).optional();
|
|
76102
|
+
var excludeRefPrefixesField = exports_external.array(exports_external.string().min(1)).optional();
|
|
76101
76103
|
var processLimitField = positiveInt.optional();
|
|
76102
76104
|
var qualityGateField = exports_external.object({ enabled: exports_external.boolean().optional() }).passthrough().optional();
|
|
76103
76105
|
var contradictionDetectionField = exports_external.object({ enabled: exports_external.boolean().optional() }).passthrough().optional();
|
|
@@ -76138,6 +76140,7 @@ var antiCollapseField = exports_external.object({
|
|
|
76138
76140
|
}).passthrough().optional();
|
|
76139
76141
|
var REFLECT_PROCESS_FIELDS = {
|
|
76140
76142
|
allowedTypes: allowedTypesField,
|
|
76143
|
+
excludeRefPrefixes: excludeRefPrefixesField,
|
|
76141
76144
|
limit: processLimitField,
|
|
76142
76145
|
qualityGate: qualityGateField,
|
|
76143
76146
|
lowValueFilter: lowValueFilterField
|
|
@@ -76169,7 +76172,8 @@ var GRAPH_EXTRACTION_PROCESS_FIELDS = {
|
|
|
76169
76172
|
topN: positiveInt.optional(),
|
|
76170
76173
|
includeTypes: exports_external.array(exports_external.string().min(1)).min(1).optional(),
|
|
76171
76174
|
batchSize: positiveInt.optional(),
|
|
76172
|
-
fullScan: exports_external.boolean().optional()
|
|
76175
|
+
fullScan: exports_external.boolean().optional(),
|
|
76176
|
+
maxChunksPerAsset: positiveInt.optional()
|
|
76173
76177
|
};
|
|
76174
76178
|
var EXTRACT_PROCESS_FIELDS = {
|
|
76175
76179
|
defaultSince: exports_external.string().min(1).optional(),
|
|
@@ -76220,6 +76224,15 @@ function checkRetiredProcessKeys(value, ctx) {
|
|
|
76220
76224
|
}
|
|
76221
76225
|
}
|
|
76222
76226
|
}
|
|
76227
|
+
function rejectExcludeRefPrefixesOutsideReflect(value, ctx) {
|
|
76228
|
+
if ("excludeRefPrefixes" in value) {
|
|
76229
|
+
ctx.addIssue({
|
|
76230
|
+
code: exports_external.ZodIssueCode.custom,
|
|
76231
|
+
path: ["excludeRefPrefixes"],
|
|
76232
|
+
message: "excludeRefPrefixes is only valid on the reflect process"
|
|
76233
|
+
});
|
|
76234
|
+
}
|
|
76235
|
+
}
|
|
76223
76236
|
var ImproveProcessConfigSchema = exports_external.object({
|
|
76224
76237
|
...IMPROVE_PROCESS_BASE_FIELDS,
|
|
76225
76238
|
...REFLECT_PROCESS_FIELDS,
|
|
@@ -76232,8 +76245,8 @@ var ImproveProcessConfigSchema = exports_external.object({
|
|
|
76232
76245
|
...PROACTIVE_MAINTENANCE_PROCESS_FIELDS
|
|
76233
76246
|
}).passthrough().superRefine(checkRetiredProcessKeys);
|
|
76234
76247
|
var ReflectProcessConfigSchema = exports_external.object({ ...IMPROVE_PROCESS_BASE_FIELDS, ...REFLECT_PROCESS_FIELDS }).passthrough().superRefine(checkRetiredProcessKeys);
|
|
76235
|
-
var DistillProcessConfigSchema = exports_external.object({ ...IMPROVE_PROCESS_BASE_FIELDS, ...DISTILL_PROCESS_FIELDS }).passthrough().superRefine(checkRetiredProcessKeys);
|
|
76236
|
-
var ConsolidateProcessConfigSchema = exports_external.object({ ...IMPROVE_PROCESS_BASE_FIELDS, ...CONSOLIDATE_PROCESS_FIELDS }).passthrough().superRefine(checkRetiredProcessKeys);
|
|
76248
|
+
var DistillProcessConfigSchema = exports_external.object({ ...IMPROVE_PROCESS_BASE_FIELDS, ...DISTILL_PROCESS_FIELDS }).passthrough().superRefine(checkRetiredProcessKeys).superRefine(rejectExcludeRefPrefixesOutsideReflect);
|
|
76249
|
+
var ConsolidateProcessConfigSchema = exports_external.object({ ...IMPROVE_PROCESS_BASE_FIELDS, ...CONSOLIDATE_PROCESS_FIELDS }).passthrough().superRefine(checkRetiredProcessKeys).superRefine(rejectExcludeRefPrefixesOutsideReflect);
|
|
76237
76250
|
var MemoryInferenceProcessConfigSchema = exports_external.object({ ...IMPROVE_PROCESS_BASE_FIELDS, ...MEMORY_INFERENCE_PROCESS_FIELDS }).passthrough().superRefine(checkRetiredProcessKeys);
|
|
76238
76251
|
var GraphExtractionProcessConfigSchema = exports_external.object({ ...IMPROVE_PROCESS_BASE_FIELDS, ...GRAPH_EXTRACTION_PROCESS_FIELDS }).passthrough().superRefine(checkRetiredProcessKeys);
|
|
76239
76252
|
var ExtractProcessConfigSchema = exports_external.object({ ...IMPROVE_PROCESS_BASE_FIELDS, ...EXTRACT_PROCESS_FIELDS }).passthrough().superRefine(checkRetiredProcessKeys);
|
|
@@ -97986,7 +97999,7 @@ var GATE_OUTCOMES = {
|
|
|
97986
97999
|
"auto-rejected": true
|
|
97987
98000
|
};
|
|
97988
98001
|
function validatePresentMetadata(meta) {
|
|
97989
|
-
const stringFields = ["sourceRun", "beforeHash", "backupContent"];
|
|
98002
|
+
const stringFields = ["sourceRun", "beforeHash", "beforeHashNormalized", "backupContent"];
|
|
97990
98003
|
for (const field of stringFields) {
|
|
97991
98004
|
if (Object.hasOwn(meta, field) && typeof meta[field] !== "string")
|
|
97992
98005
|
invalidPresentField(field);
|
|
@@ -98066,6 +98079,7 @@ function proposalRowToProposal(row) {
|
|
|
98066
98079
|
changes,
|
|
98067
98080
|
...proposedTarget !== undefined ? { proposedTarget } : {},
|
|
98068
98081
|
...typeof meta.beforeHash === "string" ? { beforeHash: meta.beforeHash } : {},
|
|
98082
|
+
...typeof meta.beforeHashNormalized === "string" ? { beforeHashNormalized: meta.beforeHashNormalized } : {},
|
|
98069
98083
|
...meta.review !== undefined ? { review: meta.review } : {},
|
|
98070
98084
|
...typeof meta.confidence === "number" ? { confidence: meta.confidence } : {},
|
|
98071
98085
|
...meta.gateDecision !== undefined ? { gateDecision: meta.gateDecision } : {},
|
|
@@ -98092,6 +98106,8 @@ function proposalToRowValues(proposal, stashDir) {
|
|
|
98092
98106
|
metaObj.proposedTarget = currentProposalTarget(proposal.proposedTarget);
|
|
98093
98107
|
if (proposal.beforeHash !== undefined)
|
|
98094
98108
|
metaObj.beforeHash = proposal.beforeHash;
|
|
98109
|
+
if (proposal.beforeHashNormalized !== undefined)
|
|
98110
|
+
metaObj.beforeHashNormalized = proposal.beforeHashNormalized;
|
|
98095
98111
|
if (proposal.sourceRun !== undefined)
|
|
98096
98112
|
metaObj.sourceRun = proposal.sourceRun;
|
|
98097
98113
|
if (proposal.review !== undefined)
|
|
@@ -12791,6 +12791,7 @@ var init_schema = __esm(() => {
|
|
|
12791
12791
|
content_policy_reject: true,
|
|
12792
12792
|
unsupported_type: true,
|
|
12793
12793
|
no_change: true,
|
|
12794
|
+
quality_rejected: true,
|
|
12794
12795
|
aborted: true
|
|
12795
12796
|
};
|
|
12796
12797
|
PROGRAM_RETRY_REASONS = Object.keys(RETRY_REASON_SET);
|
|
@@ -75426,6 +75427,7 @@ var IMPROVE_PROCESS_BASE_FIELDS = {
|
|
|
75426
75427
|
timeoutMs: exports_external.union([positiveInt, exports_external.null()]).optional()
|
|
75427
75428
|
};
|
|
75428
75429
|
var allowedTypesField = exports_external.array(exports_external.string().min(1)).optional();
|
|
75430
|
+
var excludeRefPrefixesField = exports_external.array(exports_external.string().min(1)).optional();
|
|
75429
75431
|
var processLimitField = positiveInt.optional();
|
|
75430
75432
|
var qualityGateField = exports_external.object({ enabled: exports_external.boolean().optional() }).passthrough().optional();
|
|
75431
75433
|
var contradictionDetectionField = exports_external.object({ enabled: exports_external.boolean().optional() }).passthrough().optional();
|
|
@@ -75466,6 +75468,7 @@ var antiCollapseField = exports_external.object({
|
|
|
75466
75468
|
}).passthrough().optional();
|
|
75467
75469
|
var REFLECT_PROCESS_FIELDS = {
|
|
75468
75470
|
allowedTypes: allowedTypesField,
|
|
75471
|
+
excludeRefPrefixes: excludeRefPrefixesField,
|
|
75469
75472
|
limit: processLimitField,
|
|
75470
75473
|
qualityGate: qualityGateField,
|
|
75471
75474
|
lowValueFilter: lowValueFilterField
|
|
@@ -75497,7 +75500,8 @@ var GRAPH_EXTRACTION_PROCESS_FIELDS = {
|
|
|
75497
75500
|
topN: positiveInt.optional(),
|
|
75498
75501
|
includeTypes: exports_external.array(exports_external.string().min(1)).min(1).optional(),
|
|
75499
75502
|
batchSize: positiveInt.optional(),
|
|
75500
|
-
fullScan: exports_external.boolean().optional()
|
|
75503
|
+
fullScan: exports_external.boolean().optional(),
|
|
75504
|
+
maxChunksPerAsset: positiveInt.optional()
|
|
75501
75505
|
};
|
|
75502
75506
|
var EXTRACT_PROCESS_FIELDS = {
|
|
75503
75507
|
defaultSince: exports_external.string().min(1).optional(),
|
|
@@ -75548,6 +75552,15 @@ function checkRetiredProcessKeys(value, ctx) {
|
|
|
75548
75552
|
}
|
|
75549
75553
|
}
|
|
75550
75554
|
}
|
|
75555
|
+
function rejectExcludeRefPrefixesOutsideReflect(value, ctx) {
|
|
75556
|
+
if ("excludeRefPrefixes" in value) {
|
|
75557
|
+
ctx.addIssue({
|
|
75558
|
+
code: exports_external.ZodIssueCode.custom,
|
|
75559
|
+
path: ["excludeRefPrefixes"],
|
|
75560
|
+
message: "excludeRefPrefixes is only valid on the reflect process"
|
|
75561
|
+
});
|
|
75562
|
+
}
|
|
75563
|
+
}
|
|
75551
75564
|
var ImproveProcessConfigSchema = exports_external.object({
|
|
75552
75565
|
...IMPROVE_PROCESS_BASE_FIELDS,
|
|
75553
75566
|
...REFLECT_PROCESS_FIELDS,
|
|
@@ -75560,8 +75573,8 @@ var ImproveProcessConfigSchema = exports_external.object({
|
|
|
75560
75573
|
...PROACTIVE_MAINTENANCE_PROCESS_FIELDS
|
|
75561
75574
|
}).passthrough().superRefine(checkRetiredProcessKeys);
|
|
75562
75575
|
var ReflectProcessConfigSchema = exports_external.object({ ...IMPROVE_PROCESS_BASE_FIELDS, ...REFLECT_PROCESS_FIELDS }).passthrough().superRefine(checkRetiredProcessKeys);
|
|
75563
|
-
var DistillProcessConfigSchema = exports_external.object({ ...IMPROVE_PROCESS_BASE_FIELDS, ...DISTILL_PROCESS_FIELDS }).passthrough().superRefine(checkRetiredProcessKeys);
|
|
75564
|
-
var ConsolidateProcessConfigSchema = exports_external.object({ ...IMPROVE_PROCESS_BASE_FIELDS, ...CONSOLIDATE_PROCESS_FIELDS }).passthrough().superRefine(checkRetiredProcessKeys);
|
|
75576
|
+
var DistillProcessConfigSchema = exports_external.object({ ...IMPROVE_PROCESS_BASE_FIELDS, ...DISTILL_PROCESS_FIELDS }).passthrough().superRefine(checkRetiredProcessKeys).superRefine(rejectExcludeRefPrefixesOutsideReflect);
|
|
75577
|
+
var ConsolidateProcessConfigSchema = exports_external.object({ ...IMPROVE_PROCESS_BASE_FIELDS, ...CONSOLIDATE_PROCESS_FIELDS }).passthrough().superRefine(checkRetiredProcessKeys).superRefine(rejectExcludeRefPrefixesOutsideReflect);
|
|
75565
75578
|
var MemoryInferenceProcessConfigSchema = exports_external.object({ ...IMPROVE_PROCESS_BASE_FIELDS, ...MEMORY_INFERENCE_PROCESS_FIELDS }).passthrough().superRefine(checkRetiredProcessKeys);
|
|
75566
75579
|
var GraphExtractionProcessConfigSchema = exports_external.object({ ...IMPROVE_PROCESS_BASE_FIELDS, ...GRAPH_EXTRACTION_PROCESS_FIELDS }).passthrough().superRefine(checkRetiredProcessKeys);
|
|
75567
75580
|
var ExtractProcessConfigSchema = exports_external.object({ ...IMPROVE_PROCESS_BASE_FIELDS, ...EXTRACT_PROCESS_FIELDS }).passthrough().superRefine(checkRetiredProcessKeys);
|
|
@@ -97942,7 +97955,7 @@ var GATE_OUTCOMES = {
|
|
|
97942
97955
|
"auto-rejected": true
|
|
97943
97956
|
};
|
|
97944
97957
|
function validatePresentMetadata(meta) {
|
|
97945
|
-
const stringFields = ["sourceRun", "beforeHash", "backupContent"];
|
|
97958
|
+
const stringFields = ["sourceRun", "beforeHash", "beforeHashNormalized", "backupContent"];
|
|
97946
97959
|
for (const field of stringFields) {
|
|
97947
97960
|
if (Object.hasOwn(meta, field) && typeof meta[field] !== "string")
|
|
97948
97961
|
invalidPresentField(field);
|
|
@@ -98022,6 +98035,7 @@ function proposalRowToProposal(row) {
|
|
|
98022
98035
|
changes,
|
|
98023
98036
|
...proposedTarget !== undefined ? { proposedTarget } : {},
|
|
98024
98037
|
...typeof meta.beforeHash === "string" ? { beforeHash: meta.beforeHash } : {},
|
|
98038
|
+
...typeof meta.beforeHashNormalized === "string" ? { beforeHashNormalized: meta.beforeHashNormalized } : {},
|
|
98025
98039
|
...meta.review !== undefined ? { review: meta.review } : {},
|
|
98026
98040
|
...typeof meta.confidence === "number" ? { confidence: meta.confidence } : {},
|
|
98027
98041
|
...meta.gateDecision !== undefined ? { gateDecision: meta.gateDecision } : {},
|
|
@@ -98048,6 +98062,8 @@ function proposalToRowValues(proposal, stashDir) {
|
|
|
98048
98062
|
metaObj.proposedTarget = currentProposalTarget(proposal.proposedTarget);
|
|
98049
98063
|
if (proposal.beforeHash !== undefined)
|
|
98050
98064
|
metaObj.beforeHash = proposal.beforeHash;
|
|
98065
|
+
if (proposal.beforeHashNormalized !== undefined)
|
|
98066
|
+
metaObj.beforeHashNormalized = proposal.beforeHashNormalized;
|
|
98051
98067
|
if (proposal.sourceRun !== undefined)
|
|
98052
98068
|
metaObj.sourceRun = proposal.sourceRun;
|
|
98053
98069
|
if (proposal.review !== undefined)
|
|
@@ -684,6 +684,22 @@ export function getIndexedFilePaths(db) {
|
|
|
684
684
|
.all();
|
|
685
685
|
return new Set(rows.map((r) => r.file_path));
|
|
686
686
|
}
|
|
687
|
+
/**
|
|
688
|
+
* `entries.file_path -> content_hash` for every currently-indexed file that
|
|
689
|
+
* carries a stored hash. Used by per-file staleness detection
|
|
690
|
+
* (`hasNewerIndexableFiles`, R6) to tell "edited after the last build" apart
|
|
691
|
+
* from "already re-indexed with this exact content after the last build" —
|
|
692
|
+
* `indexWrittenAssets` upserts a fresh row (and its `content_hash`) without
|
|
693
|
+
* bumping `builtAt`, so mtime alone cannot distinguish the two cases.
|
|
694
|
+
*/
|
|
695
|
+
export function getIndexedFileHashes(db) {
|
|
696
|
+
const rows = db
|
|
697
|
+
.prepare(`SELECT file_path, content_hash FROM entries
|
|
698
|
+
WHERE file_path IS NOT NULL AND file_path <> ''
|
|
699
|
+
AND content_hash IS NOT NULL`)
|
|
700
|
+
.all();
|
|
701
|
+
return new Map(rows.map((r) => [r.file_path, r.content_hash]));
|
|
702
|
+
}
|
|
687
703
|
/**
|
|
688
704
|
* Resolve a single `entries.file_path` by primary key, or `undefined` if no
|
|
689
705
|
* row matches.
|
|
@@ -732,6 +748,33 @@ export function getEntryByRef(db, ref) {
|
|
|
732
748
|
const id = findEntryIdByRef(db, ref);
|
|
733
749
|
return id === undefined ? null : { id };
|
|
734
750
|
}
|
|
751
|
+
/** Build a {@link LiveRefSnapshot} from the current `entries` table. */
|
|
752
|
+
export function getLiveRefSnapshot(db) {
|
|
753
|
+
const rows = db.prepare("SELECT item_ref FROM entries").all();
|
|
754
|
+
const itemRefs = new Set();
|
|
755
|
+
const conceptIds = new Set();
|
|
756
|
+
for (const { item_ref } of rows) {
|
|
757
|
+
itemRefs.add(item_ref);
|
|
758
|
+
const boundary = item_ref.indexOf("//");
|
|
759
|
+
if (boundary >= 0)
|
|
760
|
+
conceptIds.add(item_ref.slice(boundary + 2));
|
|
761
|
+
}
|
|
762
|
+
return { itemRefs, conceptIds };
|
|
763
|
+
}
|
|
764
|
+
/**
|
|
765
|
+
* `getEntryByRef`'s liveness check (bundle-qualified exact match, or bare
|
|
766
|
+
* `//conceptId` suffix match across bundles, each tried with the `.md`
|
|
767
|
+
* toggle {@link withMdVariants} applies) against a prebuilt
|
|
768
|
+
* {@link LiveRefSnapshot} instead of the database — no query per call.
|
|
769
|
+
*/
|
|
770
|
+
export function isRefLiveInSnapshot(snapshot, ref) {
|
|
771
|
+
const parsed = parseBundleRef(ref);
|
|
772
|
+
const conceptVariants = withMdVariants(parsed.conceptId);
|
|
773
|
+
if (parsed.bundle !== undefined) {
|
|
774
|
+
return conceptVariants.some((conceptId) => snapshot.itemRefs.has(`${parsed.bundle}//${conceptId}`));
|
|
775
|
+
}
|
|
776
|
+
return conceptVariants.some((conceptId) => snapshot.conceptIds.has(conceptId));
|
|
777
|
+
}
|
|
735
778
|
/**
|
|
736
779
|
* The fully-qualified `item_ref` (`<bundle>//<conceptId>`, the durable stored
|
|
737
780
|
* spelling — spec §11.1 D-R3) for an entry `id`, or `null` when the row is gone
|
|
@@ -107,7 +107,7 @@ const GATE_OUTCOMES = {
|
|
|
107
107
|
"auto-rejected": true,
|
|
108
108
|
};
|
|
109
109
|
function validatePresentMetadata(meta) {
|
|
110
|
-
const stringFields = ["sourceRun", "beforeHash", "backupContent"];
|
|
110
|
+
const stringFields = ["sourceRun", "beforeHash", "beforeHashNormalized", "backupContent"];
|
|
111
111
|
for (const field of stringFields) {
|
|
112
112
|
if (Object.hasOwn(meta, field) && typeof meta[field] !== "string")
|
|
113
113
|
invalidPresentField(field);
|
|
@@ -224,6 +224,7 @@ export function proposalRowToProposal(row) {
|
|
|
224
224
|
changes,
|
|
225
225
|
...(proposedTarget !== undefined ? { proposedTarget } : {}),
|
|
226
226
|
...(typeof meta.beforeHash === "string" ? { beforeHash: meta.beforeHash } : {}),
|
|
227
|
+
...(typeof meta.beforeHashNormalized === "string" ? { beforeHashNormalized: meta.beforeHashNormalized } : {}),
|
|
227
228
|
...(meta.review !== undefined ? { review: meta.review } : {}),
|
|
228
229
|
...(typeof meta.confidence === "number" ? { confidence: meta.confidence } : {}),
|
|
229
230
|
...(meta.gateDecision !== undefined ? { gateDecision: meta.gateDecision } : {}),
|
|
@@ -274,6 +275,8 @@ export function proposalToRowValues(proposal, stashDir) {
|
|
|
274
275
|
metaObj.proposedTarget = currentProposalTarget(proposal.proposedTarget);
|
|
275
276
|
if (proposal.beforeHash !== undefined)
|
|
276
277
|
metaObj.beforeHash = proposal.beforeHash;
|
|
278
|
+
if (proposal.beforeHashNormalized !== undefined)
|
|
279
|
+
metaObj.beforeHashNormalized = proposal.beforeHashNormalized;
|
|
277
280
|
if (proposal.sourceRun !== undefined)
|
|
278
281
|
metaObj.sourceRun = proposal.sourceRun;
|
|
279
282
|
if (proposal.review !== undefined)
|
|
@@ -0,0 +1,123 @@
|
|
|
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
|
+
* state.db integrity + reclaimable-space probes (R0, tier0-0917).
|
|
6
|
+
*
|
|
7
|
+
* `akm health`'s `state-db-integrity` check (src/commands/health/checks.ts)
|
|
8
|
+
* is a pure projection like every other check, so the actual IO lives here:
|
|
9
|
+
* a read-only `PRAGMA quick_check` and a read-only freelist/page-count read.
|
|
10
|
+
* Both open their own short-lived connection via the plain {@link openDatabase}
|
|
11
|
+
* opener — deliberately bypassing `openStateDatabase`'s managed-open/migration
|
|
12
|
+
* machinery (src/core/state-db.ts), since a corrupt database must not need a
|
|
13
|
+
* clean migration-ledger read just to report itself as corrupt.
|
|
14
|
+
*
|
|
15
|
+
* {@link vacuumStateDbIfReclaimable} is the post-purge VACUUM step: given an
|
|
16
|
+
* already-open read-write connection (VACUUM cannot run inside a transaction,
|
|
17
|
+
* and a read-only handle cannot run it at all) and a freelist reading, it
|
|
18
|
+
* VACUUMs only when the freelist ratio crosses {@link STATE_DB_FREELIST_WARN_RATIO}
|
|
19
|
+
* and never throws — a locked/busy database is reported, not raised.
|
|
20
|
+
*
|
|
21
|
+
* @module storage/state-db-integrity
|
|
22
|
+
*/
|
|
23
|
+
import { appendEvent } from "../core/events.js";
|
|
24
|
+
import { openDatabase } from "./database.js";
|
|
25
|
+
import { SQLITE_BUSY_TIMEOUT_MS } from "./sqlite-pragmas.js";
|
|
26
|
+
/** How many corruption errors `PRAGMA quick_check` collects before it stops scanning and returns. */
|
|
27
|
+
const QUICK_CHECK_ERROR_LIMIT = 10;
|
|
28
|
+
/** Above this fraction of free pages, `state-db-integrity` warns and a post-purge pass VACUUMs. */
|
|
29
|
+
export const STATE_DB_FREELIST_WARN_RATIO = 0.5;
|
|
30
|
+
/** Event appended by {@link vacuumStateDbIfReclaimable} after a successful VACUUM. */
|
|
31
|
+
export const STATE_DB_VACUUMED_EVENT = "state_db_vacuumed";
|
|
32
|
+
function firstColumn(row) {
|
|
33
|
+
return row === undefined ? undefined : Object.values(row)[0];
|
|
34
|
+
}
|
|
35
|
+
function openReadonlyStateDb(dbPath) {
|
|
36
|
+
const db = openDatabase(dbPath, { readonly: true, create: false });
|
|
37
|
+
// Read-only handles cannot run journal_mode/foreign_keys (write operations),
|
|
38
|
+
// but busy_timeout is legal — see openReadonlyExistingDatabase's identical
|
|
39
|
+
// rationale in src/storage/repositories/index-connection.ts.
|
|
40
|
+
db.exec(`PRAGMA busy_timeout = ${SQLITE_BUSY_TIMEOUT_MS}`);
|
|
41
|
+
return db;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Run `PRAGMA quick_check(N)` against `dbPath` read-only. Sub-second on a
|
|
45
|
+
* healthy multi-hundred-MB file; on a corrupt one, `N` bounds how many errors
|
|
46
|
+
* SQLite collects before it stops scanning, which keeps the check's runtime
|
|
47
|
+
* bounded even against a badly corrupt file.
|
|
48
|
+
*/
|
|
49
|
+
export function runStateDbQuickCheck(dbPath) {
|
|
50
|
+
let db;
|
|
51
|
+
try {
|
|
52
|
+
db = openReadonlyStateDb(dbPath);
|
|
53
|
+
const rows = db.prepare(`PRAGMA quick_check(${QUICK_CHECK_ERROR_LIMIT})`).all();
|
|
54
|
+
const lines = rows.map((row) => String(firstColumn(row)));
|
|
55
|
+
const ok = lines.length === 1 && lines[0] === "ok";
|
|
56
|
+
return { ok, lines };
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
return { ok: false, lines: [], error: err instanceof Error ? err.message : String(err) };
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
db?.close();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Read `PRAGMA freelist_count` / `PRAGMA page_count` off an already-open
|
|
67
|
+
* connection. Shared by {@link getStateDbFreelistInfo} (which opens its own
|
|
68
|
+
* read-only handle) and the post-purge VACUUM call site, which must read the
|
|
69
|
+
* freelist off the same read-write connection the purge just used rather
|
|
70
|
+
* than open a second one.
|
|
71
|
+
*/
|
|
72
|
+
export function readFreelistInfo(db) {
|
|
73
|
+
const freelistCount = Number(firstColumn(db.prepare("PRAGMA freelist_count").get()) ?? 0);
|
|
74
|
+
const pageCount = Number(firstColumn(db.prepare("PRAGMA page_count").get()) ?? 0);
|
|
75
|
+
return { freelistCount, pageCount, ratio: pageCount > 0 ? freelistCount / pageCount : 0 };
|
|
76
|
+
}
|
|
77
|
+
/** Read `PRAGMA freelist_count` / `PRAGMA page_count` — how much of state.db is reclaimable by VACUUM. */
|
|
78
|
+
export function getStateDbFreelistInfo(dbPath) {
|
|
79
|
+
let db;
|
|
80
|
+
try {
|
|
81
|
+
db = openReadonlyStateDb(dbPath);
|
|
82
|
+
return readFreelistInfo(db);
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
return { freelistCount: 0, pageCount: 0, ratio: 0, error: err instanceof Error ? err.message : String(err) };
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
db?.close();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* VACUUM `db` when `freelist.ratio` exceeds {@link STATE_DB_FREELIST_WARN_RATIO},
|
|
93
|
+
* appending a {@link STATE_DB_VACUUMED_EVENT} recording pages before/after.
|
|
94
|
+
* Intended to run immediately after the retention purge, on the same
|
|
95
|
+
* read-write connection the purge just used. Never throws: a locked/busy
|
|
96
|
+
* database (another writer holds the file right now) is reported via
|
|
97
|
+
* `reason: "busy"` rather than raised, since this is opportunistic
|
|
98
|
+
* maintenance and must not fail the purge pass it follows.
|
|
99
|
+
*
|
|
100
|
+
* The event is appended via `appendEvent` (not a direct `insertEvent` on
|
|
101
|
+
* `db`) so it honors the caller's `EventsContext` — `readOnly` suppresses
|
|
102
|
+
* the write and an injected `now` is used for `ts` — the same as every
|
|
103
|
+
* other event `runRetentionPurgePass` appends in this callback.
|
|
104
|
+
*/
|
|
105
|
+
export function vacuumStateDbIfReclaimable(db, freelist, eventsCtx) {
|
|
106
|
+
if (freelist.ratio <= STATE_DB_FREELIST_WARN_RATIO) {
|
|
107
|
+
return { ran: false, reason: "below-threshold", pagesBefore: freelist.pageCount };
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
db.exec("VACUUM");
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
114
|
+
const busy = /busy|locked/i.test(message);
|
|
115
|
+
return { ran: false, reason: busy ? "busy" : "error", pagesBefore: freelist.pageCount, error: message };
|
|
116
|
+
}
|
|
117
|
+
const pagesAfter = Number(firstColumn(db.prepare("PRAGMA page_count").get()) ?? 0);
|
|
118
|
+
appendEvent({
|
|
119
|
+
eventType: STATE_DB_VACUUMED_EVENT,
|
|
120
|
+
metadata: { pagesBefore: freelist.pageCount, pagesAfter, freelistRatioBefore: freelist.ratio },
|
|
121
|
+
}, eventsCtx);
|
|
122
|
+
return { ran: true, pagesBefore: freelist.pageCount, pagesAfter };
|
|
123
|
+
}
|
package/docs/reference/cli.md
CHANGED
|
@@ -387,7 +387,7 @@ Primary result fields:
|
|
|
387
387
|
| Field | Description |
|
|
388
388
|
| --- | --- |
|
|
389
389
|
| `status` | Overall health verdict: `pass`, `warn`, or `fail` |
|
|
390
|
-
| `hardChecks` | Deterministic checks such as `state-db-schema`, `state-db-round-trip`, `state-db-migrations`, `task-log-backing`, `active-runs`, `default-engine`, `model-map-files`, `default-llm-engine`, `configured-engines`, and `active-improve-strategy` |
|
|
390
|
+
| `hardChecks` | Deterministic checks such as `state-db-schema`, `state-db-round-trip`, `state-db-integrity`, `state-db-migrations`, `task-log-backing`, `active-runs`, `default-engine`, `model-map-files`, `default-llm-engine`, `configured-engines`, and `active-improve-strategy` |
|
|
391
391
|
| `advisories` | Non-fatal warnings including `semantic-search-runtime`, `session-extraction` (akmExtract pipeline health), `cli-version` (installed vs latest release), `thinking-control` (an `enableThinking: false` engine whose recorded usage still shows reasoning tokens), and `engine-last-used` (an engine bound to an enabled improve process with no recorded use in 30 days) |
|
|
392
392
|
| `metrics` | Aggregate task/runtime metrics: `taskFailRate`, `agentFailureRate`, `stuckActiveRuns`, `logBackingRate`, `probeRoundTripMs` |
|
|
393
393
|
| `improve` | Recent improve-loop counts derived from `improve_invoked`, `improve_skipped`, and `improve_completed` events |
|
|
@@ -2459,8 +2459,9 @@ configures a judgment engine. Each row carries `enabled`, the resolved
|
|
|
2459
2459
|
`engine`/`model` (llm-backed processes only) and `engineKind`, this process's
|
|
2460
2460
|
own lowering `notices`, and — for reflect/distill/consolidate only —
|
|
2461
2461
|
`eligibleRefs`, the count of this run's `effectiveRefs` the process would act
|
|
2462
|
-
on (`shouldSkipRef`'s allowedTypes/
|
|
2463
|
-
per-ref matrix, to keep the envelope
|
|
2462
|
+
on (`shouldSkipRef`'s allowedTypes/excludeRefPrefixes (reflect only)/
|
|
2463
|
+
process-disabled check; a count, not a per-ref matrix, to keep the envelope
|
|
2464
|
+
bounded). A row that could not resolve an
|
|
2464
2465
|
engine or credential carries `unavailable: {configKey, reason}` — the same
|
|
2465
2466
|
data behind `skippedProcesses` above, reshaped per process. When the process
|
|
2466
2467
|
resolved a real engine whose credential just isn't reachable here, the row
|
|
@@ -208,6 +208,7 @@ the set of types the code actually emits at HEAD (verified against every
|
|
|
208
208
|
| `events_purged` | Old events deleted by improve maintenance (90-day default retention) | `purgedCount`, `retentionDays` |
|
|
209
209
|
| `improve_runs_purged` | Old `improve_runs` rows deleted by improve maintenance (same retention window as events) | `purgedCount`, `retentionDays` |
|
|
210
210
|
| `improve_cycle_metrics_purged` | Old `improve_cycle_metrics` rows (365-day retention) deleted by improve maintenance | `purgedCount`, `retentionDays` |
|
|
211
|
+
| `state_db_vacuumed` | state.db was VACUUMed after the retention purge because more than half its pages were free | `pagesBefore`, `pagesAfter`, `freelistRatioBefore` |
|
|
211
212
|
| `task_logs_purged` | Old scheduled-task log files purged by improve maintenance | |
|
|
212
213
|
|
|
213
214
|
*Workflows*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "akm-cli",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.17-alpha.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "akm (Agent Knowledge Manager) — a portable, local-first capability library for AI agents. Discover, load, share, and improve reusable skills, scripts, workflows, and knowledge across any shell-capable coding agent, including Claude Code, OpenCode, and Cursor.",
|
|
6
6
|
"keywords": [
|
package/schemas/akm-config.json
CHANGED
|
@@ -796,6 +796,13 @@
|
|
|
796
796
|
"minLength": 1
|
|
797
797
|
}
|
|
798
798
|
},
|
|
799
|
+
"excludeRefPrefixes": {
|
|
800
|
+
"type": "array",
|
|
801
|
+
"items": {
|
|
802
|
+
"type": "string",
|
|
803
|
+
"minLength": 1
|
|
804
|
+
}
|
|
805
|
+
},
|
|
799
806
|
"limit": {
|
|
800
807
|
"type": "integer",
|
|
801
808
|
"exclusiveMinimum": 0
|
|
@@ -1207,6 +1214,10 @@
|
|
|
1207
1214
|
},
|
|
1208
1215
|
"fullScan": {
|
|
1209
1216
|
"type": "boolean"
|
|
1217
|
+
},
|
|
1218
|
+
"maxChunksPerAsset": {
|
|
1219
|
+
"type": "integer",
|
|
1220
|
+
"exclusiveMinimum": 0
|
|
1210
1221
|
}
|
|
1211
1222
|
},
|
|
1212
1223
|
"additionalProperties": true
|
|
@@ -2578,6 +2589,13 @@
|
|
|
2578
2589
|
"minLength": 1
|
|
2579
2590
|
}
|
|
2580
2591
|
},
|
|
2592
|
+
"excludeRefPrefixes": {
|
|
2593
|
+
"type": "array",
|
|
2594
|
+
"items": {
|
|
2595
|
+
"type": "string",
|
|
2596
|
+
"minLength": 1
|
|
2597
|
+
}
|
|
2598
|
+
},
|
|
2581
2599
|
"limit": {
|
|
2582
2600
|
"type": "integer",
|
|
2583
2601
|
"exclusiveMinimum": 0
|
|
@@ -2989,6 +3007,10 @@
|
|
|
2989
3007
|
},
|
|
2990
3008
|
"fullScan": {
|
|
2991
3009
|
"type": "boolean"
|
|
3010
|
+
},
|
|
3011
|
+
"maxChunksPerAsset": {
|
|
3012
|
+
"type": "integer",
|
|
3013
|
+
"exclusiveMinimum": 0
|
|
2992
3014
|
}
|
|
2993
3015
|
},
|
|
2994
3016
|
"additionalProperties": true
|
|
@@ -3755,6 +3777,13 @@
|
|
|
3755
3777
|
"minLength": 1
|
|
3756
3778
|
}
|
|
3757
3779
|
},
|
|
3780
|
+
"excludeRefPrefixes": {
|
|
3781
|
+
"type": "array",
|
|
3782
|
+
"items": {
|
|
3783
|
+
"type": "string",
|
|
3784
|
+
"minLength": 1
|
|
3785
|
+
}
|
|
3786
|
+
},
|
|
3758
3787
|
"limit": {
|
|
3759
3788
|
"type": "integer",
|
|
3760
3789
|
"exclusiveMinimum": 0
|
|
@@ -3883,6 +3912,10 @@
|
|
|
3883
3912
|
"fullScan": {
|
|
3884
3913
|
"type": "boolean"
|
|
3885
3914
|
},
|
|
3915
|
+
"maxChunksPerAsset": {
|
|
3916
|
+
"type": "integer",
|
|
3917
|
+
"exclusiveMinimum": 0
|
|
3918
|
+
},
|
|
3886
3919
|
"defaultSince": {
|
|
3887
3920
|
"type": "string",
|
|
3888
3921
|
"minLength": 1
|
|
@@ -4148,6 +4181,13 @@
|
|
|
4148
4181
|
"minLength": 1
|
|
4149
4182
|
}
|
|
4150
4183
|
},
|
|
4184
|
+
"excludeRefPrefixes": {
|
|
4185
|
+
"type": "array",
|
|
4186
|
+
"items": {
|
|
4187
|
+
"type": "string",
|
|
4188
|
+
"minLength": 1
|
|
4189
|
+
}
|
|
4190
|
+
},
|
|
4151
4191
|
"limit": {
|
|
4152
4192
|
"type": "integer",
|
|
4153
4193
|
"exclusiveMinimum": 0
|
|
@@ -4559,6 +4599,10 @@
|
|
|
4559
4599
|
},
|
|
4560
4600
|
"fullScan": {
|
|
4561
4601
|
"type": "boolean"
|
|
4602
|
+
},
|
|
4603
|
+
"maxChunksPerAsset": {
|
|
4604
|
+
"type": "integer",
|
|
4605
|
+
"exclusiveMinimum": 0
|
|
4562
4606
|
}
|
|
4563
4607
|
},
|
|
4564
4608
|
"additionalProperties": true
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
-
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
-
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
-
import fs from "node:fs";
|
|
5
|
-
import path from "node:path";
|
|
6
|
-
import { writeFileAtomic } from "../../core/common.js";
|
|
7
|
-
import { getEvalCasesDir } from "../../core/paths.js";
|
|
8
|
-
import { recordWrittenPath } from "../../core/write-provenance.js";
|
|
9
|
-
export function writeEvalCase(stashDir, evalCase) {
|
|
10
|
-
const evalDir = getEvalCasesDir(stashDir);
|
|
11
|
-
fs.mkdirSync(evalDir, { recursive: true });
|
|
12
|
-
const fileName = `${evalCase.slug}.md`;
|
|
13
|
-
const filePath = path.join(evalDir, fileName);
|
|
14
|
-
const content = `---
|
|
15
|
-
ref: ${evalCase.ref}
|
|
16
|
-
failureReason: ${evalCase.failureReason}
|
|
17
|
-
assetType: ${evalCase.assetType}
|
|
18
|
-
rejectedAt: ${evalCase.rejectedAt}
|
|
19
|
-
source: ${evalCase.source}
|
|
20
|
-
---
|
|
21
|
-
|
|
22
|
-
# Eval Case: ${evalCase.ref}
|
|
23
|
-
|
|
24
|
-
**Failure reason:** ${evalCase.failureReason}
|
|
25
|
-
**Source:** ${evalCase.source}
|
|
26
|
-
**Asset type:** ${evalCase.assetType}
|
|
27
|
-
|
|
28
|
-
This case was automatically captured when a distillation or proposal was rejected.
|
|
29
|
-
Use it as a regression test: future improve runs on this ref should not produce
|
|
30
|
-
output that would be rejected for the same reason.
|
|
31
|
-
`;
|
|
32
|
-
writeFileAtomic(filePath, content);
|
|
33
|
-
// itlackey/akm#890: journal it even though it now lands under `$STATE`,
|
|
34
|
-
// outside the stash's git repo — `result.writtenPaths` still reports it
|
|
35
|
-
// (as an absolute path; see describeRunWrittenPaths in improve.ts), and
|
|
36
|
-
// the auto-sync commit's own containment check already excludes anything
|
|
37
|
-
// outside the stash from what gets staged, so recording it here cannot
|
|
38
|
-
// cause it to be committed.
|
|
39
|
-
recordWrittenPath(filePath);
|
|
40
|
-
return filePath;
|
|
41
|
-
}
|
|
42
|
-
export function countEvalCases(stashDir) {
|
|
43
|
-
const evalDir = getEvalCasesDir(stashDir);
|
|
44
|
-
if (!fs.existsSync(evalDir))
|
|
45
|
-
return 0;
|
|
46
|
-
try {
|
|
47
|
-
return fs.readdirSync(evalDir).filter((f) => f.endsWith(".md")).length;
|
|
48
|
-
}
|
|
49
|
-
catch {
|
|
50
|
-
return 0;
|
|
51
|
-
}
|
|
52
|
-
}
|