akm-cli 0.7.4 → 0.8.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 → .github/CHANGELOG.md} +34 -1
- package/.github/LICENSE +374 -0
- package/dist/cli/parse-args.js +86 -0
- package/dist/cli.js +1223 -650
- package/dist/commands/agent-dispatch.js +107 -0
- package/dist/commands/agent-support.js +62 -0
- package/dist/commands/config-cli.js +68 -84
- package/dist/commands/consolidate.js +812 -0
- package/dist/commands/curate.js +1 -0
- package/dist/commands/distill-promotion-policy.js +658 -0
- package/dist/commands/distill.js +224 -39
- 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 +1161 -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 +291 -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 +145 -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/vault-key-rules.js +67 -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 +106 -43
- package/dist/commands/reflect.js +167 -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 +135 -55
- 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 +173 -87
- package/dist/core/action-contributors.js +25 -0
- package/dist/core/asset-ref.js +4 -0
- package/dist/core/asset-registry.js +5 -17
- package/dist/core/asset-spec.js +11 -1
- package/dist/core/common.js +100 -0
- package/dist/core/concurrent.js +22 -0
- package/dist/core/config.js +240 -127
- package/dist/core/events.js +87 -123
- package/dist/core/frontmatter.js +0 -6
- 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 +731 -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 +418 -59
- 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 +480 -298
- package/dist/indexer/llm-cache.js +47 -0
- package/dist/indexer/matchers.js +124 -160
- package/dist/indexer/memory-inference.js +63 -29
- package/dist/indexer/metadata-contributors.js +26 -0
- package/dist/indexer/metadata.js +196 -197
- 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/builders.js +109 -0
- package/dist/integrations/agent/config.js +203 -3
- package/dist/integrations/agent/index.js +5 -2
- package/dist/integrations/agent/model-aliases.js +63 -0
- package/dist/integrations/agent/profiles.js +67 -5
- package/dist/integrations/agent/prompts.js +114 -29
- package/dist/integrations/agent/sdk-runner.js +120 -0
- package/dist/integrations/agent/spawn.js +158 -34
- 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 +226 -257
- package/dist/output/shapes.js +109 -96
- package/dist/output/text.js +274 -36
- package/dist/registry/providers/skills-sh.js +61 -49
- package/dist/registry/providers/static-index.js +44 -48
- package/dist/registry/resolve.js +8 -16
- package/dist/setup/setup.js +510 -11
- package/dist/sources/provider-factory.js +2 -1
- package/dist/sources/providers/filesystem.js +16 -23
- package/dist/sources/providers/git.js +45 -4
- package/dist/sources/providers/website.js +15 -22
- 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 +5 -2
- 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
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { computeGraphBoost } from "./graph-boost";
|
|
2
|
+
const TYPE_BOOST = {
|
|
3
|
+
skill: 0.4,
|
|
4
|
+
command: 0.35,
|
|
5
|
+
workflow: 0.35,
|
|
6
|
+
agent: 0.3,
|
|
7
|
+
script: 0.2,
|
|
8
|
+
knowledge: 0.22,
|
|
9
|
+
memory: -0.02,
|
|
10
|
+
};
|
|
11
|
+
const MAX_BOOST_SUM = 3.0;
|
|
12
|
+
const UTILITY_WEIGHT = 0.5;
|
|
13
|
+
const UTILITY_MAX_BOOST = 1.5;
|
|
14
|
+
const RECENCY_DECAY_DAYS = 30;
|
|
15
|
+
function beliefStateBoost(item) {
|
|
16
|
+
const entry = item.entry;
|
|
17
|
+
if (entry.type !== "memory")
|
|
18
|
+
return 0;
|
|
19
|
+
if (entry.beliefState === "contradicted")
|
|
20
|
+
return -0.45;
|
|
21
|
+
if (entry.beliefState === "superseded")
|
|
22
|
+
return -0.25;
|
|
23
|
+
if (entry.beliefState === "archived")
|
|
24
|
+
return -0.6;
|
|
25
|
+
if (entry.beliefState === "active")
|
|
26
|
+
return 0.06;
|
|
27
|
+
return 0;
|
|
28
|
+
}
|
|
29
|
+
const exactNameRankingContributor = {
|
|
30
|
+
name: "exact-name-ranking",
|
|
31
|
+
appliesTo: () => true,
|
|
32
|
+
adjust(item, ctx) {
|
|
33
|
+
const entry = item.entry;
|
|
34
|
+
const nameLower = entry.name.toLowerCase();
|
|
35
|
+
const rawNameBase = nameLower.split("/").pop() ?? nameLower;
|
|
36
|
+
const nameBase = entry.type === "memory" && rawNameBase.endsWith(".derived")
|
|
37
|
+
? rawNameBase.slice(0, -".derived".length)
|
|
38
|
+
: rawNameBase;
|
|
39
|
+
if (nameBase === ctx.queryLower || nameLower === ctx.queryLower) {
|
|
40
|
+
return 2.0;
|
|
41
|
+
}
|
|
42
|
+
if (nameBase.includes(ctx.queryLower) || ctx.queryLower.includes(nameBase)) {
|
|
43
|
+
return 1.0;
|
|
44
|
+
}
|
|
45
|
+
const nameTokens = nameBase.split(/[-_\s]+/).filter(Boolean);
|
|
46
|
+
const matchCount = ctx.queryTokens.filter((qt) => nameTokens.some((nt) => nt === qt || nt.includes(qt))).length;
|
|
47
|
+
return matchCount > 0 ? Math.min(0.9, matchCount * 0.3) : 0;
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
const typeRankingContributor = {
|
|
51
|
+
name: "type-ranking",
|
|
52
|
+
appliesTo: () => true,
|
|
53
|
+
adjust(item) {
|
|
54
|
+
return TYPE_BOOST[item.entry.type] ?? 0;
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
const memoryRankingContributor = {
|
|
58
|
+
name: "memory-ranking",
|
|
59
|
+
appliesTo(item) {
|
|
60
|
+
return item.entry.type === "memory";
|
|
61
|
+
},
|
|
62
|
+
adjust(item) {
|
|
63
|
+
const derivedBoost = item.entry.name.toLowerCase().endsWith(".derived") ? 0.12 : -0.08;
|
|
64
|
+
return derivedBoost + beliefStateBoost(item);
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
const tagRankingContributor = {
|
|
68
|
+
name: "tag-ranking",
|
|
69
|
+
appliesTo(item) {
|
|
70
|
+
return Array.isArray(item.entry.tags) && item.entry.tags.length > 0;
|
|
71
|
+
},
|
|
72
|
+
adjust(item, ctx) {
|
|
73
|
+
let tagBoost = 0;
|
|
74
|
+
for (const tag of item.entry.tags ?? []) {
|
|
75
|
+
if (ctx.queryTokens.some((token) => tag.toLowerCase() === token))
|
|
76
|
+
tagBoost += 0.15;
|
|
77
|
+
}
|
|
78
|
+
return Math.min(0.3, tagBoost);
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
const searchHintRankingContributor = {
|
|
82
|
+
name: "search-hint-ranking",
|
|
83
|
+
appliesTo(item) {
|
|
84
|
+
return Array.isArray(item.entry.searchHints) && item.entry.searchHints.length > 0;
|
|
85
|
+
},
|
|
86
|
+
adjust(item, ctx) {
|
|
87
|
+
let hintBoost = 0;
|
|
88
|
+
for (const hint of item.entry.searchHints ?? []) {
|
|
89
|
+
const hintLower = hint.toLowerCase();
|
|
90
|
+
for (const token of ctx.queryTokens) {
|
|
91
|
+
if (hintLower.includes(token)) {
|
|
92
|
+
hintBoost += 0.12;
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return Math.min(0.24, hintBoost);
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
const aliasRankingContributor = {
|
|
101
|
+
name: "alias-ranking",
|
|
102
|
+
appliesTo(item) {
|
|
103
|
+
return Array.isArray(item.entry.aliases) && item.entry.aliases.length > 0;
|
|
104
|
+
},
|
|
105
|
+
adjust(item, ctx) {
|
|
106
|
+
let boost = 0;
|
|
107
|
+
for (const alias of item.entry.aliases ?? []) {
|
|
108
|
+
const aliasLower = alias.toLowerCase();
|
|
109
|
+
if (aliasLower === ctx.queryLower) {
|
|
110
|
+
boost += 1.5;
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
if (ctx.queryTokens.some((token) => aliasLower.includes(token)))
|
|
114
|
+
boost += 0.3;
|
|
115
|
+
}
|
|
116
|
+
return boost;
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
const descriptionRankingContributor = {
|
|
120
|
+
name: "description-ranking",
|
|
121
|
+
appliesTo(item) {
|
|
122
|
+
return typeof item.entry.description === "string" && item.entry.description.length > 0;
|
|
123
|
+
},
|
|
124
|
+
adjust(item, ctx) {
|
|
125
|
+
const descLower = item.entry.description?.toLowerCase() ?? "";
|
|
126
|
+
const descMatchCount = ctx.queryTokens.filter((token) => descLower.includes(token)).length;
|
|
127
|
+
if (descMatchCount === ctx.queryTokens.length && ctx.queryTokens.length > 1)
|
|
128
|
+
return 0.25;
|
|
129
|
+
if (descMatchCount > 0)
|
|
130
|
+
return 0.1;
|
|
131
|
+
return 0;
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
const metadataRankingContributor = {
|
|
135
|
+
name: "metadata-ranking",
|
|
136
|
+
appliesTo: () => true,
|
|
137
|
+
adjust(item) {
|
|
138
|
+
let boost = item.entry.quality === "curated" ? 0.05 : 0;
|
|
139
|
+
if (typeof item.entry.confidence === "number") {
|
|
140
|
+
boost += Math.min(0.05, Math.max(0, item.entry.confidence) * 0.05);
|
|
141
|
+
}
|
|
142
|
+
return boost;
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
const graphRankingContributor = {
|
|
146
|
+
name: "graph-ranking",
|
|
147
|
+
appliesTo(_item, ctx) {
|
|
148
|
+
return ctx.graphContext !== null;
|
|
149
|
+
},
|
|
150
|
+
adjust(item, ctx) {
|
|
151
|
+
return ctx.graphContext ? computeGraphBoost(ctx.graphContext, item.filePath) : 0;
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
const utilityRankingContributor = {
|
|
155
|
+
name: "utility-ranking",
|
|
156
|
+
appliesTo(item, ctx) {
|
|
157
|
+
const utilScore = ctx.utilityScores.get(item.id);
|
|
158
|
+
return Boolean(utilScore && utilScore.utility > 0);
|
|
159
|
+
},
|
|
160
|
+
apply(item, ctx) {
|
|
161
|
+
const utilScore = ctx.utilityScores.get(item.id);
|
|
162
|
+
if (!utilScore || utilScore.utility <= 0)
|
|
163
|
+
return;
|
|
164
|
+
let recencyFactor = 1;
|
|
165
|
+
if (utilScore.lastUsedAt) {
|
|
166
|
+
const lastUsedMs = new Date(utilScore.lastUsedAt).getTime();
|
|
167
|
+
const daysSinceLastUse = Number.isNaN(lastUsedMs)
|
|
168
|
+
? Infinity
|
|
169
|
+
: Math.max(0, (Date.now() - lastUsedMs) / (1000 * 60 * 60 * 24));
|
|
170
|
+
recencyFactor = Math.exp(-daysSinceLastUse / RECENCY_DECAY_DAYS);
|
|
171
|
+
}
|
|
172
|
+
const rawBoost = 1 + utilScore.utility * recencyFactor * UTILITY_WEIGHT;
|
|
173
|
+
item.score *= Math.min(rawBoost, UTILITY_MAX_BOOST);
|
|
174
|
+
item.utilityBoosted = true;
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
export const defaultRankingContributors = [
|
|
178
|
+
exactNameRankingContributor,
|
|
179
|
+
typeRankingContributor,
|
|
180
|
+
memoryRankingContributor,
|
|
181
|
+
tagRankingContributor,
|
|
182
|
+
searchHintRankingContributor,
|
|
183
|
+
aliasRankingContributor,
|
|
184
|
+
descriptionRankingContributor,
|
|
185
|
+
metadataRankingContributor,
|
|
186
|
+
graphRankingContributor,
|
|
187
|
+
];
|
|
188
|
+
export const defaultUtilityRankingContributors = [utilityRankingContributor];
|
|
189
|
+
export function applyScoreContributors(item, ctx, contributors = defaultRankingContributors) {
|
|
190
|
+
let boostSum = 0;
|
|
191
|
+
for (const contributor of contributors) {
|
|
192
|
+
if (!contributor.appliesTo(item, ctx))
|
|
193
|
+
continue;
|
|
194
|
+
boostSum += contributor.adjust(item, ctx);
|
|
195
|
+
}
|
|
196
|
+
item.score *= 1 + Math.min(boostSum, MAX_BOOST_SUM);
|
|
197
|
+
}
|
|
198
|
+
export function applyUtilityContributors(item, ctx, contributors = defaultUtilityRankingContributors) {
|
|
199
|
+
for (const contributor of contributors) {
|
|
200
|
+
if (!contributor.appliesTo(item, ctx))
|
|
201
|
+
continue;
|
|
202
|
+
contributor.apply(item, ctx);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { getUtilityScoresByIds } from "./db";
|
|
2
|
+
import { applyScoreContributors, applyUtilityContributors } from "./ranking-contributors";
|
|
3
|
+
export function normalizeFtsScores(results) {
|
|
4
|
+
const ftsScoreMap = new Map();
|
|
5
|
+
if (results.length === 0)
|
|
6
|
+
return ftsScoreMap;
|
|
7
|
+
const bestBm25 = results[0].bm25Score;
|
|
8
|
+
const worstBm25 = results[results.length - 1].bm25Score;
|
|
9
|
+
const range = bestBm25 - worstBm25;
|
|
10
|
+
for (const result of results) {
|
|
11
|
+
const normalized = range !== 0 ? (result.bm25Score - worstBm25) / range : 1.0;
|
|
12
|
+
const ftsScore = 0.3 + normalized * 0.7;
|
|
13
|
+
ftsScoreMap.set(result.id, { score: ftsScore, result });
|
|
14
|
+
}
|
|
15
|
+
return ftsScoreMap;
|
|
16
|
+
}
|
|
17
|
+
export function combineSearchScores(options) {
|
|
18
|
+
const FTS_WEIGHT = 0.7;
|
|
19
|
+
const VEC_WEIGHT = 0.3;
|
|
20
|
+
const scored = [];
|
|
21
|
+
const seenIds = new Set();
|
|
22
|
+
for (const [id, { score: ftsScore, result }] of options.ftsScoreMap) {
|
|
23
|
+
seenIds.add(id);
|
|
24
|
+
const embedScore = options.embedScoreMap.get(id);
|
|
25
|
+
const combinedScore = embedScore !== undefined ? ftsScore * FTS_WEIGHT + embedScore * VEC_WEIGHT : ftsScore;
|
|
26
|
+
scored.push({
|
|
27
|
+
id,
|
|
28
|
+
entry: result.entry,
|
|
29
|
+
filePath: result.filePath,
|
|
30
|
+
score: combinedScore,
|
|
31
|
+
rankingMode: embedScore !== undefined ? "hybrid" : "fts",
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
for (const [id, cosine] of options.embedScoreMap) {
|
|
35
|
+
if (seenIds.has(id))
|
|
36
|
+
continue;
|
|
37
|
+
const found = options.getEntryById(id);
|
|
38
|
+
if (!found)
|
|
39
|
+
continue;
|
|
40
|
+
if (options.typeFilter && found.entry.type !== options.typeFilter)
|
|
41
|
+
continue;
|
|
42
|
+
scored.push({
|
|
43
|
+
id,
|
|
44
|
+
entry: found.entry,
|
|
45
|
+
filePath: found.filePath,
|
|
46
|
+
score: cosine * VEC_WEIGHT,
|
|
47
|
+
rankingMode: "semantic",
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
return scored;
|
|
51
|
+
}
|
|
52
|
+
export function applyRankingRules(options) {
|
|
53
|
+
const queryTokens = options.query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
54
|
+
const queryLower = options.query.toLowerCase().trim();
|
|
55
|
+
const rankingContext = {
|
|
56
|
+
db: options.db,
|
|
57
|
+
query: options.query,
|
|
58
|
+
queryLower,
|
|
59
|
+
queryTokens,
|
|
60
|
+
graphContext: options.graphContext,
|
|
61
|
+
};
|
|
62
|
+
for (const item of options.items) {
|
|
63
|
+
applyScoreContributors(item, rankingContext);
|
|
64
|
+
}
|
|
65
|
+
const utilScoresMap = getUtilityScoresByIds(options.db, options.items.map((item) => item.id));
|
|
66
|
+
const utilityContext = {
|
|
67
|
+
...rankingContext,
|
|
68
|
+
utilityScores: utilScoresMap,
|
|
69
|
+
};
|
|
70
|
+
for (const item of options.items) {
|
|
71
|
+
applyUtilityContributors(item, utilityContext);
|
|
72
|
+
}
|
|
73
|
+
return options.items;
|
|
74
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { getRenderer } from "./file-context";
|
|
2
|
+
const rendererSearchHitEnricher = {
|
|
3
|
+
name: "renderer-search-hit-enricher",
|
|
4
|
+
appliesTo(ctx) {
|
|
5
|
+
return ctx.rendererRegistry.rendererNameFor(ctx.type) !== undefined;
|
|
6
|
+
},
|
|
7
|
+
async enrich(hit, ctx) {
|
|
8
|
+
const rendererName = ctx.rendererRegistry.rendererNameFor(ctx.type);
|
|
9
|
+
if (!rendererName)
|
|
10
|
+
return;
|
|
11
|
+
const renderer = await getRenderer(rendererName);
|
|
12
|
+
renderer?.enrichSearchHit?.(hit, ctx.stashDir);
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
export const defaultSearchHitEnrichers = [rendererSearchHitEnricher];
|
|
16
|
+
export async function enrichSearchHit(hit, ctx, enrichers = defaultSearchHitEnrichers) {
|
|
17
|
+
for (const enricher of enrichers) {
|
|
18
|
+
if (!enricher.appliesTo(ctx))
|
|
19
|
+
continue;
|
|
20
|
+
await enricher.enrich(hit, ctx);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { resolveStashDir } from "../core/common";
|
|
4
|
-
import { loadConfig } from "../core/config";
|
|
4
|
+
import { getSources, loadConfig } from "../core/config";
|
|
5
5
|
import { resolveSourceProviderFactory } from "../sources/provider-factory";
|
|
6
6
|
// Eager side-effect imports so all built-in source providers self-register
|
|
7
7
|
// before resolveEntryContentDir() runs.
|
|
@@ -21,7 +21,7 @@ const GIT_STASH_TYPES = new Set(["git"]);
|
|
|
21
21
|
* 1. The primary stash directory (the entry marked `primary: true`, or the
|
|
22
22
|
* legacy top-level `stashDir`). Always emitted, even when the directory
|
|
23
23
|
* does not yet exist on disk, so callers can use it as the clone target.
|
|
24
|
-
* 2. Each entry in `config.sources
|
|
24
|
+
* 2. Each entry in `config.sources[]` (in declared order), excluding the
|
|
25
25
|
* one already emitted as the primary.
|
|
26
26
|
* 3. Each entry in `config.installed[]` (registry-managed stashes).
|
|
27
27
|
*
|
|
@@ -32,9 +32,10 @@ const GIT_STASH_TYPES = new Set(["git"]);
|
|
|
32
32
|
export function resolveSourceEntries(overrideStashDir, existingConfig) {
|
|
33
33
|
const stashDir = overrideStashDir ?? resolveStashDir();
|
|
34
34
|
const config = existingConfig ?? loadConfig();
|
|
35
|
-
|
|
35
|
+
// Primary stash is always writable.
|
|
36
|
+
const sources = [{ path: stashDir, writable: true }];
|
|
36
37
|
const seen = new Set([path.resolve(stashDir)]);
|
|
37
|
-
const addSource = (dir, registryId, wikiName) => {
|
|
38
|
+
const addSource = (dir, registryId, wikiName, writable) => {
|
|
38
39
|
const resolved = path.resolve(dir);
|
|
39
40
|
if (seen.has(resolved))
|
|
40
41
|
return;
|
|
@@ -47,13 +48,14 @@ export function resolveSourceEntries(overrideStashDir, existingConfig) {
|
|
|
47
48
|
path: resolved,
|
|
48
49
|
...(registryId ? { registryId } : {}),
|
|
49
50
|
...(wikiName ? { wikiName } : {}),
|
|
51
|
+
...(writable ? { writable: true } : {}),
|
|
50
52
|
});
|
|
51
53
|
}
|
|
52
54
|
};
|
|
53
55
|
// (1) + (2) Single pass over declared stashes — primary first if present,
|
|
54
56
|
// then the rest in declared order. The primary's directory is already
|
|
55
57
|
// injected as `sources[0]` above, so we only need to dedupe the source set.
|
|
56
|
-
const stashes = config
|
|
58
|
+
const stashes = getSources(config);
|
|
57
59
|
const primaryIdx = stashes.findIndex((entry) => entry.primary === true);
|
|
58
60
|
const ordered = [];
|
|
59
61
|
if (primaryIdx >= 0) {
|
|
@@ -72,11 +74,12 @@ export function resolveSourceEntries(overrideStashDir, existingConfig) {
|
|
|
72
74
|
const dir = resolveEntryContentDir(entry);
|
|
73
75
|
if (dir == null)
|
|
74
76
|
continue;
|
|
75
|
-
addSource(dir, entry.name, entry.wikiName);
|
|
77
|
+
addSource(dir, entry.name, entry.wikiName, entry.writable === true);
|
|
76
78
|
}
|
|
77
79
|
// (3) Installed stashes (registry-managed). Always last.
|
|
80
|
+
// Only installed entries explicitly marked writable: true are considered writable.
|
|
78
81
|
for (const entry of config.installed ?? []) {
|
|
79
|
-
addSource(entry.stashRoot, entry.id, entry.wikiName);
|
|
82
|
+
addSource(entry.stashRoot, entry.id, entry.wikiName, entry.writable === true);
|
|
80
83
|
}
|
|
81
84
|
return sources;
|
|
82
85
|
}
|
|
@@ -132,6 +135,19 @@ function resolveEntryContentDir(entry) {
|
|
|
132
135
|
export function resolveAllStashDirs(overrideStashDir) {
|
|
133
136
|
return resolveSourceEntries(overrideStashDir).map((s) => s.path);
|
|
134
137
|
}
|
|
138
|
+
/**
|
|
139
|
+
* Return the resolved absolute paths of all writable stash sources.
|
|
140
|
+
*
|
|
141
|
+
* The primary stash is always writable. Filesystem/git sources that have
|
|
142
|
+
* `writable: true` in config are also included. Registry-cached sources
|
|
143
|
+
* (installed without `writable: true`) are excluded because they are
|
|
144
|
+
* overwritten on `akm update` and must never be mutated.
|
|
145
|
+
*/
|
|
146
|
+
export function getWritableStashDirs(overrideStashDir, existingConfig) {
|
|
147
|
+
return resolveSourceEntries(overrideStashDir, existingConfig)
|
|
148
|
+
.filter((s) => s.writable === true)
|
|
149
|
+
.map((s) => s.path);
|
|
150
|
+
}
|
|
135
151
|
/**
|
|
136
152
|
* Find which source a file path belongs to.
|
|
137
153
|
*/
|
|
@@ -226,8 +242,7 @@ function isValidDirectory(dir) {
|
|
|
226
242
|
export async function ensureSourceCaches(config, options) {
|
|
227
243
|
const cfg = config ?? loadConfig();
|
|
228
244
|
const force = options?.force === true;
|
|
229
|
-
|
|
230
|
-
const entries = cfg.sources ?? cfg.stashes ?? [];
|
|
245
|
+
const entries = getSources(cfg);
|
|
231
246
|
for (const entry of entries) {
|
|
232
247
|
if (!GIT_STASH_TYPES.has(entry.type) || !entry.url || entry.enabled === false)
|
|
233
248
|
continue;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
-
import
|
|
2
|
+
import { writeFileAtomic } from "../core/common";
|
|
3
3
|
import { getCacheDir, getSemanticStatusPath } from "../core/paths";
|
|
4
4
|
import { DEFAULT_LOCAL_MODEL } from "../llm/embedder";
|
|
5
5
|
export function deriveSemanticProviderFingerprint(embedding) {
|
|
@@ -41,21 +41,7 @@ export function readSemanticStatus() {
|
|
|
41
41
|
export function writeSemanticStatus(status) {
|
|
42
42
|
const dir = getCacheDir();
|
|
43
43
|
fs.mkdirSync(dir, { recursive: true });
|
|
44
|
-
|
|
45
|
-
const tmpPath = path.join(dir, `semantic-status.json.tmp.${process.pid}.${Math.random().toString(36).slice(2)}`);
|
|
46
|
-
fs.writeFileSync(tmpPath, `${JSON.stringify(status, null, 2)}\n`, "utf8");
|
|
47
|
-
try {
|
|
48
|
-
fs.renameSync(tmpPath, filePath);
|
|
49
|
-
}
|
|
50
|
-
catch (err) {
|
|
51
|
-
try {
|
|
52
|
-
fs.unlinkSync(tmpPath);
|
|
53
|
-
}
|
|
54
|
-
catch {
|
|
55
|
-
/* ignore cleanup failure */
|
|
56
|
-
}
|
|
57
|
-
throw err;
|
|
58
|
-
}
|
|
44
|
+
writeFileAtomic(getSemanticStatusPath(), `${JSON.stringify(status, null, 2)}\n`);
|
|
59
45
|
}
|
|
60
46
|
export function clearSemanticStatus() {
|
|
61
47
|
try {
|
package/dist/indexer/walker.js
CHANGED
|
@@ -136,6 +136,31 @@ function isInsideGitRepo(dir) {
|
|
|
136
136
|
}
|
|
137
137
|
return false;
|
|
138
138
|
}
|
|
139
|
+
/**
|
|
140
|
+
* Recursively yield every `.md` file under `root`.
|
|
141
|
+
*
|
|
142
|
+
* Shared by graph-extraction and memory-inference so the generator logic
|
|
143
|
+
* lives in exactly one place. Silently skips directories that cannot be
|
|
144
|
+
* read (e.g. permission errors).
|
|
145
|
+
*/
|
|
146
|
+
export function* walkMarkdownFiles(root) {
|
|
147
|
+
let entries;
|
|
148
|
+
try {
|
|
149
|
+
entries = fs.readdirSync(root, { withFileTypes: true });
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
for (const entry of entries) {
|
|
155
|
+
const full = path.join(root, entry.name);
|
|
156
|
+
if (entry.isDirectory()) {
|
|
157
|
+
yield* walkMarkdownFiles(full);
|
|
158
|
+
}
|
|
159
|
+
else if (entry.isFile() && entry.name.toLowerCase().endsWith(".md")) {
|
|
160
|
+
yield full;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
139
164
|
/** Manual walk for non-git directories. */
|
|
140
165
|
function walkStashManual(stashRoot) {
|
|
141
166
|
const results = [];
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent command builder strategy (v1 spec §12.2).
|
|
3
|
+
*
|
|
4
|
+
* Each supported agent CLI platform has its own `AgentCommandBuilder` that
|
|
5
|
+
* translates a platform-agnostic `AgentDispatchRequest` into the exact argv
|
|
6
|
+
* the CLI expects. This keeps all per-platform arg differences out of the
|
|
7
|
+
* spawn wrapper and profiles.
|
|
8
|
+
*
|
|
9
|
+
* Adding a new platform: implement `AgentCommandBuilder`, add to
|
|
10
|
+
* `BUILTIN_BUILDERS`. Nothing else changes.
|
|
11
|
+
*/
|
|
12
|
+
import { resolveModel } from "./model-aliases";
|
|
13
|
+
// ── Tool normalization ────────────────────────────────────────────────────────
|
|
14
|
+
/**
|
|
15
|
+
* Normalize a toolPolicy value to a comma-separated string suitable for a
|
|
16
|
+
* CLI flag. Structured policy objects are JSON-serialized.
|
|
17
|
+
*/
|
|
18
|
+
function normalizeTools(tools) {
|
|
19
|
+
if (typeof tools === "string")
|
|
20
|
+
return tools;
|
|
21
|
+
if (Array.isArray(tools))
|
|
22
|
+
return tools.join(",");
|
|
23
|
+
return JSON.stringify(tools);
|
|
24
|
+
}
|
|
25
|
+
// ── Platform builders ─────────────────────────────────────────────────────────
|
|
26
|
+
/**
|
|
27
|
+
* OpenCode builder.
|
|
28
|
+
* Command shape: opencode run [--system-prompt "..."] [--model <m>] "<prompt>"
|
|
29
|
+
*
|
|
30
|
+
* Tool policy is omitted — opencode manages tool access through its own agent
|
|
31
|
+
* config files, not via CLI flags.
|
|
32
|
+
*/
|
|
33
|
+
const opencodeBuilder = {
|
|
34
|
+
platform: "opencode",
|
|
35
|
+
build(profile, req) {
|
|
36
|
+
const args = [...profile.args]; // starts with ["run"]
|
|
37
|
+
if (req.systemPrompt) {
|
|
38
|
+
args.push("--system-prompt", req.systemPrompt);
|
|
39
|
+
}
|
|
40
|
+
if (req.model) {
|
|
41
|
+
const resolved = resolveModel(req.model, "opencode", profile.modelAliases);
|
|
42
|
+
args.push("--model", resolved);
|
|
43
|
+
}
|
|
44
|
+
args.push(req.prompt);
|
|
45
|
+
return { argv: [profile.bin, ...args] };
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Claude Code builder.
|
|
50
|
+
* Command shape: claude [--system-prompt "..."] [--model <m>] [--allowedTools <t>] --print "<prompt>"
|
|
51
|
+
*
|
|
52
|
+
* --print switches Claude Code to non-interactive captured output mode.
|
|
53
|
+
*/
|
|
54
|
+
const claudeBuilder = {
|
|
55
|
+
platform: "claude",
|
|
56
|
+
build(profile, req) {
|
|
57
|
+
const args = [...profile.args];
|
|
58
|
+
if (req.systemPrompt) {
|
|
59
|
+
args.push("--system-prompt", req.systemPrompt);
|
|
60
|
+
}
|
|
61
|
+
if (req.model) {
|
|
62
|
+
const resolved = resolveModel(req.model, "claude", profile.modelAliases);
|
|
63
|
+
args.push("--model", resolved);
|
|
64
|
+
}
|
|
65
|
+
if (req.tools) {
|
|
66
|
+
args.push("--allowedTools", normalizeTools(req.tools));
|
|
67
|
+
}
|
|
68
|
+
// --print = non-interactive, outputs to stdout — required for captured mode
|
|
69
|
+
args.push("--print");
|
|
70
|
+
args.push(req.prompt);
|
|
71
|
+
return { argv: [profile.bin, ...args] };
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Default builder — used for custom profiles and any platform without a
|
|
76
|
+
* dedicated builder. Passes systemPrompt and model via the same flags as
|
|
77
|
+
* the builtin builders so custom profiles benefit from agent asset metadata.
|
|
78
|
+
* Tools are omitted — no standard cross-platform flag exists.
|
|
79
|
+
*/
|
|
80
|
+
const defaultBuilder = {
|
|
81
|
+
platform: "default",
|
|
82
|
+
build(profile, req) {
|
|
83
|
+
const args = [...profile.args];
|
|
84
|
+
if (req.systemPrompt) {
|
|
85
|
+
args.push("--system-prompt", req.systemPrompt);
|
|
86
|
+
}
|
|
87
|
+
if (req.model) {
|
|
88
|
+
const resolved = resolveModel(req.model, profile.name, profile.modelAliases);
|
|
89
|
+
args.push("--model", resolved);
|
|
90
|
+
}
|
|
91
|
+
args.push(req.prompt);
|
|
92
|
+
return { argv: [profile.bin, ...args] };
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
// ── Registry ──────────────────────────────────────────────────────────────────
|
|
96
|
+
const BUILTIN_BUILDERS = {
|
|
97
|
+
opencode: opencodeBuilder,
|
|
98
|
+
"opencode-headless": opencodeBuilder,
|
|
99
|
+
claude: claudeBuilder,
|
|
100
|
+
"claude-headless": claudeBuilder,
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* Return the builder for the given platform name, falling back to the default
|
|
104
|
+
* builder for unknown platforms. Custom builders injected via tests can be
|
|
105
|
+
* passed as `registry`.
|
|
106
|
+
*/
|
|
107
|
+
export function getCommandBuilder(platform, registry = BUILTIN_BUILDERS) {
|
|
108
|
+
return registry[platform] ?? defaultBuilder;
|
|
109
|
+
}
|