akm-cli 0.9.0-rc.2 → 0.9.0-rc.3
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 +34 -8
- package/dist/assets/improve-strategies/catchup.json +3 -1
- package/dist/assets/improve-strategies/consolidate.json +3 -1
- package/dist/assets/improve-strategies/frequent.json +3 -1
- package/dist/assets/improve-strategies/graph-refresh.json +3 -1
- package/dist/assets/improve-strategies/memory-focus.json +4 -1
- package/dist/assets/improve-strategies/proactive-maintenance.json +1 -0
- package/dist/assets/improve-strategies/quick.json +3 -1
- package/dist/assets/improve-strategies/recombine-only.json +2 -0
- package/dist/assets/improve-strategies/reflect-distill.json +1 -0
- package/dist/assets/improve-strategies/synthesize.json +2 -0
- package/dist/assets/tasks/core/backup.yml +2 -2
- package/dist/cli/config-migrate.js +554 -26
- package/dist/cli.js +3 -1
- package/dist/commands/backup-cli.js +6 -4
- package/dist/commands/config-cli.js +10 -3
- package/dist/commands/feedback-cli.js +24 -7
- package/dist/commands/health/checks.js +94 -15
- package/dist/commands/health/surfaces.js +2 -1
- package/dist/commands/improve/anti-collapse.js +3 -3
- package/dist/commands/improve/collapse-detector.js +5 -5
- package/dist/commands/improve/consolidate.js +123 -66
- package/dist/commands/improve/distill/promote-memory.js +8 -6
- package/dist/commands/improve/distill/quality-gate.js +1 -1
- package/dist/commands/improve/distill-guards.js +1 -1
- package/dist/commands/improve/distill-promotion-policy.js +32 -26
- package/dist/commands/improve/distill.js +52 -36
- package/dist/commands/improve/eligibility.js +9 -8
- package/dist/commands/improve/extract-prompt.js +2 -2
- package/dist/commands/improve/extract.js +43 -11
- package/dist/commands/improve/improve-auto-accept.js +14 -28
- package/dist/commands/improve/improve-cli.js +4 -2
- package/dist/commands/improve/improve-strategies.js +2 -1
- package/dist/commands/improve/improve.js +49 -8
- package/dist/commands/improve/loop-stages.js +11 -6
- package/dist/commands/improve/preparation.js +51 -17
- package/dist/commands/improve/procedural.js +10 -6
- package/dist/commands/improve/recombine.js +31 -7
- package/dist/commands/improve/reflect.js +24 -14
- package/dist/commands/improve/salience.js +5 -1
- package/dist/commands/improve/source-identity.js +43 -0
- package/dist/commands/migrate-cli.js +36 -0
- package/dist/commands/mv-cli.js +647 -258
- package/dist/commands/proposal/drain.js +16 -4
- package/dist/commands/proposal/proposal-cli.js +3 -2
- package/dist/commands/proposal/proposal.js +16 -55
- package/dist/commands/proposal/repository.js +664 -58
- package/dist/commands/read/curate.js +4 -2
- package/dist/commands/read/knowledge.js +4 -1
- package/dist/commands/read/search.js +6 -2
- package/dist/commands/read/show.js +8 -7
- package/dist/commands/sources/self-update.js +156 -112
- package/dist/commands/sources/sources-cli.js +7 -2
- package/dist/core/common.js +36 -3
- package/dist/core/config/config-io.js +2 -2
- package/dist/core/config/config-schema.js +15 -3
- package/dist/core/config/config.js +14 -13
- package/dist/core/file-lock.js +2 -2
- package/dist/core/migration-backup.js +816 -171
- package/dist/core/migration-operation.js +44 -0
- package/dist/core/state/migrations.js +4 -7
- package/dist/core/state-db.js +22 -7
- package/dist/core/write-source.js +86 -18
- package/dist/indexer/db/db.js +109 -53
- package/dist/indexer/index-writer-lock.js +38 -37
- package/dist/indexer/index-written-assets.js +73 -56
- package/dist/indexer/indexer.js +5 -1
- package/dist/indexer/search/search-source.js +2 -2
- package/dist/indexer/usage/usage-events.js +8 -2
- package/dist/integrations/agent/engine-resolution.js +14 -7
- package/dist/scripts/migrate-storage.js +867 -990
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +405 -566
- package/dist/setup/detected-engines.js +7 -13
- package/dist/setup/engine-config.js +14 -4
- package/dist/setup/setup.js +1 -1
- package/dist/setup/steps/connection.js +1 -1
- package/dist/setup/steps/tasks.js +1 -1
- package/dist/sources/providers/git-stash.js +58 -21
- package/dist/sources/providers/git.js +1 -1
- package/dist/storage/engines/sqlite-migrations.js +138 -3
- package/dist/storage/repositories/events-repository.js +24 -0
- package/dist/storage/repositories/proposals-repository.js +14 -0
- package/dist/tasks/embedded.js +2 -0
- package/dist/workflows/db.js +24 -13
- package/dist/workflows/validator.js +24 -2
- package/docs/migration/release-notes/0.9.0.md +24 -7
- package/docs/migration/v0.8-to-v0.9.md +124 -19
- package/package.json +1 -1
|
@@ -44,7 +44,13 @@ function deriveDescription(body, description) {
|
|
|
44
44
|
}
|
|
45
45
|
export function deriveKnowledgeRef(inputRef) {
|
|
46
46
|
const parsed = parseAssetRef(inputRef);
|
|
47
|
-
|
|
47
|
+
const leaf = parsed.name.split("/").filter(Boolean).at(-1) ?? parsed.name;
|
|
48
|
+
const safe = leaf
|
|
49
|
+
.toLowerCase()
|
|
50
|
+
.replace(/[^a-z0-9-]+/g, "-")
|
|
51
|
+
.replace(/-+/g, "-")
|
|
52
|
+
.replace(/^-|-$/g, "");
|
|
53
|
+
return `knowledge:${safe}`;
|
|
48
54
|
}
|
|
49
55
|
function collectPromotionFeatures(input) {
|
|
50
56
|
const parsed = parseAssetRef(input.inputRef);
|
|
@@ -112,14 +118,14 @@ function buildKnowledgeContent(input) {
|
|
|
112
118
|
const body = parsedContent.content.trim();
|
|
113
119
|
const description = typeof fm.description === "string" ? fm.description.trim() : undefined;
|
|
114
120
|
const normalizedDescription = deriveDescription(body, description);
|
|
115
|
-
const
|
|
121
|
+
const xrefs = [input.inputRef];
|
|
116
122
|
if (typeof fm.source === "string" && fm.source.trim())
|
|
117
|
-
|
|
123
|
+
xrefs.push(fm.source.trim());
|
|
118
124
|
const knowledgeFrontmatter = {
|
|
119
125
|
...(normalizedDescription ? { description: normalizedDescription } : {}),
|
|
120
126
|
...(Array.isArray(fm.tags) ? { tags: fm.tags } : {}),
|
|
121
127
|
...(typeof fm.observed_at === "string" && fm.observed_at.trim() ? { observed_at: fm.observed_at.trim() } : {}),
|
|
122
|
-
|
|
128
|
+
xrefs,
|
|
123
129
|
};
|
|
124
130
|
return assembleAsset(knowledgeFrontmatter, body);
|
|
125
131
|
}
|
|
@@ -517,7 +523,7 @@ export const DEFAULT_PROMOTION_POLICY_SELECTION = {
|
|
|
517
523
|
score: 4.250000000000001,
|
|
518
524
|
threshold: 3.8,
|
|
519
525
|
knowledgeRef: "knowledge:deploy-vpn-required",
|
|
520
|
-
content: "---\ndescription: VPN required before deploy\ntags:\n - deploy\n - ops\nobserved_at: 2026-04-20\
|
|
526
|
+
content: "---\ndescription: VPN required before deploy\ntags:\n - deploy\n - ops\nobserved_at: 2026-04-20\nxrefs:\n - memory:deploy-vpn-required\n - skill:deploy\n---\n\nAlways connect the VPN before starting production deploys.\n",
|
|
521
527
|
blockedBy: [],
|
|
522
528
|
positiveSignals: [
|
|
523
529
|
"2 positive feedback events",
|
|
@@ -543,7 +549,7 @@ export const DEFAULT_PROMOTION_POLICY_SELECTION = {
|
|
|
543
549
|
score: 5.240000000000002,
|
|
544
550
|
threshold: 3.8,
|
|
545
551
|
knowledgeRef: "knowledge:release-smoke-test",
|
|
546
|
-
content: "---\ndescription: Smoke test gates release\nobserved_at: 2026-04-18\
|
|
552
|
+
content: "---\ndescription: Smoke test gates release\nobserved_at: 2026-04-18\nxrefs:\n - memory:release-smoke-test\n - skill:release\n---\n\nRun the smoke test before tagging a release candidate.\n",
|
|
547
553
|
blockedBy: [],
|
|
548
554
|
positiveSignals: [
|
|
549
555
|
"3 positive feedback events",
|
|
@@ -569,7 +575,7 @@ export const DEFAULT_PROMOTION_POLICY_SELECTION = {
|
|
|
569
575
|
score: 4.250000000000001,
|
|
570
576
|
threshold: 3.8,
|
|
571
577
|
knowledgeRef: "knowledge:kubernetes-rollout-check",
|
|
572
|
-
content: "---\ndescription: Verify rollout status after apply\ntags:\n - k8s\nobserved_at: 2026-04-15\
|
|
578
|
+
content: "---\ndescription: Verify rollout status after apply\ntags:\n - k8s\nobserved_at: 2026-04-15\nxrefs:\n - memory:kubernetes-rollout-check\n - skill:k8s\n---\n\nCheck rollout status after kubectl apply before declaring success.\n",
|
|
573
579
|
blockedBy: [],
|
|
574
580
|
positiveSignals: [
|
|
575
581
|
"2 positive feedback events",
|
|
@@ -595,7 +601,7 @@ export const DEFAULT_PROMOTION_POLICY_SELECTION = {
|
|
|
595
601
|
score: 5.450000000000002,
|
|
596
602
|
threshold: 3.8,
|
|
597
603
|
knowledgeRef: "knowledge:incident-channel-rule",
|
|
598
|
-
content: "---\ndescription: Incident bridge stays single-threaded\nobserved_at: 2026-04-12\
|
|
604
|
+
content: "---\ndescription: Incident bridge stays single-threaded\nobserved_at: 2026-04-12\nxrefs:\n - memory:incident-channel-rule\n - skill:incident\n---\n\nKeep one operator narrating decisions in the incident bridge to avoid conflicting instructions.\n",
|
|
599
605
|
blockedBy: [],
|
|
600
606
|
positiveSignals: [
|
|
601
607
|
"3 positive feedback events",
|
|
@@ -710,7 +716,7 @@ export const DEFAULT_PROMOTION_POLICY_SELECTION = {
|
|
|
710
716
|
score: 4.1000000000000005,
|
|
711
717
|
threshold: 3.8,
|
|
712
718
|
knowledgeRef: "knowledge:feedback-conflict",
|
|
713
|
-
content: "---\ndescription: VPN required before deploy\nobserved_at: 2026-04-20\
|
|
719
|
+
content: "---\ndescription: VPN required before deploy\nobserved_at: 2026-04-20\nxrefs:\n - memory:feedback-conflict\n - skill:deploy\n---\n\nAlways connect the VPN before starting production deploys.\n",
|
|
714
720
|
blockedBy: [],
|
|
715
721
|
positiveSignals: [
|
|
716
722
|
"2 positive feedback events",
|
|
@@ -758,7 +764,7 @@ export const DEFAULT_PROMOTION_POLICY_SELECTION = {
|
|
|
758
764
|
score: 4.840000000000001,
|
|
759
765
|
threshold: 3.8,
|
|
760
766
|
knowledgeRef: "knowledge:staging-cutover-order",
|
|
761
|
-
content: "---\ndescription: Cut over staging after migrations\ntags:\n - db\n - deploy\nobserved_at: 2026-04-10\
|
|
767
|
+
content: "---\ndescription: Cut over staging after migrations\ntags:\n - db\n - deploy\nobserved_at: 2026-04-10\nxrefs:\n - memory:staging-cutover-order\n - skill:database\n---\n\nRun database migrations before shifting staging traffic onto the new release.\n",
|
|
762
768
|
blockedBy: [],
|
|
763
769
|
positiveSignals: [
|
|
764
770
|
"3 positive feedback events",
|
|
@@ -855,7 +861,7 @@ export const DEFAULT_PROMOTION_POLICY_SELECTION = {
|
|
|
855
861
|
score: 5.600000000000002,
|
|
856
862
|
threshold: 3.8,
|
|
857
863
|
knowledgeRef: "knowledge:kafka-rebalance-note",
|
|
858
|
-
content: "---\ndescription: Pause consumers during rebalance\ntags:\n - kafka\n - ops\nobserved_at: 2026-04-08\
|
|
864
|
+
content: "---\ndescription: Pause consumers during rebalance\ntags:\n - kafka\n - ops\nobserved_at: 2026-04-08\nxrefs:\n - memory:kafka-rebalance-note\n - skill:kafka\n---\n\nPause consumers during partition rebalances to avoid duplicate processing while assignments settle.\n",
|
|
859
865
|
blockedBy: [],
|
|
860
866
|
positiveSignals: [
|
|
861
867
|
"3 positive feedback events",
|
|
@@ -882,7 +888,7 @@ export const DEFAULT_PROMOTION_POLICY_SELECTION = {
|
|
|
882
888
|
score: 4.04,
|
|
883
889
|
threshold: 3.8,
|
|
884
890
|
knowledgeRef: "knowledge:gha-token-scope",
|
|
885
|
-
content: "---\ndescription: Minimize GitHub token scopes\ntags:\n - gha\n - security\nobserved_at: 2026-04-07\
|
|
891
|
+
content: "---\ndescription: Minimize GitHub token scopes\ntags:\n - gha\n - security\nobserved_at: 2026-04-07\nxrefs:\n - memory:gha-token-scope\n - skill:github-actions\n---\n\nUse the narrowest GitHub token scope that still allows the workflow step to succeed.\n",
|
|
886
892
|
blockedBy: [],
|
|
887
893
|
positiveSignals: [
|
|
888
894
|
"2 positive feedback events",
|
|
@@ -932,7 +938,7 @@ export const DEFAULT_PROMOTION_POLICY_SELECTION = {
|
|
|
932
938
|
score: 5.600000000000002,
|
|
933
939
|
threshold: 3.8,
|
|
934
940
|
knowledgeRef: "knowledge:terraform-state-location",
|
|
935
|
-
content: "---\ndescription: Use remote state locks\ntags:\n - terraform\nobserved_at: 2026-04-04\
|
|
941
|
+
content: "---\ndescription: Use remote state locks\ntags:\n - terraform\nobserved_at: 2026-04-04\nxrefs:\n - memory:terraform-state-location\n - skill:terraform\n---\n\nUse remote state with locking enabled before applying shared Terraform stacks.\n",
|
|
936
942
|
blockedBy: [],
|
|
937
943
|
positiveSignals: [
|
|
938
944
|
"3 positive feedback events",
|
|
@@ -984,7 +990,7 @@ export const DEFAULT_PROMOTION_POLICY_SELECTION = {
|
|
|
984
990
|
score: 4.250000000000001,
|
|
985
991
|
threshold: 3.8,
|
|
986
992
|
knowledgeRef: "knowledge:cache-ttl-fact",
|
|
987
|
-
content: "---\ndescription: Cache TTL defaults to five minutes\ntags:\n - cache\n - platform\nobserved_at: 2026-04-02\
|
|
993
|
+
content: "---\ndescription: Cache TTL defaults to five minutes\ntags:\n - cache\n - platform\nobserved_at: 2026-04-02\nxrefs:\n - memory:cache-ttl-fact\n - skill:platform\n---\n\nThe shared platform cache TTL defaults to five minutes unless the service opts out.\n",
|
|
988
994
|
blockedBy: [],
|
|
989
995
|
positiveSignals: [
|
|
990
996
|
"2 positive feedback events",
|
|
@@ -1046,7 +1052,7 @@ export const DEFAULT_PROMOTION_POLICY_SELECTION = {
|
|
|
1046
1052
|
score: 3,
|
|
1047
1053
|
threshold: 2,
|
|
1048
1054
|
knowledgeRef: "knowledge:kafka-rebalance-note",
|
|
1049
|
-
content: "---\ndescription: Pause consumers during rebalance\ntags:\n - kafka\n - ops\nobserved_at: 2026-04-08\
|
|
1055
|
+
content: "---\ndescription: Pause consumers during rebalance\ntags:\n - kafka\n - ops\nobserved_at: 2026-04-08\nxrefs:\n - memory:kafka-rebalance-note\n - skill:kafka\n---\n\nPause consumers during partition rebalances to avoid duplicate processing while assignments settle.\n",
|
|
1050
1056
|
blockedBy: [],
|
|
1051
1057
|
positiveSignals: ["baseline positive feedback rule"],
|
|
1052
1058
|
negativeSignals: [],
|
|
@@ -1063,7 +1069,7 @@ export const DEFAULT_PROMOTION_POLICY_SELECTION = {
|
|
|
1063
1069
|
score: 2,
|
|
1064
1070
|
threshold: 2,
|
|
1065
1071
|
knowledgeRef: "knowledge:gha-token-scope",
|
|
1066
|
-
content: "---\ndescription: Minimize GitHub token scopes\ntags:\n - gha\n - security\nobserved_at: 2026-04-07\
|
|
1072
|
+
content: "---\ndescription: Minimize GitHub token scopes\ntags:\n - gha\n - security\nobserved_at: 2026-04-07\nxrefs:\n - memory:gha-token-scope\n - skill:github-actions\n---\n\nUse the narrowest GitHub token scope that still allows the workflow step to succeed.\n",
|
|
1067
1073
|
blockedBy: [],
|
|
1068
1074
|
positiveSignals: ["baseline positive feedback rule"],
|
|
1069
1075
|
negativeSignals: [],
|
|
@@ -1080,7 +1086,7 @@ export const DEFAULT_PROMOTION_POLICY_SELECTION = {
|
|
|
1080
1086
|
score: 2,
|
|
1081
1087
|
threshold: 2,
|
|
1082
1088
|
knowledgeRef: "knowledge:helm-debug-guess",
|
|
1083
|
-
content: "---\ndescription: Helm upgrade might need --debug\nobserved_at: 2026-04-05\
|
|
1089
|
+
content: "---\ndescription: Helm upgrade might need --debug\nobserved_at: 2026-04-05\nxrefs:\n - memory:helm-debug-guess\n - skill:helm\n---\n\nIt might help to add --debug to helm upgrade output during failures.\n",
|
|
1084
1090
|
blockedBy: [],
|
|
1085
1091
|
positiveSignals: ["baseline positive feedback rule"],
|
|
1086
1092
|
negativeSignals: [],
|
|
@@ -1097,7 +1103,7 @@ export const DEFAULT_PROMOTION_POLICY_SELECTION = {
|
|
|
1097
1103
|
score: 3,
|
|
1098
1104
|
threshold: 2,
|
|
1099
1105
|
knowledgeRef: "knowledge:terraform-state-location",
|
|
1100
|
-
content: "---\ndescription: Use remote state locks\ntags:\n - terraform\nobserved_at: 2026-04-04\
|
|
1106
|
+
content: "---\ndescription: Use remote state locks\ntags:\n - terraform\nobserved_at: 2026-04-04\nxrefs:\n - memory:terraform-state-location\n - skill:terraform\n---\n\nUse remote state with locking enabled before applying shared Terraform stacks.\n",
|
|
1101
1107
|
blockedBy: [],
|
|
1102
1108
|
positiveSignals: ["baseline positive feedback rule"],
|
|
1103
1109
|
negativeSignals: [],
|
|
@@ -1114,7 +1120,7 @@ export const DEFAULT_PROMOTION_POLICY_SELECTION = {
|
|
|
1114
1120
|
score: 2,
|
|
1115
1121
|
threshold: 2,
|
|
1116
1122
|
knowledgeRef: "knowledge:mixed-signal-rollback",
|
|
1117
|
-
content: "---\ndescription: Rollback the cluster immediately\nobserved_at: 2026-04-03\
|
|
1123
|
+
content: "---\ndescription: Rollback the cluster immediately\nobserved_at: 2026-04-03\nxrefs:\n - memory:mixed-signal-rollback\n - skill:incident\n---\n\nRollback the cluster immediately after any 5xx spike.\n",
|
|
1118
1124
|
blockedBy: [],
|
|
1119
1125
|
positiveSignals: ["baseline positive feedback rule"],
|
|
1120
1126
|
negativeSignals: [],
|
|
@@ -1131,7 +1137,7 @@ export const DEFAULT_PROMOTION_POLICY_SELECTION = {
|
|
|
1131
1137
|
score: 2,
|
|
1132
1138
|
threshold: 2,
|
|
1133
1139
|
knowledgeRef: "knowledge:cache-ttl-fact",
|
|
1134
|
-
content: "---\ndescription: Cache TTL defaults to five minutes\ntags:\n - cache\n - platform\nobserved_at: 2026-04-02\
|
|
1140
|
+
content: "---\ndescription: Cache TTL defaults to five minutes\ntags:\n - cache\n - platform\nobserved_at: 2026-04-02\nxrefs:\n - memory:cache-ttl-fact\n - skill:platform\n---\n\nThe shared platform cache TTL defaults to five minutes unless the service opts out.\n",
|
|
1135
1141
|
blockedBy: [],
|
|
1136
1142
|
positiveSignals: ["baseline positive feedback rule"],
|
|
1137
1143
|
negativeSignals: [],
|
|
@@ -1187,7 +1193,7 @@ export const DEFAULT_PROMOTION_POLICY_SELECTION = {
|
|
|
1187
1193
|
score: 2,
|
|
1188
1194
|
threshold: 3,
|
|
1189
1195
|
knowledgeRef: "knowledge:kafka-rebalance-note",
|
|
1190
|
-
content: "---\ndescription: Pause consumers during rebalance\ntags:\n - kafka\n - ops\nobserved_at: 2026-04-08\
|
|
1196
|
+
content: "---\ndescription: Pause consumers during rebalance\ntags:\n - kafka\n - ops\nobserved_at: 2026-04-08\nxrefs:\n - memory:kafka-rebalance-note\n - skill:kafka\n---\n\nPause consumers during partition rebalances to avoid duplicate processing while assignments settle.\n",
|
|
1191
1197
|
blockedBy: [],
|
|
1192
1198
|
positiveSignals: ["baseline metadata rule"],
|
|
1193
1199
|
negativeSignals: [],
|
|
@@ -1204,7 +1210,7 @@ export const DEFAULT_PROMOTION_POLICY_SELECTION = {
|
|
|
1204
1210
|
score: 2,
|
|
1205
1211
|
threshold: 3,
|
|
1206
1212
|
knowledgeRef: "knowledge:gha-token-scope",
|
|
1207
|
-
content: "---\ndescription: Minimize GitHub token scopes\ntags:\n - gha\n - security\nobserved_at: 2026-04-07\
|
|
1213
|
+
content: "---\ndescription: Minimize GitHub token scopes\ntags:\n - gha\n - security\nobserved_at: 2026-04-07\nxrefs:\n - memory:gha-token-scope\n - skill:github-actions\n---\n\nUse the narrowest GitHub token scope that still allows the workflow step to succeed.\n",
|
|
1208
1214
|
blockedBy: [],
|
|
1209
1215
|
positiveSignals: ["baseline metadata rule"],
|
|
1210
1216
|
negativeSignals: [],
|
|
@@ -1221,7 +1227,7 @@ export const DEFAULT_PROMOTION_POLICY_SELECTION = {
|
|
|
1221
1227
|
score: 2,
|
|
1222
1228
|
threshold: 3,
|
|
1223
1229
|
knowledgeRef: "knowledge:helm-debug-guess",
|
|
1224
|
-
content: "---\ndescription: Helm upgrade might need --debug\nobserved_at: 2026-04-05\
|
|
1230
|
+
content: "---\ndescription: Helm upgrade might need --debug\nobserved_at: 2026-04-05\nxrefs:\n - memory:helm-debug-guess\n - skill:helm\n---\n\nIt might help to add --debug to helm upgrade output during failures.\n",
|
|
1225
1231
|
blockedBy: [],
|
|
1226
1232
|
positiveSignals: ["baseline metadata rule"],
|
|
1227
1233
|
negativeSignals: [],
|
|
@@ -1238,7 +1244,7 @@ export const DEFAULT_PROMOTION_POLICY_SELECTION = {
|
|
|
1238
1244
|
score: 2,
|
|
1239
1245
|
threshold: 3,
|
|
1240
1246
|
knowledgeRef: "knowledge:terraform-state-location",
|
|
1241
|
-
content: "---\ndescription: Use remote state locks\ntags:\n - terraform\nobserved_at: 2026-04-04\
|
|
1247
|
+
content: "---\ndescription: Use remote state locks\ntags:\n - terraform\nobserved_at: 2026-04-04\nxrefs:\n - memory:terraform-state-location\n - skill:terraform\n---\n\nUse remote state with locking enabled before applying shared Terraform stacks.\n",
|
|
1242
1248
|
blockedBy: [],
|
|
1243
1249
|
positiveSignals: ["baseline metadata rule"],
|
|
1244
1250
|
negativeSignals: [],
|
|
@@ -1255,7 +1261,7 @@ export const DEFAULT_PROMOTION_POLICY_SELECTION = {
|
|
|
1255
1261
|
score: 2,
|
|
1256
1262
|
threshold: 3,
|
|
1257
1263
|
knowledgeRef: "knowledge:mixed-signal-rollback",
|
|
1258
|
-
content: "---\ndescription: Rollback the cluster immediately\nobserved_at: 2026-04-03\
|
|
1264
|
+
content: "---\ndescription: Rollback the cluster immediately\nobserved_at: 2026-04-03\nxrefs:\n - memory:mixed-signal-rollback\n - skill:incident\n---\n\nRollback the cluster immediately after any 5xx spike.\n",
|
|
1259
1265
|
blockedBy: [],
|
|
1260
1266
|
positiveSignals: ["baseline metadata rule"],
|
|
1261
1267
|
negativeSignals: [],
|
|
@@ -1272,7 +1278,7 @@ export const DEFAULT_PROMOTION_POLICY_SELECTION = {
|
|
|
1272
1278
|
score: 2,
|
|
1273
1279
|
threshold: 3,
|
|
1274
1280
|
knowledgeRef: "knowledge:cache-ttl-fact",
|
|
1275
|
-
content: "---\ndescription: Cache TTL defaults to five minutes\ntags:\n - cache\n - platform\nobserved_at: 2026-04-02\
|
|
1281
|
+
content: "---\ndescription: Cache TTL defaults to five minutes\ntags:\n - cache\n - platform\nobserved_at: 2026-04-02\nxrefs:\n - memory:cache-ttl-fact\n - skill:platform\n---\n\nThe shared platform cache TTL defaults to five minutes unless the service opts out.\n",
|
|
1276
1282
|
blockedBy: [],
|
|
1277
1283
|
positiveSignals: ["baseline metadata rule"],
|
|
1278
1284
|
negativeSignals: [],
|
|
@@ -34,14 +34,10 @@
|
|
|
34
34
|
*
|
|
35
35
|
* # Lesson-name derivation rule
|
|
36
36
|
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* derived name so two sources with the same asset-type/name collapse onto
|
|
42
|
-
* the same lesson queue entry rather than each generating its own — the
|
|
43
|
-
* proposal queue tolerates duplicate refs (id is a UUID), so the human
|
|
44
|
-
* reviewer can decide which one to accept.
|
|
37
|
+
* A nested input preserves its first legitimate scope segment
|
|
38
|
+
* (`memory:project-a/deploy` → `lesson:project-a/memory-deploy-lesson`). An
|
|
39
|
+
* unscoped input stays flat; asset types are not project scopes. Origin prefixes
|
|
40
|
+
* remain durable provenance but are not embedded in the output path.
|
|
45
41
|
*
|
|
46
42
|
* # Why we do not call `runAgent`
|
|
47
43
|
*
|
|
@@ -54,7 +50,7 @@ import fs from "node:fs";
|
|
|
54
50
|
import distillKnowledgeSystemPrompt from "../../assets/prompts/distill-knowledge-system.md" with { type: "text" };
|
|
55
51
|
import distillLessonSystemPrompt from "../../assets/prompts/distill-lesson-system.md" with { type: "text" };
|
|
56
52
|
import { parseAssetRef } from "../../core/asset/asset-ref.js";
|
|
57
|
-
import { assembleAssetFromString } from "../../core/asset/asset-serialize.js";
|
|
53
|
+
import { assembleAsset, assembleAssetFromString } from "../../core/asset/asset-serialize.js";
|
|
58
54
|
import { parseFrontmatter, writeSalienceToFrontmatter } from "../../core/asset/frontmatter.js";
|
|
59
55
|
import { stripMarkdownFences } from "../../core/asset/markdown.js";
|
|
60
56
|
import { authoringRulesForType } from "../../core/authoring-rules.js";
|
|
@@ -64,7 +60,7 @@ import { ConfigError, UsageError } from "../../core/errors.js";
|
|
|
64
60
|
import { appendEvent, readEvents } from "../../core/events.js";
|
|
65
61
|
import { lintLessonContent } from "../../core/lesson-lint.js";
|
|
66
62
|
import { getDbPath } from "../../core/paths.js";
|
|
67
|
-
import {
|
|
63
|
+
import { resolveStandardsContext } from "../../core/standards/resolve-standards-context.js";
|
|
68
64
|
import { withStateDb } from "../../core/state-db.js";
|
|
69
65
|
import { warnVerbose } from "../../core/warn.js";
|
|
70
66
|
import { closeDatabase, getAllEntries, openIndexDatabase } from "../../indexer/db/db.js";
|
|
@@ -82,6 +78,7 @@ import { deriveKnowledgeRef } from "./distill-promotion-policy.js";
|
|
|
82
78
|
import { buildRefVocabulary, scoreEncodingSalience } from "./encoding-salience.js";
|
|
83
79
|
import { resolveImproveStrategy, resolveProcessEnabled } from "./improve-strategies.js";
|
|
84
80
|
import { computeSalience, upsertAssetSalience } from "./salience.js";
|
|
81
|
+
import { bareImproveRef, durableImproveRef } from "./source-identity.js";
|
|
85
82
|
// Re-exported for `reflect.ts`, which applies the same LLM-as-judge gate to
|
|
86
83
|
// reflect proposals (R-5 / #374).
|
|
87
84
|
export { runLessonQualityJudge };
|
|
@@ -125,14 +122,21 @@ export function deriveLessonRef(inputRef) {
|
|
|
125
122
|
// distils into the same lesson namespace as `skill:deploy`. The proposal
|
|
126
123
|
// id (a UUID) keeps the queue entries distinct, so collisions are not a
|
|
127
124
|
// problem — and reviewers want to see them next to each other anyway.
|
|
128
|
-
const
|
|
125
|
+
const parts = parsed.name.split("/");
|
|
126
|
+
const scope = parts.length > 1 ? parts.shift() : undefined;
|
|
127
|
+
const slug = `${parsed.type}-${parts.join("-")}`.toLowerCase();
|
|
129
128
|
// Replace anything outside the canonical asset-name charset with `-`. Keep
|
|
130
129
|
// it deterministic so re-runs produce the same ref.
|
|
131
130
|
const safe = slug
|
|
132
131
|
.replace(/[^a-z0-9-]+/g, "-")
|
|
133
132
|
.replace(/-+/g, "-")
|
|
134
133
|
.replace(/^-|-$/g, "");
|
|
135
|
-
|
|
134
|
+
const safeScope = scope
|
|
135
|
+
?.toLowerCase()
|
|
136
|
+
.replace(/[^a-z0-9-]+/g, "-")
|
|
137
|
+
.replace(/-+/g, "-")
|
|
138
|
+
.replace(/^-|-$/g, "");
|
|
139
|
+
return `lesson:${safeScope ? `${safeScope}/` : ""}${safe}-lesson`;
|
|
136
140
|
}
|
|
137
141
|
// ── Content quality validators ──────────────────────────────────────────────
|
|
138
142
|
//
|
|
@@ -254,7 +258,7 @@ export function assembleStructuredDistillMarkdown(payload, kind) {
|
|
|
254
258
|
if (kind === "knowledge" && Array.isArray(payload.sources)) {
|
|
255
259
|
const sources = payload.sources.filter((s) => typeof s === "string" && s.trim().length > 0);
|
|
256
260
|
if (sources.length > 0)
|
|
257
|
-
fm.
|
|
261
|
+
fm.xrefs = sources;
|
|
258
262
|
}
|
|
259
263
|
const fmLines = Object.entries(fm)
|
|
260
264
|
.map(([k, v]) => {
|
|
@@ -429,6 +433,7 @@ export async function akmDistill(options) {
|
|
|
429
433
|
}
|
|
430
434
|
// Validate the ref shape up front so a typo never reaches the LLM.
|
|
431
435
|
const parsedInputRef = parseAssetRef(inputRef);
|
|
436
|
+
const durableInputRef = durableImproveRef(inputRef, options.sourceName);
|
|
432
437
|
const targetKind = options.proposalKind ?? "lesson";
|
|
433
438
|
// Attribution tagging: spread into every distill_invoked event's metadata so
|
|
434
439
|
// the lane that selected this asset is recorded uniformly across all outcome
|
|
@@ -457,7 +462,7 @@ export async function akmDistill(options) {
|
|
|
457
462
|
: "Distill refuses lesson inputs — lessons are the distilled form, not a source.";
|
|
458
463
|
appendEvent({
|
|
459
464
|
eventType: "distill_invoked",
|
|
460
|
-
ref:
|
|
465
|
+
ref: durableInputRef,
|
|
461
466
|
metadata: {
|
|
462
467
|
outcome: "skipped",
|
|
463
468
|
lessonRef: skippedRef,
|
|
@@ -485,7 +490,7 @@ export async function akmDistill(options) {
|
|
|
485
490
|
const runner = resolveImproveProcessRunner(options.improveProfile, "distill", config);
|
|
486
491
|
return runner ? materializeLlmRunnerConnection(runner) : getDefaultLlmConfig(config);
|
|
487
492
|
})();
|
|
488
|
-
const lookup = options.lookupFn ?? defaultLookup;
|
|
493
|
+
const lookup = options.lookupFn ?? ((ref) => defaultLookup(ref, stash));
|
|
489
494
|
const readEventsImpl = options.readEventsFn ?? readEvents;
|
|
490
495
|
// R1 opt-out must flow into every computeSalience call this command makes so
|
|
491
496
|
// distill-written rank_score rows use the same weights as preparation's.
|
|
@@ -497,7 +502,7 @@ export async function akmDistill(options) {
|
|
|
497
502
|
let assetContent = null;
|
|
498
503
|
let assetFilePath = null;
|
|
499
504
|
try {
|
|
500
|
-
const filePath = await lookup(
|
|
505
|
+
const filePath = await lookup(durableInputRef);
|
|
501
506
|
if (filePath && fs.existsSync(filePath)) {
|
|
502
507
|
assetFilePath = filePath;
|
|
503
508
|
assetContent = fs.readFileSync(filePath, "utf8");
|
|
@@ -565,7 +570,7 @@ export async function akmDistill(options) {
|
|
|
565
570
|
encodingSalience: salienceResult.score,
|
|
566
571
|
outcomeWeightEnabled,
|
|
567
572
|
});
|
|
568
|
-
upsertAssetSalience(stateDb,
|
|
573
|
+
upsertAssetSalience(stateDb, durableInputRef, vector);
|
|
569
574
|
});
|
|
570
575
|
}
|
|
571
576
|
catch {
|
|
@@ -576,12 +581,15 @@ export async function akmDistill(options) {
|
|
|
576
581
|
// Scoring errors never block distillation.
|
|
577
582
|
}
|
|
578
583
|
}
|
|
579
|
-
const { events } = readEventsImpl({
|
|
580
|
-
ref:
|
|
584
|
+
const { events: unfilteredEvents } = readEventsImpl({
|
|
585
|
+
...(!options.legacyBareState ? { ref: durableInputRef } : {}),
|
|
581
586
|
type: "feedback",
|
|
582
587
|
excludeTags: options.excludeTags,
|
|
583
588
|
includeTags: options.includeTags,
|
|
584
589
|
});
|
|
590
|
+
const events = options.legacyBareState
|
|
591
|
+
? unfilteredEvents.filter((event) => event.ref === durableInputRef || event.ref === bareImproveRef(inputRef))
|
|
592
|
+
: unfilteredEvents;
|
|
585
593
|
// #267 — feedback exclusion. Filter events whose `ref` matches the
|
|
586
594
|
// exclusion list BEFORE the prompt is built. The original event stream
|
|
587
595
|
// is never mutated; only the `feedback` slice that reaches the LLM is
|
|
@@ -608,6 +616,8 @@ export async function akmDistill(options) {
|
|
|
608
616
|
const promotionResult = await promoteMemoryToKnowledge({
|
|
609
617
|
targetKind,
|
|
610
618
|
inputRef,
|
|
619
|
+
durableInputRef,
|
|
620
|
+
sourceName: options.sourceName,
|
|
611
621
|
assetContent,
|
|
612
622
|
filteredEvents,
|
|
613
623
|
config,
|
|
@@ -661,7 +671,7 @@ export async function akmDistill(options) {
|
|
|
661
671
|
}
|
|
662
672
|
// Distill output is a lesson/knowledge (non-wiki) → stash authoring
|
|
663
673
|
// standards. Resolved once for this single call.
|
|
664
|
-
const standardsContext =
|
|
674
|
+
const standardsContext = resolveStandardsContext(effectiveLessonRef, stash);
|
|
665
675
|
const baseUserPrompt = buildDistillPrompt({
|
|
666
676
|
inputRef,
|
|
667
677
|
assetContent,
|
|
@@ -743,7 +753,7 @@ export async function akmDistill(options) {
|
|
|
743
753
|
// is observable.
|
|
744
754
|
appendEvent({
|
|
745
755
|
eventType: "distill_invoked",
|
|
746
|
-
ref:
|
|
756
|
+
ref: durableInputRef,
|
|
747
757
|
metadata: {
|
|
748
758
|
outcome: "llm_failed",
|
|
749
759
|
lessonRef: effectiveLessonRef,
|
|
@@ -812,7 +822,7 @@ export async function akmDistill(options) {
|
|
|
812
822
|
if (findings.length > 0) {
|
|
813
823
|
appendEvent({
|
|
814
824
|
eventType: "distill_invoked",
|
|
815
|
-
ref:
|
|
825
|
+
ref: durableInputRef,
|
|
816
826
|
metadata: {
|
|
817
827
|
outcome: "validation_failed",
|
|
818
828
|
lessonRef: effectiveLessonRef,
|
|
@@ -882,22 +892,22 @@ export async function akmDistill(options) {
|
|
|
882
892
|
// structured payload alongside the raw content (matches the shape used by
|
|
883
893
|
// other proposal sources).
|
|
884
894
|
//
|
|
885
|
-
//
|
|
886
|
-
// frontmatter when the field is absent, providing reviewers with provenance
|
|
887
|
-
// without requiring them to open event history. A-MEM arXiv:2502.12110 —
|
|
888
|
-
// all notes carry explicit provenance links.
|
|
895
|
+
// Serialize canonical provenance into the content that promotion writes.
|
|
889
896
|
const parsed = parseFrontmatter(content);
|
|
890
|
-
const
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
897
|
+
const existingXrefs = Array.isArray(parsed.data.xrefs) ? parsed.data.xrefs.map(String) : [];
|
|
898
|
+
const frontmatterWithXrefs = {
|
|
899
|
+
...parsed.data,
|
|
900
|
+
xrefs: [...new Set([...existingXrefs, durableInputRef])],
|
|
901
|
+
};
|
|
902
|
+
delete frontmatterWithXrefs.sources;
|
|
903
|
+
content = assembleAsset(frontmatterWithXrefs, parsed.content);
|
|
894
904
|
const proposalResult2 = createProposal(stash, {
|
|
895
905
|
ref: effectiveLessonRef,
|
|
896
906
|
source: "distill",
|
|
897
907
|
...(options.sourceRun !== undefined ? { sourceRun: options.sourceRun } : {}),
|
|
898
908
|
payload: {
|
|
899
909
|
content,
|
|
900
|
-
frontmatter:
|
|
910
|
+
frontmatter: frontmatterWithXrefs,
|
|
901
911
|
},
|
|
902
912
|
...(lessonJudgeConfidence !== undefined ? { confidence: lessonJudgeConfidence } : {}),
|
|
903
913
|
// Attribution tagging: persist the eligibility lane on the proposal.
|
|
@@ -906,7 +916,7 @@ export async function akmDistill(options) {
|
|
|
906
916
|
if (isProposalSkipped(proposalResult2)) {
|
|
907
917
|
appendEvent({
|
|
908
918
|
eventType: "distill_invoked",
|
|
909
|
-
ref:
|
|
919
|
+
ref: durableInputRef,
|
|
910
920
|
metadata: {
|
|
911
921
|
outcome: "skipped",
|
|
912
922
|
lessonRef: effectiveLessonRef,
|
|
@@ -928,10 +938,10 @@ export async function akmDistill(options) {
|
|
|
928
938
|
// G4: content-score the distilled OUTPUT so it carries a real encoding
|
|
929
939
|
// salience (encoding_source='content') from creation — lessons never get
|
|
930
940
|
// another chance (they are refused as distill inputs).
|
|
931
|
-
persistOutputEncodingSalience(effectiveLessonRef, content, existingRefVocabulary, outcomeWeightEnabled);
|
|
941
|
+
persistOutputEncodingSalience(durableImproveRef(effectiveLessonRef, options.sourceName), content, existingRefVocabulary, outcomeWeightEnabled);
|
|
932
942
|
appendEvent({
|
|
933
943
|
eventType: "distill_invoked",
|
|
934
|
-
ref:
|
|
944
|
+
ref: durableInputRef,
|
|
935
945
|
metadata: {
|
|
936
946
|
outcome: "queued",
|
|
937
947
|
lessonRef: effectiveLessonRef,
|
|
@@ -962,6 +972,12 @@ export async function akmDistill(options) {
|
|
|
962
972
|
};
|
|
963
973
|
}
|
|
964
974
|
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
965
|
-
async function defaultLookup(ref) {
|
|
966
|
-
return resolveAssetPath(ref, {
|
|
975
|
+
async function defaultLookup(ref, stashDir) {
|
|
976
|
+
return resolveAssetPath(ref, {
|
|
977
|
+
stashDir,
|
|
978
|
+
mode: "disk-only",
|
|
979
|
+
directoryIndexNames: ["SKILL.md"],
|
|
980
|
+
preserveDirectNameFallback: true,
|
|
981
|
+
honorOrigin: false,
|
|
982
|
+
});
|
|
967
983
|
}
|
|
@@ -13,6 +13,7 @@ import { getWritableStashDirs, resolveSourceEntries } from "../../indexer/search
|
|
|
13
13
|
import { resolveAssetPath } from "../../indexer/walk/path-resolver.js";
|
|
14
14
|
import { isDistillRefusedInputType } from "./distill.js";
|
|
15
15
|
import { isStrategyFilteredForAllPasses } from "./improve-strategies.js";
|
|
16
|
+
import { improveStateReadRefs } from "./source-identity.js";
|
|
16
17
|
// Eligibility / candidate-selection predicates for improve. Free functions
|
|
17
18
|
// (no akmImprove closure state) extracted from improve.ts to shrink the
|
|
18
19
|
// orchestrator and make candidate selection independently testable.
|
|
@@ -292,15 +293,15 @@ export function shouldDistillMemoryRef(ref, stashDir) {
|
|
|
292
293
|
* metadata events are ignored so a stray `akm feedback <ref>` invocation
|
|
293
294
|
* without a flag doesn't trigger downstream re-processing.
|
|
294
295
|
*/
|
|
295
|
-
export function buildLatestFeedbackTsMap(refs, sinceIso) {
|
|
296
|
+
export function buildLatestFeedbackTsMap(refs, sinceIso, sourceName, includeLegacyBare = false) {
|
|
296
297
|
const out = new Map();
|
|
297
298
|
if (refs.length === 0)
|
|
298
299
|
return out;
|
|
299
|
-
const
|
|
300
|
+
const refByDurableKey = new Map(refs.flatMap((ref) => improveStateReadRefs(ref, sourceName, includeLegacyBare).map((key) => [key, ref])));
|
|
300
301
|
const { events } = readEvents({ type: "feedback", since: sinceIso });
|
|
301
302
|
for (const e of events) {
|
|
302
|
-
const ref = e.ref;
|
|
303
|
-
if (!ref
|
|
303
|
+
const ref = e.ref ? refByDurableKey.get(e.ref) : undefined;
|
|
304
|
+
if (!ref)
|
|
304
305
|
continue;
|
|
305
306
|
const meta = e.metadata;
|
|
306
307
|
const hasSignal = meta !== undefined && (typeof meta.signal === "string" || typeof meta.note === "string");
|
|
@@ -321,16 +322,16 @@ export function buildLatestFeedbackTsMap(refs, sinceIso) {
|
|
|
321
322
|
* lesson/knowledge ref, not the source memory — joining back through the
|
|
322
323
|
* payload would be fragile.
|
|
323
324
|
*/
|
|
324
|
-
export function buildLatestProposalTsMap(refs, source) {
|
|
325
|
+
export function buildLatestProposalTsMap(refs, source, sourceName, includeLegacyBare = false) {
|
|
325
326
|
const out = new Map();
|
|
326
327
|
if (refs.length === 0)
|
|
327
328
|
return out;
|
|
328
|
-
const
|
|
329
|
+
const refByDurableKey = new Map(refs.flatMap((ref) => improveStateReadRefs(ref, sourceName, includeLegacyBare).map((key) => [key, ref])));
|
|
329
330
|
const eventType = source === "reflect" ? "reflect_invoked" : "distill_invoked";
|
|
330
331
|
const { events } = readEvents({ type: eventType });
|
|
331
332
|
for (const e of events) {
|
|
332
|
-
const ref = e.ref;
|
|
333
|
-
if (!ref
|
|
333
|
+
const ref = e.ref ? refByDurableKey.get(e.ref) : undefined;
|
|
334
|
+
if (!ref)
|
|
334
335
|
continue;
|
|
335
336
|
// For distill_invoked we only count attempts that produced (or attempted
|
|
336
337
|
// to produce) a real proposal — config_disabled / parse-error outcomes
|
|
@@ -54,8 +54,8 @@ export const EXTRACT_JSON_SCHEMA = {
|
|
|
54
54
|
},
|
|
55
55
|
name: {
|
|
56
56
|
type: "string",
|
|
57
|
-
description: "Kebab-case slug
|
|
58
|
-
pattern: "^[a-z0-9][a-z0-9-]*[a-z0-9]
|
|
57
|
+
description: "Kebab-case slug, optionally under one stable scope/domain segment.",
|
|
58
|
+
pattern: "^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:/[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)?$",
|
|
59
59
|
},
|
|
60
60
|
description: {
|
|
61
61
|
type: "string",
|