akm-cli 0.7.4 → 0.8.0-rc1
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 → .github/CHANGELOG.md} +34 -1
- package/.github/LICENSE +374 -0
- package/dist/cli/parse-args.js +43 -0
- package/dist/cli.js +1007 -593
- package/dist/commands/agent-dispatch.js +102 -0
- package/dist/commands/agent-support.js +62 -0
- package/dist/commands/config-cli.js +68 -84
- package/dist/commands/consolidate.js +823 -0
- package/dist/commands/curate.js +1 -0
- package/dist/commands/distill-promotion-policy.js +658 -0
- package/dist/commands/distill.js +250 -48
- package/dist/commands/eval-cases.js +40 -0
- package/dist/commands/events.js +12 -24
- package/dist/commands/graph.js +222 -0
- package/dist/commands/health.js +376 -0
- package/dist/commands/help/help-accept.md +9 -0
- package/dist/commands/help/help-improve.md +53 -0
- package/dist/commands/help/help-proposals.md +15 -0
- package/dist/commands/help/help-propose.md +17 -0
- package/dist/commands/help/help-reject.md +8 -0
- package/dist/commands/history.js +3 -30
- package/dist/commands/improve.js +1170 -0
- package/dist/commands/info.js +2 -2
- package/dist/commands/init.js +2 -2
- package/dist/commands/install-audit.js +5 -1
- package/dist/commands/installed-stashes.js +118 -138
- package/dist/commands/knowledge.js +133 -0
- package/dist/commands/lint/agent-linter.js +46 -0
- package/dist/commands/lint/base-linter.js +251 -0
- package/dist/commands/lint/command-linter.js +46 -0
- package/dist/commands/lint/default-linter.js +13 -0
- package/dist/commands/lint/index.js +107 -0
- package/dist/commands/lint/knowledge-linter.js +13 -0
- package/dist/commands/lint/memory-linter.js +58 -0
- package/dist/commands/lint/registry.js +33 -0
- package/dist/commands/lint/skill-linter.js +42 -0
- package/dist/commands/lint/task-linter.js +47 -0
- package/dist/commands/lint/types.js +1 -0
- package/dist/commands/lint/workflow-linter.js +53 -0
- package/dist/commands/lint.js +1 -0
- package/dist/commands/migration-help.js +2 -2
- package/dist/commands/proposal.js +8 -7
- package/dist/commands/propose.js +113 -43
- package/dist/commands/reflect.js +175 -41
- package/dist/commands/registry-search.js +2 -2
- package/dist/commands/remember.js +55 -1
- package/dist/commands/schema-repair.js +130 -0
- package/dist/commands/search.js +21 -5
- package/dist/commands/show.js +131 -52
- package/dist/commands/source-add.js +10 -10
- package/dist/commands/source-manage.js +11 -19
- package/dist/commands/tasks.js +385 -0
- package/dist/commands/url-checker.js +39 -0
- package/dist/commands/vault.js +7 -33
- package/dist/core/action-contributors.js +25 -0
- package/dist/core/asset-registry.js +5 -17
- package/dist/core/asset-spec.js +11 -1
- package/dist/core/common.js +94 -0
- package/dist/core/concurrent.js +22 -0
- package/dist/core/config.js +229 -122
- package/dist/core/events.js +87 -123
- package/dist/core/frontmatter.js +3 -1
- package/dist/core/markdown.js +17 -0
- package/dist/core/memory-improve.js +678 -0
- package/dist/core/parse.js +155 -0
- package/dist/core/paths.js +101 -3
- package/dist/core/proposal-validators.js +61 -0
- package/dist/core/proposals.js +49 -38
- package/dist/core/state-db.js +775 -0
- package/dist/core/time.js +51 -0
- package/dist/core/warn.js +59 -1
- package/dist/indexer/db-search.js +86 -472
- package/dist/indexer/db.js +392 -6
- package/dist/indexer/ensure-index.js +133 -0
- package/dist/indexer/graph-boost.js +247 -94
- package/dist/indexer/graph-db.js +201 -0
- package/dist/indexer/graph-dedup.js +99 -0
- package/dist/indexer/graph-extraction.js +417 -74
- package/dist/indexer/index-context.js +10 -0
- package/dist/indexer/indexer.js +466 -298
- package/dist/indexer/llm-cache.js +47 -0
- package/dist/indexer/match-contributors.js +141 -0
- package/dist/indexer/matchers.js +24 -190
- package/dist/indexer/memory-inference.js +63 -29
- package/dist/indexer/metadata-contributors.js +26 -0
- package/dist/indexer/metadata.js +188 -175
- package/dist/indexer/path-resolver.js +89 -0
- package/dist/indexer/ranking-contributors.js +204 -0
- package/dist/indexer/ranking.js +74 -0
- package/dist/indexer/search-hit-enrichers.js +22 -0
- package/dist/indexer/search-source.js +24 -9
- package/dist/indexer/semantic-status.js +2 -16
- package/dist/indexer/walker.js +25 -0
- package/dist/integrations/agent/config.js +175 -3
- package/dist/integrations/agent/index.js +3 -1
- package/dist/integrations/agent/pipeline.js +39 -0
- package/dist/integrations/agent/profiles.js +67 -5
- package/dist/integrations/agent/prompts.js +114 -29
- package/dist/integrations/agent/runners.js +31 -0
- package/dist/integrations/agent/sdk-runner.js +120 -0
- package/dist/integrations/agent/spawn.js +136 -28
- package/dist/integrations/lockfile.js +10 -18
- package/dist/integrations/session-logs/index.js +65 -0
- package/dist/integrations/session-logs/providers/claude-code.js +56 -0
- package/dist/integrations/session-logs/providers/opencode.js +52 -0
- package/dist/integrations/session-logs/types.js +1 -0
- package/dist/llm/call-ai.js +74 -0
- package/dist/llm/client.js +63 -86
- package/dist/llm/feature-gate.js +27 -16
- package/dist/llm/graph-extract.js +297 -64
- package/dist/llm/memory-infer.js +52 -71
- package/dist/llm/metadata-enhance.js +39 -22
- package/dist/llm/prompts/graph-extract-user-prompt.md +12 -0
- package/dist/output/cli-hints-full.md +277 -0
- package/dist/output/cli-hints-short.md +65 -0
- package/dist/output/cli-hints.js +2 -309
- package/dist/output/renderers.js +196 -124
- package/dist/output/shapes.js +41 -3
- package/dist/output/text.js +257 -21
- package/dist/registry/providers/skills-sh.js +61 -49
- package/dist/registry/providers/static-index.js +44 -48
- package/dist/setup/setup.js +510 -11
- package/dist/sources/provider-factory.js +2 -1
- package/dist/sources/providers/git.js +44 -2
- package/dist/sources/website-ingest.js +4 -0
- package/dist/tasks/backends/cron.js +200 -0
- package/dist/tasks/backends/exec-utils.js +25 -0
- package/dist/tasks/backends/index.js +32 -0
- package/dist/tasks/backends/launchd-template.xml +19 -0
- package/dist/tasks/backends/launchd.js +184 -0
- package/dist/tasks/backends/schtasks-template.xml +29 -0
- package/dist/tasks/backends/schtasks.js +212 -0
- package/dist/tasks/parser.js +198 -0
- package/dist/tasks/resolveAkmBin.js +84 -0
- package/dist/tasks/runner.js +432 -0
- package/dist/tasks/schedule.js +208 -0
- package/dist/tasks/schema.js +13 -0
- package/dist/tasks/validator.js +59 -0
- package/dist/wiki/index-template.md +12 -0
- package/dist/wiki/ingest-workflow-template.md +54 -0
- package/dist/wiki/log-template.md +8 -0
- package/dist/wiki/schema-template.md +61 -0
- package/dist/wiki/wiki-templates.js +12 -0
- package/dist/wiki/wiki.js +10 -61
- package/dist/workflows/authoring.js +5 -25
- package/dist/workflows/db.js +9 -0
- package/dist/workflows/renderer.js +8 -3
- package/dist/workflows/runs.js +73 -88
- package/dist/workflows/scope-key.js +76 -0
- package/dist/workflows/validator.js +1 -1
- package/dist/workflows/workflow-template.md +24 -0
- package/docs/README.md +3 -0
- package/docs/migration/release-notes/0.7.0.md +1 -1
- package/docs/migration/release-notes/0.7.4.md +1 -1
- package/docs/migration/release-notes/0.7.5.md +20 -0
- package/docs/migration/release-notes/0.8.0.md +43 -0
- package/package.json +4 -3
- package/dist/templates/wiki-templates.js +0 -100
package/dist/commands/curate.js
CHANGED
|
@@ -135,6 +135,7 @@ async function enrichCuratedStashHit(query, hit) {
|
|
|
135
135
|
ref: hit.ref,
|
|
136
136
|
...(description ? { description } : {}),
|
|
137
137
|
...(preview ? { preview } : {}),
|
|
138
|
+
...(shown?.keys?.length ? { keys: shown.keys } : {}),
|
|
138
139
|
...(shown?.parameters?.length ? { parameters: shown.parameters } : {}),
|
|
139
140
|
...(shown?.run ? { run: shown.run } : {}),
|
|
140
141
|
followUp: `akm show ${hit.ref}`,
|
|
@@ -0,0 +1,658 @@
|
|
|
1
|
+
import { stringify as yamlStringify } from "yaml";
|
|
2
|
+
import { parseAssetRef } from "../core/asset-ref";
|
|
3
|
+
import { parseFrontmatter } from "../core/frontmatter";
|
|
4
|
+
function hasNonEmptyList(value) {
|
|
5
|
+
return Array.isArray(value) && value.some((item) => typeof item === "string" && item.trim().length > 0);
|
|
6
|
+
}
|
|
7
|
+
function hasConflictMarkers(metadata) {
|
|
8
|
+
return (metadata.conflict === true ||
|
|
9
|
+
metadata.conflicted === true ||
|
|
10
|
+
metadata.contradiction === true ||
|
|
11
|
+
metadata.contradicted === true ||
|
|
12
|
+
metadata.superseded === true ||
|
|
13
|
+
metadata.obsolete === true ||
|
|
14
|
+
metadata.retracted === true ||
|
|
15
|
+
hasNonEmptyList(metadata.contradictedBy) ||
|
|
16
|
+
hasNonEmptyList(metadata.supersededBy));
|
|
17
|
+
}
|
|
18
|
+
function hasTentativeLanguage(text) {
|
|
19
|
+
return /\b(maybe|might|probably|possibly|perhaps|unclear|unsure|not sure|tbd|todo|investigate)\b/i.test(text);
|
|
20
|
+
}
|
|
21
|
+
function scoreConfidence(confidence) {
|
|
22
|
+
if (typeof confidence !== "number")
|
|
23
|
+
return 0;
|
|
24
|
+
if (confidence >= 0.95)
|
|
25
|
+
return 1;
|
|
26
|
+
if (confidence >= 0.85)
|
|
27
|
+
return 0.7;
|
|
28
|
+
if (confidence >= 0.75)
|
|
29
|
+
return 0.35;
|
|
30
|
+
return 0;
|
|
31
|
+
}
|
|
32
|
+
function deriveDescription(body, description) {
|
|
33
|
+
if (description && description.length > 0)
|
|
34
|
+
return description;
|
|
35
|
+
return body
|
|
36
|
+
.split(/\r?\n/)
|
|
37
|
+
.map((line) => line.trim())
|
|
38
|
+
.find((line) => line.length > 0)
|
|
39
|
+
?.replace(/^#+\s*/, "")
|
|
40
|
+
?.slice(0, 160);
|
|
41
|
+
}
|
|
42
|
+
function memoryContent(frontmatter, body) {
|
|
43
|
+
return ["---", ...frontmatter, "---", "", body, ""].join("\n");
|
|
44
|
+
}
|
|
45
|
+
export function deriveKnowledgeRef(inputRef) {
|
|
46
|
+
const parsed = parseAssetRef(inputRef);
|
|
47
|
+
return `knowledge:${parsed.name}`;
|
|
48
|
+
}
|
|
49
|
+
function collectPromotionFeatures(input) {
|
|
50
|
+
const parsed = parseAssetRef(input.inputRef);
|
|
51
|
+
const blockedBy = [];
|
|
52
|
+
if (parsed.type !== "memory") {
|
|
53
|
+
return { blockedBy: ["not-memory"] };
|
|
54
|
+
}
|
|
55
|
+
if (parsed.name.endsWith(".derived")) {
|
|
56
|
+
return { blockedBy: ["derived-memory"] };
|
|
57
|
+
}
|
|
58
|
+
if (!input.assetContent) {
|
|
59
|
+
return { blockedBy: ["missing-asset-content"] };
|
|
60
|
+
}
|
|
61
|
+
const parsedContent = parseFrontmatter(input.assetContent);
|
|
62
|
+
const fm = parsedContent.data;
|
|
63
|
+
const body = parsedContent.content.trim();
|
|
64
|
+
if (!body) {
|
|
65
|
+
return { blockedBy: ["empty-body"] };
|
|
66
|
+
}
|
|
67
|
+
if (hasConflictMarkers(fm))
|
|
68
|
+
blockedBy.push("asset-conflict-marker");
|
|
69
|
+
const quality = typeof fm.quality === "string" ? fm.quality.trim().toLowerCase() : undefined;
|
|
70
|
+
if (quality === "proposed")
|
|
71
|
+
blockedBy.push("proposed-quality");
|
|
72
|
+
if (fm.subjective === true)
|
|
73
|
+
blockedBy.push("subjective-memory");
|
|
74
|
+
if (typeof fm.expires === "string" && fm.expires.trim())
|
|
75
|
+
blockedBy.push("expiring-memory");
|
|
76
|
+
if (input.feedbackEvents.some((event) => event.metadata && hasConflictMarkers(event.metadata))) {
|
|
77
|
+
blockedBy.push("feedback-conflict-marker");
|
|
78
|
+
}
|
|
79
|
+
if (blockedBy.length > 0) {
|
|
80
|
+
return { blockedBy, body };
|
|
81
|
+
}
|
|
82
|
+
let positiveFeedback = 0;
|
|
83
|
+
let negativeFeedback = 0;
|
|
84
|
+
for (const event of input.feedbackEvents) {
|
|
85
|
+
const signal = typeof event.metadata?.signal === "string" ? event.metadata.signal.trim().toLowerCase() : undefined;
|
|
86
|
+
if (signal === "positive")
|
|
87
|
+
positiveFeedback += 1;
|
|
88
|
+
if (signal === "negative")
|
|
89
|
+
negativeFeedback += 1;
|
|
90
|
+
}
|
|
91
|
+
const description = typeof fm.description === "string" ? fm.description.trim() : undefined;
|
|
92
|
+
const tags = Array.isArray(fm.tags) ? fm.tags : undefined;
|
|
93
|
+
const source = typeof fm.source === "string" && fm.source.trim() ? fm.source.trim() : undefined;
|
|
94
|
+
const observedAt = typeof fm.observed_at === "string" && fm.observed_at.trim() ? fm.observed_at.trim() : undefined;
|
|
95
|
+
const features = {
|
|
96
|
+
positiveFeedback,
|
|
97
|
+
negativeFeedback,
|
|
98
|
+
hasCuratedQuality: quality === "curated",
|
|
99
|
+
confidenceBoost: scoreConfidence(fm.confidence),
|
|
100
|
+
hasSource: source !== undefined,
|
|
101
|
+
hasObservedAt: observedAt !== undefined,
|
|
102
|
+
hasDescription: description !== undefined,
|
|
103
|
+
hasTags: Array.isArray(tags) && tags.some((tag) => typeof tag === "string" && tag.trim().length > 0),
|
|
104
|
+
substantiveBody: body.split(/\s+/).filter(Boolean).length >= 8,
|
|
105
|
+
tentativeLanguage: hasTentativeLanguage([description, body].filter(Boolean).join("\n")),
|
|
106
|
+
};
|
|
107
|
+
return { blockedBy, features, body, description, tags, observedAt, source };
|
|
108
|
+
}
|
|
109
|
+
function buildKnowledgeContent(input) {
|
|
110
|
+
const parsedContent = parseFrontmatter(input.assetContent);
|
|
111
|
+
const fm = parsedContent.data;
|
|
112
|
+
const body = parsedContent.content.trim();
|
|
113
|
+
const description = typeof fm.description === "string" ? fm.description.trim() : undefined;
|
|
114
|
+
const normalizedDescription = deriveDescription(body, description);
|
|
115
|
+
const sources = [input.inputRef];
|
|
116
|
+
if (typeof fm.source === "string" && fm.source.trim())
|
|
117
|
+
sources.push(fm.source.trim());
|
|
118
|
+
const knowledgeFrontmatter = {
|
|
119
|
+
...(normalizedDescription ? { description: normalizedDescription } : {}),
|
|
120
|
+
...(Array.isArray(fm.tags) ? { tags: fm.tags } : {}),
|
|
121
|
+
...(typeof fm.observed_at === "string" && fm.observed_at.trim() ? { observed_at: fm.observed_at.trim() } : {}),
|
|
122
|
+
sources,
|
|
123
|
+
};
|
|
124
|
+
const frontmatter = yamlStringify(knowledgeFrontmatter).trimEnd();
|
|
125
|
+
return `---\n${frontmatter}\n---\n\n${body}\n`;
|
|
126
|
+
}
|
|
127
|
+
function assessWithWeightedModel(input, model, threshold) {
|
|
128
|
+
const knowledgeRef = deriveKnowledgeRef(input.inputRef);
|
|
129
|
+
const featureState = collectPromotionFeatures(input);
|
|
130
|
+
if (featureState.blockedBy.length > 0) {
|
|
131
|
+
return {
|
|
132
|
+
applicable: !featureState.blockedBy.includes("not-memory"),
|
|
133
|
+
promote: false,
|
|
134
|
+
score: 0,
|
|
135
|
+
threshold,
|
|
136
|
+
knowledgeRef,
|
|
137
|
+
blockedBy: featureState.blockedBy,
|
|
138
|
+
positiveSignals: [],
|
|
139
|
+
negativeSignals: [],
|
|
140
|
+
modelName: model.name,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
const features = featureState.features;
|
|
144
|
+
let score = 0;
|
|
145
|
+
const positiveSignals = [];
|
|
146
|
+
const negativeSignals = [];
|
|
147
|
+
if (features.positiveFeedback > 0) {
|
|
148
|
+
const boost = Math.min(features.positiveFeedback, 4) * model.positiveWeight;
|
|
149
|
+
score += boost;
|
|
150
|
+
positiveSignals.push(`${features.positiveFeedback} positive feedback event${features.positiveFeedback === 1 ? "" : "s"}`);
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
score -= model.noPositivePenalty;
|
|
154
|
+
negativeSignals.push("no positive feedback reinforcement");
|
|
155
|
+
}
|
|
156
|
+
if (features.positiveFeedback >= 2) {
|
|
157
|
+
score += model.repeatedPositiveWeight;
|
|
158
|
+
positiveSignals.push("repeated reinforcement");
|
|
159
|
+
}
|
|
160
|
+
else if (features.positiveFeedback === 1) {
|
|
161
|
+
score -= model.singlePositivePenalty;
|
|
162
|
+
negativeSignals.push("only one reinforcing feedback event");
|
|
163
|
+
}
|
|
164
|
+
if (features.negativeFeedback > 0) {
|
|
165
|
+
score -= features.negativeFeedback * model.negativeWeight;
|
|
166
|
+
negativeSignals.push(`${features.negativeFeedback} negative feedback event${features.negativeFeedback === 1 ? "" : "s"}`);
|
|
167
|
+
}
|
|
168
|
+
if (features.hasCuratedQuality) {
|
|
169
|
+
score += model.curatedWeight;
|
|
170
|
+
positiveSignals.push("curated quality");
|
|
171
|
+
}
|
|
172
|
+
if (features.confidenceBoost > 0) {
|
|
173
|
+
score += features.confidenceBoost * model.confidenceWeight;
|
|
174
|
+
positiveSignals.push("strong confidence");
|
|
175
|
+
}
|
|
176
|
+
if (features.hasSource) {
|
|
177
|
+
score += model.sourceWeight;
|
|
178
|
+
positiveSignals.push("linked source");
|
|
179
|
+
}
|
|
180
|
+
if (features.hasObservedAt) {
|
|
181
|
+
score += model.observedAtWeight;
|
|
182
|
+
positiveSignals.push("observed_at present");
|
|
183
|
+
}
|
|
184
|
+
if (features.hasDescription) {
|
|
185
|
+
score += model.descriptionWeight;
|
|
186
|
+
positiveSignals.push("description present");
|
|
187
|
+
}
|
|
188
|
+
if (features.hasTags) {
|
|
189
|
+
score += model.tagWeight;
|
|
190
|
+
positiveSignals.push("tagged memory");
|
|
191
|
+
}
|
|
192
|
+
if (features.substantiveBody) {
|
|
193
|
+
score += model.substantiveBodyWeight;
|
|
194
|
+
positiveSignals.push("substantive body");
|
|
195
|
+
}
|
|
196
|
+
if (features.tentativeLanguage) {
|
|
197
|
+
score -= model.tentativePenalty;
|
|
198
|
+
negativeSignals.push("tentative language");
|
|
199
|
+
}
|
|
200
|
+
const promote = score >= threshold;
|
|
201
|
+
return {
|
|
202
|
+
applicable: true,
|
|
203
|
+
promote,
|
|
204
|
+
score,
|
|
205
|
+
threshold,
|
|
206
|
+
knowledgeRef,
|
|
207
|
+
...(promote ? { content: buildKnowledgeContent(input) } : {}),
|
|
208
|
+
blockedBy: [],
|
|
209
|
+
positiveSignals,
|
|
210
|
+
negativeSignals,
|
|
211
|
+
modelName: model.name,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
function precision(tp, fp) {
|
|
215
|
+
return tp + fp === 0 ? 1 : tp / (tp + fp);
|
|
216
|
+
}
|
|
217
|
+
function recall(tp, fn) {
|
|
218
|
+
return tp + fn === 0 ? 1 : tp / (tp + fn);
|
|
219
|
+
}
|
|
220
|
+
function f1Score(p, r) {
|
|
221
|
+
return p + r === 0 ? 0 : (2 * p * r) / (p + r);
|
|
222
|
+
}
|
|
223
|
+
function casePromoteValue(testCase) {
|
|
224
|
+
return testCase.promoteValue ?? 3;
|
|
225
|
+
}
|
|
226
|
+
function caseFalsePromoteCost(testCase) {
|
|
227
|
+
return testCase.falsePromoteCost ?? 4;
|
|
228
|
+
}
|
|
229
|
+
function caseMissedPromoteCost(testCase) {
|
|
230
|
+
return testCase.missedPromoteCost ?? 2;
|
|
231
|
+
}
|
|
232
|
+
export function evaluateMemoryPromotionBenchmark(cases, policy = DEFAULT_PROMOTION_POLICY) {
|
|
233
|
+
const results = cases.map((fixture) => {
|
|
234
|
+
const assessment = policy.assess(fixture.input);
|
|
235
|
+
const passed = assessment.promote === fixture.expectPromote;
|
|
236
|
+
return {
|
|
237
|
+
fixture,
|
|
238
|
+
name: fixture.name,
|
|
239
|
+
expectPromote: fixture.expectPromote,
|
|
240
|
+
assessment,
|
|
241
|
+
passed,
|
|
242
|
+
};
|
|
243
|
+
});
|
|
244
|
+
const truePositives = results.filter((result) => result.assessment.promote && result.expectPromote).length;
|
|
245
|
+
const trueNegatives = results.filter((result) => !result.assessment.promote && !result.expectPromote).length;
|
|
246
|
+
const falsePositives = results.filter((result) => result.assessment.promote && !result.expectPromote).length;
|
|
247
|
+
const falseNegatives = results.filter((result) => !result.assessment.promote && result.expectPromote).length;
|
|
248
|
+
const correct = truePositives + trueNegatives;
|
|
249
|
+
const p = precision(truePositives, falsePositives);
|
|
250
|
+
const r = recall(truePositives, falseNegatives);
|
|
251
|
+
let netOutcomeScore = 0;
|
|
252
|
+
let capturedPromoteValue = 0;
|
|
253
|
+
let preventedFalsePromotionCost = 0;
|
|
254
|
+
for (const result of results) {
|
|
255
|
+
if (result.expectPromote && result.assessment.promote) {
|
|
256
|
+
const value = casePromoteValue(result.fixture);
|
|
257
|
+
netOutcomeScore += value;
|
|
258
|
+
capturedPromoteValue += value;
|
|
259
|
+
}
|
|
260
|
+
else if (result.expectPromote && !result.assessment.promote) {
|
|
261
|
+
netOutcomeScore -= caseMissedPromoteCost(result.fixture);
|
|
262
|
+
}
|
|
263
|
+
else if (!result.expectPromote && result.assessment.promote) {
|
|
264
|
+
netOutcomeScore -= caseFalsePromoteCost(result.fixture);
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
preventedFalsePromotionCost += caseFalsePromoteCost(result.fixture);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return {
|
|
271
|
+
total: results.length,
|
|
272
|
+
correct,
|
|
273
|
+
falsePositives,
|
|
274
|
+
falseNegatives,
|
|
275
|
+
accuracy: results.length === 0 ? 1 : correct / results.length,
|
|
276
|
+
precision: p,
|
|
277
|
+
recall: r,
|
|
278
|
+
f1: f1Score(p, r),
|
|
279
|
+
truePositives,
|
|
280
|
+
trueNegatives,
|
|
281
|
+
netOutcomeScore,
|
|
282
|
+
capturedPromoteValue,
|
|
283
|
+
preventedFalsePromotionCost,
|
|
284
|
+
results: results.map(({ name, expectPromote, assessment, passed }) => ({
|
|
285
|
+
name,
|
|
286
|
+
expectPromote,
|
|
287
|
+
assessment,
|
|
288
|
+
passed,
|
|
289
|
+
})),
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
const CANDIDATE_MODELS = [
|
|
293
|
+
{
|
|
294
|
+
name: "balanced-evidence",
|
|
295
|
+
positiveWeight: 0.8,
|
|
296
|
+
repeatedPositiveWeight: 0.65,
|
|
297
|
+
noPositivePenalty: 0.9,
|
|
298
|
+
singlePositivePenalty: 0.7,
|
|
299
|
+
negativeWeight: 2.0,
|
|
300
|
+
curatedWeight: 0.55,
|
|
301
|
+
confidenceWeight: 0.7,
|
|
302
|
+
sourceWeight: 0.4,
|
|
303
|
+
observedAtWeight: 0.4,
|
|
304
|
+
descriptionWeight: 0.2,
|
|
305
|
+
tagWeight: 0.15,
|
|
306
|
+
substantiveBodyWeight: 0.15,
|
|
307
|
+
tentativePenalty: 1.1,
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
name: "strict-feedback",
|
|
311
|
+
positiveWeight: 0.75,
|
|
312
|
+
repeatedPositiveWeight: 0.8,
|
|
313
|
+
noPositivePenalty: 1.1,
|
|
314
|
+
singlePositivePenalty: 0.9,
|
|
315
|
+
negativeWeight: 2.25,
|
|
316
|
+
curatedWeight: 0.45,
|
|
317
|
+
confidenceWeight: 0.55,
|
|
318
|
+
sourceWeight: 0.35,
|
|
319
|
+
observedAtWeight: 0.35,
|
|
320
|
+
descriptionWeight: 0.15,
|
|
321
|
+
tagWeight: 0.1,
|
|
322
|
+
substantiveBodyWeight: 0.1,
|
|
323
|
+
tentativePenalty: 1.15,
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
name: "metadata-friendly",
|
|
327
|
+
positiveWeight: 0.7,
|
|
328
|
+
repeatedPositiveWeight: 0.55,
|
|
329
|
+
noPositivePenalty: 0.75,
|
|
330
|
+
singlePositivePenalty: 0.55,
|
|
331
|
+
negativeWeight: 1.85,
|
|
332
|
+
curatedWeight: 0.75,
|
|
333
|
+
confidenceWeight: 0.9,
|
|
334
|
+
sourceWeight: 0.5,
|
|
335
|
+
observedAtWeight: 0.5,
|
|
336
|
+
descriptionWeight: 0.25,
|
|
337
|
+
tagWeight: 0.2,
|
|
338
|
+
substantiveBodyWeight: 0.2,
|
|
339
|
+
tentativePenalty: 1.0,
|
|
340
|
+
},
|
|
341
|
+
];
|
|
342
|
+
function thresholdCandidates() {
|
|
343
|
+
const values = [];
|
|
344
|
+
for (let value = 2.4; value <= 4.2; value += 0.2) {
|
|
345
|
+
values.push(Number(value.toFixed(1)));
|
|
346
|
+
}
|
|
347
|
+
return values;
|
|
348
|
+
}
|
|
349
|
+
const POSITIVE_FEEDBACK_BASELINE = {
|
|
350
|
+
name: "baseline-positive-feedback",
|
|
351
|
+
threshold: 2,
|
|
352
|
+
assess(input) {
|
|
353
|
+
const knowledgeRef = deriveKnowledgeRef(input.inputRef);
|
|
354
|
+
const featureState = collectPromotionFeatures(input);
|
|
355
|
+
if (featureState.blockedBy.length > 0) {
|
|
356
|
+
return {
|
|
357
|
+
applicable: !featureState.blockedBy.includes("not-memory"),
|
|
358
|
+
promote: false,
|
|
359
|
+
score: 0,
|
|
360
|
+
threshold: 2,
|
|
361
|
+
knowledgeRef,
|
|
362
|
+
blockedBy: featureState.blockedBy,
|
|
363
|
+
positiveSignals: [],
|
|
364
|
+
negativeSignals: [],
|
|
365
|
+
modelName: "baseline-positive-feedback",
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
const features = featureState.features;
|
|
369
|
+
const promote = features.positiveFeedback >= 2;
|
|
370
|
+
return {
|
|
371
|
+
applicable: true,
|
|
372
|
+
promote,
|
|
373
|
+
score: features.positiveFeedback,
|
|
374
|
+
threshold: 2,
|
|
375
|
+
knowledgeRef,
|
|
376
|
+
...(promote ? { content: buildKnowledgeContent(input) } : {}),
|
|
377
|
+
blockedBy: [],
|
|
378
|
+
positiveSignals: promote ? ["baseline positive feedback rule"] : [],
|
|
379
|
+
negativeSignals: promote ? [] : ["baseline positive feedback rule not met"],
|
|
380
|
+
modelName: "baseline-positive-feedback",
|
|
381
|
+
};
|
|
382
|
+
},
|
|
383
|
+
};
|
|
384
|
+
const METADATA_BASELINE = {
|
|
385
|
+
name: "baseline-metadata",
|
|
386
|
+
threshold: 2,
|
|
387
|
+
assess(input) {
|
|
388
|
+
const knowledgeRef = deriveKnowledgeRef(input.inputRef);
|
|
389
|
+
const featureState = collectPromotionFeatures(input);
|
|
390
|
+
if (featureState.blockedBy.length > 0) {
|
|
391
|
+
return {
|
|
392
|
+
applicable: !featureState.blockedBy.includes("not-memory"),
|
|
393
|
+
promote: false,
|
|
394
|
+
score: 0,
|
|
395
|
+
threshold: 2,
|
|
396
|
+
knowledgeRef,
|
|
397
|
+
blockedBy: featureState.blockedBy,
|
|
398
|
+
positiveSignals: [],
|
|
399
|
+
negativeSignals: [],
|
|
400
|
+
modelName: "baseline-metadata",
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
const features = featureState.features;
|
|
404
|
+
const metadataScore = (features.hasSource ? 1 : 0) + (features.hasObservedAt ? 1 : 0);
|
|
405
|
+
const promote = metadataScore >= 2;
|
|
406
|
+
return {
|
|
407
|
+
applicable: true,
|
|
408
|
+
promote,
|
|
409
|
+
score: metadataScore,
|
|
410
|
+
threshold: 3,
|
|
411
|
+
knowledgeRef,
|
|
412
|
+
...(promote ? { content: buildKnowledgeContent(input) } : {}),
|
|
413
|
+
blockedBy: [],
|
|
414
|
+
positiveSignals: promote ? ["baseline metadata rule"] : [],
|
|
415
|
+
negativeSignals: promote ? [] : ["baseline metadata rule not met"],
|
|
416
|
+
modelName: "baseline-metadata",
|
|
417
|
+
};
|
|
418
|
+
},
|
|
419
|
+
};
|
|
420
|
+
function selectPromotionPolicy(corpus) {
|
|
421
|
+
const trainingCases = corpus.filter((testCase) => (testCase.split ?? "train") === "train");
|
|
422
|
+
const heldOutCases = corpus.filter((testCase) => (testCase.split ?? "train") === "heldout");
|
|
423
|
+
let bestPolicy;
|
|
424
|
+
let bestTraining;
|
|
425
|
+
for (const model of CANDIDATE_MODELS) {
|
|
426
|
+
for (const threshold of thresholdCandidates()) {
|
|
427
|
+
const policy = {
|
|
428
|
+
name: model.name,
|
|
429
|
+
threshold,
|
|
430
|
+
assess: (input) => assessWithWeightedModel(input, model, threshold),
|
|
431
|
+
};
|
|
432
|
+
const training = evaluateMemoryPromotionBenchmark(trainingCases, policy);
|
|
433
|
+
if (!bestTraining) {
|
|
434
|
+
bestTraining = training;
|
|
435
|
+
bestPolicy = policy;
|
|
436
|
+
continue;
|
|
437
|
+
}
|
|
438
|
+
const trainingWins = training.f1 > bestTraining.f1 ||
|
|
439
|
+
(training.f1 === bestTraining.f1 && training.netOutcomeScore > bestTraining.netOutcomeScore) ||
|
|
440
|
+
(training.f1 === bestTraining.f1 &&
|
|
441
|
+
training.netOutcomeScore === bestTraining.netOutcomeScore &&
|
|
442
|
+
training.accuracy > bestTraining.accuracy);
|
|
443
|
+
if (trainingWins) {
|
|
444
|
+
bestTraining = training;
|
|
445
|
+
bestPolicy = policy;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
const selectedPolicy = bestPolicy;
|
|
450
|
+
const selectedTraining = bestTraining;
|
|
451
|
+
const heldOut = evaluateMemoryPromotionBenchmark(heldOutCases, selectedPolicy);
|
|
452
|
+
const baselines = [POSITIVE_FEEDBACK_BASELINE, METADATA_BASELINE].map((policy) => {
|
|
453
|
+
const baselineHeldOut = evaluateMemoryPromotionBenchmark(heldOutCases, policy);
|
|
454
|
+
const noWorseThanSelected = heldOut.f1 >= baselineHeldOut.f1 && heldOut.netOutcomeScore >= baselineHeldOut.netOutcomeScore;
|
|
455
|
+
const strictWinMetrics = [];
|
|
456
|
+
if (heldOut.f1 > baselineHeldOut.f1)
|
|
457
|
+
strictWinMetrics.push("f1");
|
|
458
|
+
if (heldOut.netOutcomeScore > baselineHeldOut.netOutcomeScore)
|
|
459
|
+
strictWinMetrics.push("netOutcomeScore");
|
|
460
|
+
if (heldOut.accuracy > baselineHeldOut.accuracy)
|
|
461
|
+
strictWinMetrics.push("accuracy");
|
|
462
|
+
return {
|
|
463
|
+
name: policy.name,
|
|
464
|
+
heldOut: baselineHeldOut,
|
|
465
|
+
noWorseThanSelected,
|
|
466
|
+
strictWin: noWorseThanSelected && strictWinMetrics.length > 0,
|
|
467
|
+
strictWinMetrics,
|
|
468
|
+
};
|
|
469
|
+
});
|
|
470
|
+
const strictlyBeatsBaselines = baselines.every((baseline) => baseline.strictWin);
|
|
471
|
+
return {
|
|
472
|
+
corpusSize: corpus.length,
|
|
473
|
+
trainingSize: trainingCases.length,
|
|
474
|
+
heldOutSize: heldOutCases.length,
|
|
475
|
+
selectedModel: { name: selectedPolicy.name, threshold: selectedPolicy.threshold },
|
|
476
|
+
training: selectedTraining,
|
|
477
|
+
heldOut,
|
|
478
|
+
baselines,
|
|
479
|
+
strictlyBeatsBaselines,
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
function benchmarkCase(name, expectPromote, split, frontmatter, body, feedbackSignals, outcome) {
|
|
483
|
+
return {
|
|
484
|
+
name,
|
|
485
|
+
expectPromote,
|
|
486
|
+
split,
|
|
487
|
+
input: {
|
|
488
|
+
inputRef: `memory:${name}`,
|
|
489
|
+
assetContent: memoryContent(frontmatter, body),
|
|
490
|
+
feedbackEvents: feedbackSignals.map((signal) => ({ metadata: { signal } })),
|
|
491
|
+
},
|
|
492
|
+
...outcome,
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
export const DEFAULT_PROMOTION_POLICY_CORPUS = [
|
|
496
|
+
benchmarkCase("deploy-vpn-required", true, "train", [
|
|
497
|
+
"description: VPN required before deploy",
|
|
498
|
+
"source: skill:deploy",
|
|
499
|
+
"observed_at: 2026-04-20",
|
|
500
|
+
"confidence: 0.95",
|
|
501
|
+
"tags: [deploy, ops]",
|
|
502
|
+
], "Always connect the VPN before starting production deploys.", ["positive", "positive"]),
|
|
503
|
+
benchmarkCase("release-smoke-test", true, "train", [
|
|
504
|
+
"description: Smoke test gates release",
|
|
505
|
+
"quality: curated",
|
|
506
|
+
"source: skill:release",
|
|
507
|
+
"observed_at: 2026-04-18",
|
|
508
|
+
"confidence: 0.85",
|
|
509
|
+
], "Run the smoke test before tagging a release candidate.", ["positive", "positive", "positive"]),
|
|
510
|
+
benchmarkCase("kubernetes-rollout-check", true, "train", [
|
|
511
|
+
"description: Verify rollout status after apply",
|
|
512
|
+
"source: skill:k8s",
|
|
513
|
+
"observed_at: 2026-04-15",
|
|
514
|
+
"confidence: 0.95",
|
|
515
|
+
"tags: [k8s]",
|
|
516
|
+
], "Check rollout status after kubectl apply before declaring success.", ["positive", "positive"]),
|
|
517
|
+
benchmarkCase("incident-channel-rule", true, "train", [
|
|
518
|
+
"description: Incident bridge stays single-threaded",
|
|
519
|
+
"quality: curated",
|
|
520
|
+
"source: skill:incident",
|
|
521
|
+
"observed_at: 2026-04-12",
|
|
522
|
+
"confidence: 0.95",
|
|
523
|
+
], "Keep one operator narrating decisions in the incident bridge to avoid conflicting instructions.", ["positive", "positive", "positive"]),
|
|
524
|
+
benchmarkCase("weak-single-signal", false, "train", [
|
|
525
|
+
"description: VPN required before deploy",
|
|
526
|
+
"source: skill:deploy",
|
|
527
|
+
"observed_at: 2026-04-20",
|
|
528
|
+
"confidence: 0.95",
|
|
529
|
+
"tags: [deploy]",
|
|
530
|
+
], "Always connect the VPN before starting production deploys.", ["positive"]),
|
|
531
|
+
benchmarkCase("contested-fact", false, "train", [
|
|
532
|
+
"description: VPN required before deploy",
|
|
533
|
+
"quality: curated",
|
|
534
|
+
"source: skill:deploy",
|
|
535
|
+
"observed_at: 2026-04-20",
|
|
536
|
+
"confidence: 0.95",
|
|
537
|
+
], "Always connect the VPN before starting production deploys.", ["positive", "negative", "positive"], { falsePromoteCost: 5 }),
|
|
538
|
+
benchmarkCase("tentative-fact", false, "train", ["description: Deploy may require VPN", "source: skill:deploy", "observed_at: 2026-04-20", "confidence: 0.95"], "Maybe connect the VPN before starting production deploys.", ["positive", "positive"]),
|
|
539
|
+
benchmarkCase("subjective-preference", false, "train", [
|
|
540
|
+
"description: VPN required before deploy",
|
|
541
|
+
"subjective: true",
|
|
542
|
+
"source: skill:deploy",
|
|
543
|
+
"observed_at: 2026-04-20",
|
|
544
|
+
"confidence: 0.95",
|
|
545
|
+
], "I prefer connecting the VPN before starting production deploys.", ["positive", "positive"]),
|
|
546
|
+
benchmarkCase("feedback-conflict", false, "train", ["description: VPN required before deploy", "source: skill:deploy", "observed_at: 2026-04-20", "confidence: 0.95"], "Always connect the VPN before starting production deploys.", ["positive", "positive"]),
|
|
547
|
+
{
|
|
548
|
+
name: "feedback-conflict",
|
|
549
|
+
expectPromote: false,
|
|
550
|
+
split: "train",
|
|
551
|
+
input: {
|
|
552
|
+
inputRef: "memory:feedback-conflict",
|
|
553
|
+
assetContent: memoryContent([
|
|
554
|
+
"description: VPN required before deploy",
|
|
555
|
+
"source: skill:deploy",
|
|
556
|
+
"observed_at: 2026-04-20",
|
|
557
|
+
"confidence: 0.95",
|
|
558
|
+
], "Always connect the VPN before starting production deploys."),
|
|
559
|
+
feedbackEvents: [{ metadata: { signal: "positive" } }, { metadata: { signal: "positive", conflict: true } }],
|
|
560
|
+
},
|
|
561
|
+
},
|
|
562
|
+
benchmarkCase("derived-memory", false, "train", ["description: VPN required before deploy", "source: skill:deploy", "confidence: 0.95"], "Always connect the VPN before starting production deploys.", ["positive", "positive"]),
|
|
563
|
+
{
|
|
564
|
+
name: "derived-memory",
|
|
565
|
+
expectPromote: false,
|
|
566
|
+
split: "train",
|
|
567
|
+
input: {
|
|
568
|
+
inputRef: "memory:derived-memory.derived",
|
|
569
|
+
assetContent: memoryContent(["description: VPN required before deploy", "source: skill:deploy", "confidence: 0.95"], "Always connect the VPN before starting production deploys."),
|
|
570
|
+
feedbackEvents: [{ metadata: { signal: "positive" } }, { metadata: { signal: "positive" } }],
|
|
571
|
+
},
|
|
572
|
+
},
|
|
573
|
+
benchmarkCase("staging-cutover-order", true, "train", [
|
|
574
|
+
"description: Cut over staging after migrations",
|
|
575
|
+
"source: skill:database",
|
|
576
|
+
"observed_at: 2026-04-10",
|
|
577
|
+
"confidence: 0.85",
|
|
578
|
+
"tags: [db, deploy]",
|
|
579
|
+
], "Run database migrations before shifting staging traffic onto the new release.", ["positive", "positive", "positive"]),
|
|
580
|
+
benchmarkCase("temporary-token-workaround", false, "train", [
|
|
581
|
+
"description: Temporary deploy token workaround",
|
|
582
|
+
"source: skill:deploy",
|
|
583
|
+
"observed_at: 2026-04-20",
|
|
584
|
+
"confidence: 0.95",
|
|
585
|
+
"expires: 2026-06-01",
|
|
586
|
+
], "Use the temporary deploy token workaround until the incident is closed.", ["positive", "positive"]),
|
|
587
|
+
benchmarkCase("thin-metadata-memory", false, "train", ["description: VPN required before deploy", "source: skill:deploy"], "Always connect the VPN before starting production deploys.", ["positive", "positive"]),
|
|
588
|
+
benchmarkCase("promoted-quality-memory", false, "train", [
|
|
589
|
+
"description: VPN required before deploy",
|
|
590
|
+
"quality: proposed",
|
|
591
|
+
"source: skill:deploy",
|
|
592
|
+
"observed_at: 2026-04-20",
|
|
593
|
+
"confidence: 0.95",
|
|
594
|
+
], "Always connect the VPN before starting production deploys.", ["positive", "positive"]),
|
|
595
|
+
benchmarkCase("kafka-rebalance-note", true, "heldout", [
|
|
596
|
+
"description: Pause consumers during rebalance",
|
|
597
|
+
"quality: curated",
|
|
598
|
+
"source: skill:kafka",
|
|
599
|
+
"observed_at: 2026-04-08",
|
|
600
|
+
"confidence: 0.95",
|
|
601
|
+
"tags: [kafka, ops]",
|
|
602
|
+
], "Pause consumers during partition rebalances to avoid duplicate processing while assignments settle.", ["positive", "positive", "positive"], { promoteValue: 4 }),
|
|
603
|
+
benchmarkCase("gha-token-scope", true, "heldout", [
|
|
604
|
+
"description: Minimize GitHub token scopes",
|
|
605
|
+
"source: skill:github-actions",
|
|
606
|
+
"observed_at: 2026-04-07",
|
|
607
|
+
"confidence: 0.85",
|
|
608
|
+
"tags: [gha, security]",
|
|
609
|
+
], "Use the narrowest GitHub token scope that still allows the workflow step to succeed.", ["positive", "positive"], { promoteValue: 4 }),
|
|
610
|
+
benchmarkCase("helm-debug-guess", false, "heldout", [
|
|
611
|
+
"description: Helm upgrade might need --debug",
|
|
612
|
+
"source: skill:helm",
|
|
613
|
+
"observed_at: 2026-04-05",
|
|
614
|
+
"confidence: 0.85",
|
|
615
|
+
], "It might help to add --debug to helm upgrade output during failures.", ["positive", "positive"]),
|
|
616
|
+
benchmarkCase("terraform-state-location", true, "heldout", [
|
|
617
|
+
"description: Use remote state locks",
|
|
618
|
+
"quality: curated",
|
|
619
|
+
"source: skill:terraform",
|
|
620
|
+
"observed_at: 2026-04-04",
|
|
621
|
+
"confidence: 0.95",
|
|
622
|
+
"tags: [terraform]",
|
|
623
|
+
], "Use remote state with locking enabled before applying shared Terraform stacks.", ["positive", "positive", "positive"]),
|
|
624
|
+
benchmarkCase("mixed-signal-rollback", false, "heldout", [
|
|
625
|
+
"description: Rollback the cluster immediately",
|
|
626
|
+
"quality: curated",
|
|
627
|
+
"source: skill:incident",
|
|
628
|
+
"observed_at: 2026-04-03",
|
|
629
|
+
"confidence: 0.95",
|
|
630
|
+
], "Rollback the cluster immediately after any 5xx spike.", ["positive", "negative", "positive"], { falsePromoteCost: 6 }),
|
|
631
|
+
benchmarkCase("cache-ttl-fact", true, "heldout", [
|
|
632
|
+
"description: Cache TTL defaults to five minutes",
|
|
633
|
+
"source: skill:platform",
|
|
634
|
+
"observed_at: 2026-04-02",
|
|
635
|
+
"confidence: 0.95",
|
|
636
|
+
"tags: [cache, platform]",
|
|
637
|
+
], "The shared platform cache TTL defaults to five minutes unless the service opts out.", ["positive", "positive"]),
|
|
638
|
+
benchmarkCase("personal-shell-alias", false, "heldout", [
|
|
639
|
+
"description: Preferred shell alias",
|
|
640
|
+
"subjective: true",
|
|
641
|
+
"source: skill:shell",
|
|
642
|
+
"observed_at: 2026-04-01",
|
|
643
|
+
"confidence: 0.95",
|
|
644
|
+
], "I prefer aliasing kubectl to k.", ["positive", "positive"]),
|
|
645
|
+
];
|
|
646
|
+
export const DEFAULT_PROMOTION_POLICY_SELECTION = selectPromotionPolicy(DEFAULT_PROMOTION_POLICY_CORPUS);
|
|
647
|
+
const SELECTED_MODEL = CANDIDATE_MODELS.find((model) => model.name === DEFAULT_PROMOTION_POLICY_SELECTION.selectedModel.name);
|
|
648
|
+
export const DEFAULT_PROMOTION_POLICY = {
|
|
649
|
+
name: DEFAULT_PROMOTION_POLICY_SELECTION.selectedModel.name,
|
|
650
|
+
threshold: DEFAULT_PROMOTION_POLICY_SELECTION.selectedModel.threshold,
|
|
651
|
+
assess: (input) => assessWithWeightedModel(input, SELECTED_MODEL, DEFAULT_PROMOTION_POLICY_SELECTION.selectedModel.threshold),
|
|
652
|
+
};
|
|
653
|
+
export function getDefaultPromotionPolicySelection() {
|
|
654
|
+
return DEFAULT_PROMOTION_POLICY_SELECTION;
|
|
655
|
+
}
|
|
656
|
+
export function assessMemoryKnowledgePromotionCandidate(input) {
|
|
657
|
+
return DEFAULT_PROMOTION_POLICY.assess(input);
|
|
658
|
+
}
|