akm-cli 0.8.0-rc2 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{.github/CHANGELOG.md → CHANGELOG.md} +238 -3
- package/README.md +22 -6
- package/SECURITY.md +93 -0
- package/dist/assets/help/help-accept.md +12 -0
- package/dist/assets/help/help-improve.md +81 -0
- package/dist/{commands → assets}/help/help-proposals.md +7 -4
- package/dist/assets/help/help-reject.md +11 -0
- package/dist/{output → assets/hints}/cli-hints-full.md +60 -32
- package/dist/{output → assets/hints}/cli-hints-short.md +10 -7
- package/dist/assets/profiles/default.json +15 -0
- package/dist/assets/profiles/graph-refresh.json +13 -0
- package/dist/assets/profiles/memory-focus.json +12 -0
- package/dist/assets/profiles/quick.json +15 -0
- package/dist/assets/profiles/thorough.json +15 -0
- package/dist/assets/prompts/extract-session.md +80 -0
- package/dist/assets/prompts/graph-extract-user-prompt.md +35 -0
- package/dist/assets/tasks/graph-refresh-weekly.yml +10 -0
- package/dist/cli/config-migrate.js +144 -0
- package/dist/cli/config-validate.js +39 -0
- package/dist/cli/confirm.js +73 -0
- package/dist/cli/parse-args.js +93 -3
- package/dist/cli/shared.js +129 -0
- package/dist/cli.js +2141 -1268
- package/dist/commands/add-cli.js +279 -0
- package/dist/commands/agent-dispatch.js +20 -12
- package/dist/commands/agent-support.js +11 -5
- package/dist/commands/completions.js +3 -0
- package/dist/commands/config-cli.js +129 -517
- package/dist/commands/consolidate.js +1557 -147
- package/dist/commands/curate.js +44 -3
- package/dist/commands/db-cli.js +23 -0
- package/dist/commands/distill-promotion-policy.js +5 -3
- package/dist/commands/distill.js +906 -100
- package/dist/commands/env.js +213 -0
- package/dist/commands/eval-cases.js +3 -0
- package/dist/commands/events.js +3 -0
- package/dist/commands/extract-cli.js +127 -0
- package/dist/commands/extract-prompt.js +217 -0
- package/dist/commands/extract.js +477 -0
- package/dist/commands/feedback-cli.js +331 -0
- package/dist/commands/graph.js +260 -5
- package/dist/commands/health.js +1042 -55
- package/dist/commands/history.js +51 -16
- package/dist/commands/improve-auto-accept.js +97 -0
- package/dist/commands/improve-cli.js +236 -0
- package/dist/commands/improve-profiles.js +138 -0
- package/dist/commands/improve-result-file.js +167 -0
- package/dist/commands/improve.js +1736 -346
- package/dist/commands/info.js +26 -28
- package/dist/commands/init.js +49 -1
- package/dist/commands/installed-stashes.js +6 -23
- package/dist/commands/knowledge.js +3 -0
- package/dist/commands/lint/agent-linter.js +3 -0
- package/dist/commands/lint/base-linter.js +199 -5
- package/dist/commands/lint/command-linter.js +3 -0
- package/dist/commands/lint/default-linter.js +3 -0
- package/dist/commands/lint/env-key-rules.js +154 -0
- package/dist/commands/lint/index.js +92 -3
- package/dist/commands/lint/knowledge-linter.js +3 -0
- package/dist/commands/lint/markdown-insertion.js +343 -0
- package/dist/commands/lint/memory-linter.js +3 -0
- package/dist/commands/lint/registry.js +3 -0
- package/dist/commands/lint/skill-linter.js +3 -0
- package/dist/commands/lint/task-linter.js +15 -12
- package/dist/commands/lint/types.js +3 -0
- package/dist/commands/lint/workflow-linter.js +3 -0
- package/dist/commands/lint.js +3 -0
- package/dist/commands/migration-help.js +5 -2
- package/dist/commands/proposal-drain-policies.js +128 -0
- package/dist/commands/proposal-drain.js +477 -0
- package/dist/commands/proposal.js +60 -6
- package/dist/commands/propose.js +24 -19
- package/dist/commands/reflect.js +1004 -94
- package/dist/commands/registry-cli.js +150 -0
- package/dist/commands/registry-search.js +3 -0
- package/dist/commands/remember-cli.js +257 -0
- package/dist/commands/remember.js +15 -6
- package/dist/commands/schema-repair.js +88 -15
- package/dist/commands/search.js +99 -14
- package/dist/commands/secret.js +173 -0
- package/dist/commands/self-update.js +3 -0
- package/dist/commands/show.js +32 -13
- package/dist/commands/source-add.js +7 -35
- package/dist/commands/source-clone.js +3 -0
- package/dist/commands/source-manage.js +3 -0
- package/dist/commands/tasks.js +161 -95
- package/dist/commands/url-checker.js +3 -0
- package/dist/core/action-contributors.js +3 -0
- package/dist/core/asset-ref.js +13 -2
- package/dist/core/asset-registry.js +9 -2
- package/dist/core/asset-serialize.js +88 -0
- package/dist/core/asset-spec.js +61 -5
- package/dist/core/common.js +93 -5
- package/dist/core/concurrent.js +3 -0
- package/dist/core/config-io.js +347 -0
- package/dist/core/config-migration.js +622 -0
- package/dist/core/config-schema.js +558 -0
- package/dist/core/config-sources.js +108 -0
- package/dist/core/config-types.js +4 -0
- package/dist/core/config-walker.js +337 -0
- package/dist/core/config.js +366 -1077
- package/dist/core/errors.js +42 -20
- package/dist/core/events.js +31 -25
- package/dist/core/file-lock.js +104 -0
- package/dist/core/frontmatter.js +75 -10
- package/dist/core/lesson-lint.js +3 -0
- package/dist/core/markdown.js +3 -0
- package/dist/core/memory-belief.js +62 -0
- package/dist/core/memory-contradiction-detect.js +274 -0
- package/dist/core/memory-improve.js +142 -14
- package/dist/core/parse.js +3 -0
- package/dist/core/paths.js +218 -50
- package/dist/core/proposal-quality-validators.js +380 -0
- package/dist/core/proposal-validators.js +11 -3
- package/dist/core/proposals.js +464 -5
- package/dist/core/state-db.js +349 -56
- package/dist/core/text-truncation.js +107 -0
- package/dist/core/time.js +3 -0
- package/dist/core/tty.js +59 -0
- package/dist/core/warn.js +7 -2
- package/dist/core/write-source.js +12 -0
- package/dist/indexer/db-backup.js +391 -0
- package/dist/indexer/db-search.js +136 -28
- package/dist/indexer/db.js +661 -166
- package/dist/indexer/ensure-index.js +3 -0
- package/dist/indexer/file-context.js +3 -0
- package/dist/indexer/graph-boost.js +162 -40
- package/dist/indexer/graph-db.js +241 -51
- package/dist/indexer/graph-dedup.js +3 -7
- package/dist/indexer/graph-extraction.js +242 -149
- package/dist/indexer/index-context.js +3 -9
- package/dist/indexer/indexer.js +86 -16
- package/dist/indexer/llm-cache.js +24 -19
- package/dist/indexer/manifest.js +3 -0
- package/dist/indexer/matchers.js +184 -11
- package/dist/indexer/memory-inference.js +94 -50
- package/dist/indexer/metadata-contributors.js +3 -0
- package/dist/indexer/metadata.js +110 -50
- package/dist/indexer/path-resolver.js +3 -0
- package/dist/indexer/project-context.js +192 -0
- package/dist/indexer/ranking-contributors.js +134 -7
- package/dist/indexer/ranking.js +8 -1
- package/dist/indexer/search-fields.js +5 -9
- package/dist/indexer/search-hit-enrichers.js +91 -2
- package/dist/indexer/search-source.js +20 -1
- package/dist/indexer/semantic-status.js +4 -1
- package/dist/indexer/staleness-detect.js +447 -0
- package/dist/indexer/usage-events.js +12 -9
- package/dist/indexer/walker.js +3 -0
- package/dist/integrations/agent/builders.js +135 -0
- package/dist/integrations/agent/config.js +121 -401
- package/dist/integrations/agent/detect.js +3 -0
- package/dist/integrations/agent/index.js +6 -14
- package/dist/integrations/agent/model-aliases.js +55 -0
- package/dist/integrations/agent/profiles.js +3 -0
- package/dist/integrations/agent/prompts.js +137 -8
- package/dist/integrations/agent/runner.js +208 -0
- package/dist/integrations/agent/sdk-runner.js +8 -2
- package/dist/integrations/agent/spawn.js +54 -14
- package/dist/integrations/github.js +3 -0
- package/dist/integrations/lockfile.js +22 -51
- package/dist/integrations/session-logs/index.js +4 -0
- package/dist/integrations/session-logs/inline-refs.js +35 -0
- package/dist/integrations/session-logs/pre-filter.js +152 -0
- package/dist/integrations/session-logs/providers/claude-code.js +226 -0
- package/dist/integrations/session-logs/providers/opencode.js +231 -25
- package/dist/integrations/session-logs/types.js +3 -0
- package/dist/llm/call-ai.js +14 -26
- package/dist/llm/client.js +16 -2
- package/dist/llm/embedder.js +20 -29
- package/dist/llm/embedders/cache.js +3 -7
- package/dist/llm/embedders/local.js +42 -1
- package/dist/llm/embedders/remote.js +20 -8
- package/dist/llm/embedders/types.js +3 -7
- package/dist/llm/feature-gate.js +92 -56
- package/dist/llm/graph-extract.js +402 -31
- package/dist/llm/index-passes.js +44 -29
- package/dist/llm/memory-infer.js +30 -2
- package/dist/llm/metadata-enhance.js +3 -7
- package/dist/output/cli-hints.js +7 -4
- package/dist/output/context.js +60 -8
- package/dist/output/renderers.js +170 -194
- package/dist/output/shapes/curate.js +56 -0
- package/dist/output/shapes/distill.js +10 -0
- package/dist/output/shapes/env-list.js +19 -0
- package/dist/output/shapes/events.js +11 -0
- package/dist/output/shapes/helpers.js +424 -0
- package/dist/output/shapes/history.js +7 -0
- package/dist/output/shapes/passthrough.js +105 -0
- package/dist/output/shapes/proposal-accept.js +7 -0
- package/dist/output/shapes/proposal-diff.js +7 -0
- package/dist/output/shapes/proposal-list.js +7 -0
- package/dist/output/shapes/proposal-producer.js +11 -0
- package/dist/output/shapes/proposal-reject.js +7 -0
- package/dist/output/shapes/proposal-show.js +7 -0
- package/dist/output/shapes/registry-search.js +6 -0
- package/dist/output/shapes/registry.js +30 -0
- package/dist/output/shapes/search.js +6 -0
- package/dist/output/shapes/secret-list.js +19 -0
- package/dist/output/shapes/show.js +6 -0
- package/dist/output/shapes/vault-list.js +19 -0
- package/dist/output/shapes.js +51 -549
- package/dist/output/text/add.js +6 -0
- package/dist/output/text/clone.js +6 -0
- package/dist/output/text/config.js +6 -0
- package/dist/output/text/curate.js +6 -0
- package/dist/output/text/distill.js +7 -0
- package/dist/output/text/enable-disable.js +7 -0
- package/dist/output/text/events.js +10 -0
- package/dist/output/text/feedback.js +6 -0
- package/dist/output/text/helpers.js +1059 -0
- package/dist/output/text/history.js +7 -0
- package/dist/output/text/import.js +6 -0
- package/dist/output/text/index.js +6 -0
- package/dist/output/text/info.js +6 -0
- package/dist/output/text/init.js +6 -0
- package/dist/output/text/list.js +6 -0
- package/dist/output/text/proposal-producer.js +8 -0
- package/dist/output/text/proposal.js +12 -0
- package/dist/output/text/registry-commands.js +11 -0
- package/dist/output/text/registry.js +30 -0
- package/dist/output/text/remember.js +6 -0
- package/dist/output/text/remove.js +6 -0
- package/dist/output/text/save.js +6 -0
- package/dist/output/text/search.js +6 -0
- package/dist/output/text/show.js +6 -0
- package/dist/output/text/update.js +6 -0
- package/dist/output/text/upgrade.js +6 -0
- package/dist/output/text/vault.js +16 -0
- package/dist/output/text/wiki.js +15 -0
- package/dist/output/text/workflow.js +14 -0
- package/dist/output/text.js +44 -1329
- package/dist/registry/build-index.js +3 -0
- package/dist/registry/create-provider-registry.js +3 -0
- package/dist/registry/factory.js +4 -1
- package/dist/registry/origin-resolve.js +3 -0
- package/dist/registry/providers/index.js +3 -0
- package/dist/registry/providers/skills-sh.js +11 -2
- package/dist/registry/providers/static-index.js +10 -1
- package/dist/registry/providers/types.js +3 -24
- package/dist/registry/resolve.js +11 -16
- package/dist/registry/types.js +3 -0
- package/dist/scripts/migrate-storage.js +17767 -0
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +9031 -0
- package/dist/scripts/migrations/v16-to-v17.js +141 -0
- package/dist/setup/detect.js +3 -0
- package/dist/setup/ripgrep-install.js +3 -0
- package/dist/setup/ripgrep-resolve.js +3 -0
- package/dist/setup/setup.js +306 -67
- package/dist/setup/steps.js +3 -15
- package/dist/sources/include.js +3 -0
- package/dist/sources/provider-factory.js +3 -11
- package/dist/sources/provider.js +3 -20
- package/dist/sources/providers/filesystem.js +19 -23
- package/dist/sources/providers/git.js +171 -21
- package/dist/sources/providers/index.js +3 -0
- package/dist/sources/providers/install-types.js +3 -13
- package/dist/sources/providers/npm.js +3 -4
- package/dist/sources/providers/provider-utils.js +3 -0
- package/dist/sources/providers/sync-from-ref.js +3 -11
- package/dist/sources/providers/tar-utils.js +3 -0
- package/dist/sources/providers/website.js +18 -22
- package/dist/sources/resolve.js +3 -0
- package/dist/sources/types.js +3 -0
- package/dist/sources/website-ingest.js +3 -0
- package/dist/tasks/backends/cron.js +3 -0
- package/dist/tasks/backends/exec-utils.js +3 -0
- package/dist/tasks/backends/index.js +3 -11
- package/dist/tasks/backends/launchd.js +4 -1
- package/dist/tasks/backends/schtasks.js +4 -1
- package/dist/tasks/parser.js +51 -38
- package/dist/tasks/resolveAkmBin.js +3 -0
- package/dist/tasks/runner.js +35 -9
- package/dist/tasks/schedule.js +20 -1
- package/dist/tasks/schema.js +5 -3
- package/dist/tasks/validator.js +6 -3
- package/dist/version.js +3 -0
- package/dist/wiki/wiki-templates.js +6 -3
- package/dist/wiki/wiki.js +4 -1
- package/dist/workflows/authoring.js +4 -1
- package/dist/workflows/cli.js +3 -0
- package/dist/workflows/db.js +140 -10
- package/dist/workflows/document-cache.js +3 -10
- package/dist/workflows/parser.js +3 -0
- package/dist/workflows/renderer.js +3 -0
- package/dist/workflows/runs.js +18 -1
- package/dist/workflows/schema.js +3 -0
- package/dist/workflows/scope-key.js +3 -0
- package/dist/workflows/validator.js +5 -9
- package/docs/README.md +7 -2
- package/docs/data-and-telemetry.md +225 -0
- package/docs/migration/release-notes/0.7.5.md +2 -2
- package/docs/migration/release-notes/0.8.0.md +57 -5
- package/docs/migration/v0.7-to-v0.8.md +1378 -0
- package/package.json +28 -11
- package/.github/LICENSE +0 -374
- package/dist/commands/help/help-accept.md +0 -9
- package/dist/commands/help/help-improve.md +0 -53
- package/dist/commands/help/help-reject.md +0 -8
- package/dist/commands/install-audit.js +0 -385
- package/dist/commands/vault.js +0 -310
- package/dist/indexer/match-contributors.js +0 -141
- package/dist/integrations/agent/pipeline.js +0 -39
- package/dist/integrations/agent/runners.js +0 -31
- package/dist/llm/prompts/graph-extract-user-prompt.md +0 -12
- /package/dist/{tasks → assets}/backends/launchd-template.xml +0 -0
- /package/dist/{tasks → assets}/backends/schtasks-template.xml +0 -0
- /package/dist/{commands → assets}/help/help-propose.md +0 -0
- /package/dist/{wiki → assets/wiki}/index-template.md +0 -0
- /package/dist/{wiki → assets/wiki}/ingest-workflow-template.md +0 -0
- /package/dist/{wiki → assets/wiki}/log-template.md +0 -0
- /package/dist/{wiki → assets/wiki}/schema-template.md +0 -0
- /package/dist/{workflows → assets/workflows}/workflow-template.md +0 -0
package/dist/commands/history.js
CHANGED
|
@@ -1,21 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
*
|
|
5
|
-
* Event sources:
|
|
6
|
-
* - `usage_events` SQLite table: search, show, and feedback events recorded
|
|
7
|
-
* by the local indexer during normal CLI use.
|
|
8
|
-
* - `events.jsonl` append-only stream (opt-in via `--include-proposals`):
|
|
9
|
-
* proposal lifecycle events (`promoted`, `rejected`) emitted by
|
|
10
|
-
* `akm proposal accept` / `akm proposal reject`. Use this flag to see
|
|
11
|
-
* the full proposal review trail alongside usage events.
|
|
12
|
-
*
|
|
13
|
-
* The two sources are merged and sorted chronologically (oldest first) so
|
|
14
|
-
* consumers see a coherent lifecycle trail in a single output.
|
|
15
|
-
*/
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
16
4
|
import { parseAssetRef } from "../core/asset-ref";
|
|
17
5
|
import { UsageError } from "../core/errors";
|
|
18
6
|
import { readEvents } from "../core/events";
|
|
7
|
+
import { listProposals } from "../core/proposals";
|
|
19
8
|
import { isoToSqlite, parseSinceToIso } from "../core/time";
|
|
20
9
|
import { closeDatabase, openExistingDatabase } from "../indexer/db";
|
|
21
10
|
// Proposal lifecycle event types emitted by the proposal substrate (#225).
|
|
@@ -39,6 +28,7 @@ function toEntry(row) {
|
|
|
39
28
|
entryId: row.entry_id,
|
|
40
29
|
query: row.query,
|
|
41
30
|
signal: row.signal,
|
|
31
|
+
source: row.source,
|
|
42
32
|
metadata: parseMetadata(row.metadata),
|
|
43
33
|
createdAt: row.created_at,
|
|
44
34
|
};
|
|
@@ -91,8 +81,12 @@ export async function akmHistory(options = {}) {
|
|
|
91
81
|
conditions.push("created_at >= ?");
|
|
92
82
|
params.push(sinceNormalized);
|
|
93
83
|
}
|
|
84
|
+
if (options.source !== undefined) {
|
|
85
|
+
conditions.push("source = ?");
|
|
86
|
+
params.push(options.source);
|
|
87
|
+
}
|
|
94
88
|
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
95
|
-
const sql = `SELECT id, event_type, query, entry_id, entry_ref, signal, metadata, created_at
|
|
89
|
+
const sql = `SELECT id, event_type, query, entry_id, entry_ref, signal, metadata, source, created_at
|
|
96
90
|
FROM usage_events ${where}
|
|
97
91
|
ORDER BY id ASC`;
|
|
98
92
|
const rows = db.prepare(sql).all(...params);
|
|
@@ -127,6 +121,7 @@ export async function akmHistory(options = {}) {
|
|
|
127
121
|
entryId: null,
|
|
128
122
|
query: null,
|
|
129
123
|
signal: null,
|
|
124
|
+
source: null,
|
|
130
125
|
metadata: event.metadata ?? null,
|
|
131
126
|
createdAt,
|
|
132
127
|
});
|
|
@@ -142,6 +137,45 @@ export async function akmHistory(options = {}) {
|
|
|
142
137
|
return 1;
|
|
143
138
|
return a.id - b.id;
|
|
144
139
|
});
|
|
140
|
+
// ── Accept-rate-per-source (F-4 / #385) ─────────────────────────────────
|
|
141
|
+
let acceptRateBySource;
|
|
142
|
+
if (options.acceptRateBySource) {
|
|
143
|
+
const stashDir = options.stashDir;
|
|
144
|
+
if (stashDir) {
|
|
145
|
+
const bySource = new Map();
|
|
146
|
+
const countProposals = (statuses, includeArchive) => {
|
|
147
|
+
for (const status of statuses) {
|
|
148
|
+
const proposals = listProposals(stashDir, { status, includeArchive });
|
|
149
|
+
for (const p of proposals) {
|
|
150
|
+
const src = p.source || "(unknown)";
|
|
151
|
+
const entry = bySource.get(src) ?? { accepted: 0, rejected: 0, pending: 0 };
|
|
152
|
+
if (status === "accepted")
|
|
153
|
+
entry.accepted++;
|
|
154
|
+
else if (status === "rejected")
|
|
155
|
+
entry.rejected++;
|
|
156
|
+
else
|
|
157
|
+
entry.pending++;
|
|
158
|
+
bySource.set(src, entry);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
countProposals(["pending"], false);
|
|
163
|
+
countProposals(["accepted", "rejected"], true);
|
|
164
|
+
acceptRateBySource = Array.from(bySource.entries())
|
|
165
|
+
.map(([source, counts]) => {
|
|
166
|
+
const decided = counts.accepted + counts.rejected;
|
|
167
|
+
return {
|
|
168
|
+
source,
|
|
169
|
+
total: decided + counts.pending,
|
|
170
|
+
accepted: counts.accepted,
|
|
171
|
+
rejected: counts.rejected,
|
|
172
|
+
pending: counts.pending,
|
|
173
|
+
acceptRate: decided > 0 ? counts.accepted / decided : null,
|
|
174
|
+
};
|
|
175
|
+
})
|
|
176
|
+
.sort((a, b) => b.total - a.total); // Most active source first
|
|
177
|
+
}
|
|
178
|
+
}
|
|
145
179
|
const response = {
|
|
146
180
|
schemaVersion: 1,
|
|
147
181
|
...(normalizedRef !== undefined ? { ref: normalizedRef } : {}),
|
|
@@ -149,6 +183,7 @@ export async function akmHistory(options = {}) {
|
|
|
149
183
|
totalCount: entries.length,
|
|
150
184
|
entries,
|
|
151
185
|
sources,
|
|
186
|
+
...(acceptRateBySource !== undefined ? { acceptRateBySource } : {}),
|
|
152
187
|
};
|
|
153
188
|
return response;
|
|
154
189
|
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
import { loadConfig } from "../core/config";
|
|
5
|
+
import { appendEvent } from "../core/events";
|
|
6
|
+
import { promoteProposal } from "../core/proposals";
|
|
7
|
+
import { info, warn } from "../core/warn";
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Gate implementation
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
/**
|
|
12
|
+
* Attempt to auto-accept each candidate proposal whose confidence meets the
|
|
13
|
+
* effective threshold. Safe to call unconditionally — returns all-empty when
|
|
14
|
+
* the gate is disabled or the run is a dry-run.
|
|
15
|
+
*
|
|
16
|
+
* @param candidates Proposals to evaluate, each with an optional confidence.
|
|
17
|
+
* @param cfg Gate configuration (phase label, thresholds, context).
|
|
18
|
+
* @param promoteFn Injectable override for `promoteProposal` (test seam).
|
|
19
|
+
*/
|
|
20
|
+
export async function runAutoAcceptGate(candidates, cfg, promoteFn = promoteProposal) {
|
|
21
|
+
const result = { promoted: [], skipped: [], failed: [] };
|
|
22
|
+
// --- Guard: gate is disabled or context is incomplete ---
|
|
23
|
+
if (cfg.dryRun || cfg.globalThreshold === undefined || !cfg.stashDir) {
|
|
24
|
+
result.skipped = candidates.map((c) => c.proposalId);
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
const effectiveThreshold = Math.max(cfg.globalThreshold, cfg.minimumThreshold ?? 0) / 100;
|
|
28
|
+
const resolvedConfig = typeof cfg.config === "function" ? cfg.config() : cfg.config;
|
|
29
|
+
for (const candidate of candidates) {
|
|
30
|
+
const { proposalId, confidence } = candidate;
|
|
31
|
+
if (confidence === undefined || confidence < effectiveThreshold) {
|
|
32
|
+
result.skipped.push(proposalId);
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
const promotion = await promoteFn(cfg.stashDir, resolvedConfig, proposalId, {}, undefined);
|
|
37
|
+
appendEvent({
|
|
38
|
+
eventType: "promoted",
|
|
39
|
+
ref: promotion.ref,
|
|
40
|
+
metadata: {
|
|
41
|
+
proposalId: promotion.proposal.id,
|
|
42
|
+
source: promotion.proposal.source,
|
|
43
|
+
...(promotion.proposal.sourceRun !== undefined ? { sourceRun: promotion.proposal.sourceRun } : {}),
|
|
44
|
+
assetPath: promotion.assetPath,
|
|
45
|
+
autoAccept: true,
|
|
46
|
+
confidence,
|
|
47
|
+
threshold: effectiveThreshold,
|
|
48
|
+
phase: cfg.phase,
|
|
49
|
+
},
|
|
50
|
+
}, cfg.eventsCtx ?? {});
|
|
51
|
+
info(`[improve] auto-accepted ${promotion.ref} (${cfg.phase}; confidence=${confidence.toFixed(2)} >= threshold=${effectiveThreshold.toFixed(2)})`);
|
|
52
|
+
result.promoted.push(proposalId);
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
warn(`[improve] ${cfg.phase} auto-accept failed for ${proposalId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
56
|
+
result.failed.push(proposalId);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
// Confidence resolvers
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
/**
|
|
65
|
+
* Read the confidence value for an extract proposal.
|
|
66
|
+
* Extract stores confidence at `payload.frontmatter.confidence` (set by
|
|
67
|
+
* extract.ts when the LLM response is parsed), not at the top-level field.
|
|
68
|
+
*/
|
|
69
|
+
export function resolveExtractConfidence(proposal) {
|
|
70
|
+
const fm = proposal.payload.frontmatter;
|
|
71
|
+
const fmConf = fm?.confidence;
|
|
72
|
+
if (typeof fmConf === "number")
|
|
73
|
+
return fmConf;
|
|
74
|
+
// Fall back to top-level in case a future extract version normalises the path
|
|
75
|
+
if (typeof proposal.confidence === "number")
|
|
76
|
+
return proposal.confidence;
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
// Config builder helpers
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
/**
|
|
83
|
+
* Build a gate config for a phase, inheriting global settings from the
|
|
84
|
+
* improve options. Callers supply only the phase-specific overrides.
|
|
85
|
+
*/
|
|
86
|
+
export function makeGateConfig(phase, shared, overrides = {}) {
|
|
87
|
+
return {
|
|
88
|
+
phase,
|
|
89
|
+
globalThreshold: shared.globalThreshold,
|
|
90
|
+
dryRun: shared.dryRun,
|
|
91
|
+
stashDir: shared.stashDir,
|
|
92
|
+
config: shared.config,
|
|
93
|
+
eventsCtx: shared.eventsCtx,
|
|
94
|
+
...overrides,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
export { loadConfig };
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { defineCommand } from "citty";
|
|
6
|
+
import { getStringArg, parseAutoAcceptFlag, parseNonNegativeIntFlag, parsePositiveIntFlag } from "../cli/parse-args";
|
|
7
|
+
import { output, runWithJsonErrors } from "../cli/shared";
|
|
8
|
+
import { loadConfig } from "../core/config";
|
|
9
|
+
import { UsageError } from "../core/errors";
|
|
10
|
+
import { getCacheDir } from "../core/paths";
|
|
11
|
+
import { clearLogFile, setLogFile } from "../core/warn";
|
|
12
|
+
import { resolveSourceEntries } from "../indexer/search-source";
|
|
13
|
+
import { getHyphenatedArg, getHyphenatedBoolean, parseFlagValue } from "../output/context";
|
|
14
|
+
import { akmImprove } from "./improve";
|
|
15
|
+
import { buildImproveRunId, recordTerminatedImproveRun, relativeImproveResultPath, writeImproveResultFile, } from "./improve-result-file";
|
|
16
|
+
export const improveCommand = defineCommand({
|
|
17
|
+
meta: {
|
|
18
|
+
name: "improve",
|
|
19
|
+
description: "Analyze existing AKM assets and generate improvement proposals; also consolidates memories when profiles.improve.default.processes.consolidate.enabled is true",
|
|
20
|
+
},
|
|
21
|
+
args: {
|
|
22
|
+
scope: {
|
|
23
|
+
type: "positional",
|
|
24
|
+
description: "Optional asset type or asset ref to improve",
|
|
25
|
+
required: false,
|
|
26
|
+
},
|
|
27
|
+
task: { type: "string", description: "Add extra guidance for this improvement pass" },
|
|
28
|
+
"dry-run": { type: "boolean", description: "Show planned actions without writing", default: false },
|
|
29
|
+
target: { type: "string", description: "Override the write target for accepted proposals" },
|
|
30
|
+
"auto-accept": {
|
|
31
|
+
type: "string",
|
|
32
|
+
description: "Auto-accept proposals at or above this confidence threshold (0-100). Default: disabled. Pass a value 0-100 to enable. 'safe' is an alias for 90. Pass 'false' to be explicit.",
|
|
33
|
+
},
|
|
34
|
+
limit: { type: "string", description: "Maximum number of assets to process (highest utility first)" },
|
|
35
|
+
"timeout-ms": {
|
|
36
|
+
type: "string",
|
|
37
|
+
description: "Wall-clock budget for the entire run in milliseconds (default: 7200000 = 2 hours)",
|
|
38
|
+
},
|
|
39
|
+
"consolidate-recovery": {
|
|
40
|
+
type: "string",
|
|
41
|
+
description: "How to handle stale/incomplete consolidation journals: abort (default) or clean (remove stale journal artifacts)",
|
|
42
|
+
},
|
|
43
|
+
"require-feedback-signal": {
|
|
44
|
+
type: "boolean",
|
|
45
|
+
description: "Only process assets with recent feedback signals (disables retrieval fallback)",
|
|
46
|
+
default: false,
|
|
47
|
+
},
|
|
48
|
+
"min-retrieval-count": {
|
|
49
|
+
type: "string",
|
|
50
|
+
description: "Minimum retrieval count for zero-feedback fallback eligibility (default: 1, set 0 to include all assets regardless of retrieval history)",
|
|
51
|
+
},
|
|
52
|
+
"json-to-stdout": {
|
|
53
|
+
type: "boolean",
|
|
54
|
+
description: "Emit the full JSON result on stdout (legacy behaviour). (0.8.0+: full result is recorded in the improve_runs table of state.db and stdout is empty; use this flag for the prior behaviour, e.g. `akm improve --json-to-stdout | jq`.)",
|
|
55
|
+
default: false,
|
|
56
|
+
},
|
|
57
|
+
profile: {
|
|
58
|
+
type: "string",
|
|
59
|
+
description: "Named improve profile from profiles.improve or built-in profiles (default, quick, thorough, memory-focus, graph-refresh). Controls which sub-processes run and which asset types are processed.",
|
|
60
|
+
},
|
|
61
|
+
sync: {
|
|
62
|
+
type: "boolean",
|
|
63
|
+
description: "Commit (and optionally push) the git-backed primary stash when the run finishes. Use --no-sync to disable. Default: on for git-backed stashes (per profile config).",
|
|
64
|
+
},
|
|
65
|
+
push: {
|
|
66
|
+
type: "boolean",
|
|
67
|
+
description: "Push after the end-of-run sync commit when writable + remote configured. Use --no-push to commit only. Default: per profile config (true).",
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
async run({ args }) {
|
|
71
|
+
await runWithJsonErrors(async () => {
|
|
72
|
+
const formatFlagValue = parseFlagValue(process.argv, "--format");
|
|
73
|
+
if (formatFlagValue !== undefined) {
|
|
74
|
+
throw new UsageError(`akm improve does not accept --format. That flag controls output formatting for other commands (search, show, etc.).\n` +
|
|
75
|
+
`Did you mean: akm improve (no --format flag)?`, "INVALID_FLAG_VALUE");
|
|
76
|
+
}
|
|
77
|
+
const jsonToStdout = getHyphenatedBoolean(args, "json-to-stdout");
|
|
78
|
+
const autoAcceptRaw = getHyphenatedArg(args, "auto-accept");
|
|
79
|
+
const autoAccept = parseAutoAcceptFlag(autoAcceptRaw);
|
|
80
|
+
const targetArg = getStringArg(args, "target");
|
|
81
|
+
const taskArg = getStringArg(args, "task");
|
|
82
|
+
const dryRun = getHyphenatedBoolean(args, "dry-run");
|
|
83
|
+
const limitRaw = parsePositiveIntFlag(args.limit ?? undefined);
|
|
84
|
+
const timeoutMs = parsePositiveIntFlag(getHyphenatedArg(args, "timeout-ms"), "--timeout-ms");
|
|
85
|
+
const consolidateRecoveryRaw = getHyphenatedArg(args, "consolidate-recovery");
|
|
86
|
+
const consolidateRecovery = consolidateRecoveryRaw === undefined
|
|
87
|
+
? undefined
|
|
88
|
+
: consolidateRecoveryRaw.trim().toLowerCase();
|
|
89
|
+
if (consolidateRecovery !== undefined && consolidateRecovery !== "abort" && consolidateRecovery !== "clean") {
|
|
90
|
+
throw new UsageError(`Invalid --consolidate-recovery value: "${consolidateRecoveryRaw}". Must be one of: abort, clean.`, "INVALID_FLAG_VALUE");
|
|
91
|
+
}
|
|
92
|
+
const minRetrievalCountRaw = getHyphenatedArg(args, "min-retrieval-count");
|
|
93
|
+
const minRetrievalCount = parseNonNegativeIntFlag(minRetrievalCountRaw, "--min-retrieval-count");
|
|
94
|
+
const requireFeedbackSignal = getHyphenatedBoolean(args, "require-feedback-signal");
|
|
95
|
+
const profileArg = getStringArg(args, "profile");
|
|
96
|
+
// Only set the keys the user actually passed (citty leaves the flag
|
|
97
|
+
// undefined unless `--sync`/`--no-sync` / `--push`/`--no-push` appears),
|
|
98
|
+
// so the resolved profile `sync` block wins by default.
|
|
99
|
+
const syncFlag = getHyphenatedArg(args, "sync");
|
|
100
|
+
const pushFlag = getHyphenatedArg(args, "push");
|
|
101
|
+
const syncOverride = {};
|
|
102
|
+
if (syncFlag !== undefined)
|
|
103
|
+
syncOverride.enabled = syncFlag;
|
|
104
|
+
if (pushFlag !== undefined)
|
|
105
|
+
syncOverride.push = pushFlag;
|
|
106
|
+
const improveLogFile = path.join(getCacheDir(), "logs", "improve", `${new Date().toISOString().replace(/[:.]/g, "-")}.log`);
|
|
107
|
+
setLogFile(improveLogFile);
|
|
108
|
+
const startedAtMs = Date.now();
|
|
109
|
+
const startedAtIso = new Date(startedAtMs).toISOString();
|
|
110
|
+
// Mint the run-id up front so signal handlers can persist a partial
|
|
111
|
+
// record if the process is killed mid-run. Pre-2026-05-26 the runId
|
|
112
|
+
// was minted at end-of-run, so SIGTERM'd runs (cron timeout) left no
|
|
113
|
+
// row in improve_runs and effectively disappeared from `akm health`.
|
|
114
|
+
const runId = buildImproveRunId();
|
|
115
|
+
const primaryStashDir = resolveSourceEntries(undefined, loadConfig())[0]?.path;
|
|
116
|
+
const scopeArg = getStringArg(args, "scope");
|
|
117
|
+
const inferredScopeMode = (scopeArg ?? "").includes(":") ? "ref" : scopeArg ? "type" : "all";
|
|
118
|
+
// Signal handler + exception path both flow through this helper so
|
|
119
|
+
// every abnormal termination produces a row with ok:false and a
|
|
120
|
+
// reason in metadata.terminated.
|
|
121
|
+
let runRecorded = false;
|
|
122
|
+
const persistTerminated = (reason, errorMessage) => {
|
|
123
|
+
if (runRecorded)
|
|
124
|
+
return;
|
|
125
|
+
if (!primaryStashDir)
|
|
126
|
+
return;
|
|
127
|
+
runRecorded = true;
|
|
128
|
+
try {
|
|
129
|
+
recordTerminatedImproveRun(primaryStashDir, runId, startedAtIso, reason, {
|
|
130
|
+
scopeMode: inferredScopeMode,
|
|
131
|
+
scopeValue: scopeArg ?? null,
|
|
132
|
+
dryRun: Boolean(dryRun),
|
|
133
|
+
profile: profileArg ?? null,
|
|
134
|
+
...(errorMessage ? { errorMessage } : {}),
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
process.stderr.write(`warning: failed to persist terminated improve run ${runId}: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
const sigtermHandler = () => {
|
|
142
|
+
persistTerminated("SIGTERM");
|
|
143
|
+
process.stderr.write(`[improve] received SIGTERM; recorded terminated run ${runId}\n`);
|
|
144
|
+
process.exit(143);
|
|
145
|
+
};
|
|
146
|
+
const sigintHandler = () => {
|
|
147
|
+
persistTerminated("SIGINT");
|
|
148
|
+
process.stderr.write(`[improve] received SIGINT; recorded terminated run ${runId}\n`);
|
|
149
|
+
process.exit(130);
|
|
150
|
+
};
|
|
151
|
+
const sighupHandler = () => {
|
|
152
|
+
persistTerminated("SIGHUP");
|
|
153
|
+
process.exit(129);
|
|
154
|
+
};
|
|
155
|
+
process.once("SIGTERM", sigtermHandler);
|
|
156
|
+
process.once("SIGINT", sigintHandler);
|
|
157
|
+
process.once("SIGHUP", sighupHandler);
|
|
158
|
+
let improveResult;
|
|
159
|
+
try {
|
|
160
|
+
improveResult = await akmImprove({
|
|
161
|
+
scope: scopeArg,
|
|
162
|
+
task: taskArg,
|
|
163
|
+
dryRun,
|
|
164
|
+
target: targetArg,
|
|
165
|
+
autoAccept,
|
|
166
|
+
...(limitRaw !== undefined ? { limit: limitRaw } : {}),
|
|
167
|
+
...(timeoutMs !== undefined ? { timeoutMs } : {}),
|
|
168
|
+
...(minRetrievalCount !== undefined ? { minRetrievalCount } : {}),
|
|
169
|
+
...(requireFeedbackSignal ? { requireFeedbackSignal } : {}),
|
|
170
|
+
...(profileArg !== undefined ? { profile: profileArg } : {}),
|
|
171
|
+
...(Object.keys(syncOverride).length > 0 ? { sync: syncOverride } : {}),
|
|
172
|
+
consolidateOptions: {
|
|
173
|
+
target: targetArg,
|
|
174
|
+
dryRun,
|
|
175
|
+
autoAccept,
|
|
176
|
+
task: taskArg,
|
|
177
|
+
...(consolidateRecovery !== undefined ? { recoveryMode: consolidateRecovery } : {}),
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
catch (err) {
|
|
182
|
+
// akmImprove threw — record the failure before letting runWithJsonErrors
|
|
183
|
+
// emit the standard JSON error envelope. Without this, exceptions in
|
|
184
|
+
// the main loop (LLM provider crash, OOM, etc.) leave no improve_runs
|
|
185
|
+
// row, matching the SIGTERM gap.
|
|
186
|
+
persistTerminated("exception", err instanceof Error ? err.message : String(err));
|
|
187
|
+
throw err;
|
|
188
|
+
}
|
|
189
|
+
finally {
|
|
190
|
+
process.removeListener("SIGTERM", sigtermHandler);
|
|
191
|
+
process.removeListener("SIGINT", sigintHandler);
|
|
192
|
+
process.removeListener("SIGHUP", sighupHandler);
|
|
193
|
+
clearLogFile();
|
|
194
|
+
}
|
|
195
|
+
const durationMs = Date.now() - startedAtMs;
|
|
196
|
+
if (jsonToStdout) {
|
|
197
|
+
// Legacy / escape-hatch mode: full JSON on stdout, no file write.
|
|
198
|
+
// Kept for scripts/agents that already pipe to jq.
|
|
199
|
+
output("improve", improveResult);
|
|
200
|
+
process.exit(0);
|
|
201
|
+
}
|
|
202
|
+
// Default mode (0.8.0+): persist the full result as a row in the
|
|
203
|
+
// `improve_runs` table of state.db (migration 003) and emit NOTHING
|
|
204
|
+
// on stdout. The verbose JSON would otherwise scroll earlier progress
|
|
205
|
+
// logs out of the terminal buffer. The existing `[improve] ...`
|
|
206
|
+
// progress log lines on stderr remain the canonical console UX —
|
|
207
|
+
// do NOT add any new console output here.
|
|
208
|
+
//
|
|
209
|
+
// Pre-0.8.0 wrote `<stash>/.akm/runs/<run-id>/improve-result.json`;
|
|
210
|
+
// those files are no longer authored. Query recent runs with:
|
|
211
|
+
// sqlite3 "$AKM_DATA_DIR/state.db" \
|
|
212
|
+
// "SELECT id, started_at, ok, dry_run FROM improve_runs \
|
|
213
|
+
// ORDER BY started_at DESC LIMIT 10"
|
|
214
|
+
// runId + primaryStashDir minted up-top so signal handlers can record
|
|
215
|
+
// partial runs; reuse them here for the success path.
|
|
216
|
+
const resultRef = relativeImproveResultPath(runId);
|
|
217
|
+
runRecorded = true; // Suppress any late signal-handler write — the success path owns the row now.
|
|
218
|
+
if (primaryStashDir) {
|
|
219
|
+
try {
|
|
220
|
+
writeImproveResultFile(primaryStashDir, runId, improveResult);
|
|
221
|
+
}
|
|
222
|
+
catch (err) {
|
|
223
|
+
// Stderr warning on the failure path is preferable to crashing
|
|
224
|
+
// the run after all the work has completed.
|
|
225
|
+
process.stderr.write(`warning: failed to record improve run ${resultRef}: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
process.stderr.write(`warning: no writable stash directory resolved; improve result not persisted to state.db (use --json-to-stdout to capture)\n`);
|
|
230
|
+
}
|
|
231
|
+
// durationMs reserved for future use (no console emission today).
|
|
232
|
+
void durationMs;
|
|
233
|
+
process.exit(0);
|
|
234
|
+
});
|
|
235
|
+
},
|
|
236
|
+
});
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
import profileDefault from "../assets/profiles/default.json" with { type: "json" };
|
|
5
|
+
import profileGraphRefresh from "../assets/profiles/graph-refresh.json" with { type: "json" };
|
|
6
|
+
import profileMemoryFocus from "../assets/profiles/memory-focus.json" with { type: "json" };
|
|
7
|
+
import profileQuick from "../assets/profiles/quick.json" with { type: "json" };
|
|
8
|
+
import profileThorough from "../assets/profiles/thorough.json" with { type: "json" };
|
|
9
|
+
import { parseAssetRef } from "../core/asset-ref";
|
|
10
|
+
import { warn } from "../core/warn";
|
|
11
|
+
/** Profile name used as the final fallback when nothing else resolves. */
|
|
12
|
+
const FALLBACK_PROFILE_NAME = "default";
|
|
13
|
+
// Built-in default allowed types per process
|
|
14
|
+
export const DEFAULT_ALLOWED_TYPES = {
|
|
15
|
+
reflect: ["agent", "command", "knowledge", "lesson", "memory", "skill", "wiki", "workflow"],
|
|
16
|
+
distill: ["memory"],
|
|
17
|
+
consolidate: ["memory"],
|
|
18
|
+
};
|
|
19
|
+
// Built-in profiles are loaded from embedded JSON files in src/assets/profiles/.
|
|
20
|
+
// To add a new profile: create a new .json file there, import it above, and add
|
|
21
|
+
// it to this map. No code change needed beyond those two steps.
|
|
22
|
+
const BUILTIN_PROFILES = {
|
|
23
|
+
default: profileDefault,
|
|
24
|
+
quick: profileQuick,
|
|
25
|
+
thorough: profileThorough,
|
|
26
|
+
"memory-focus": profileMemoryFocus,
|
|
27
|
+
"graph-refresh": profileGraphRefresh,
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Default enabled-state for known improve processes when neither the user
|
|
31
|
+
* profile nor the built-in default profile specifies an override.
|
|
32
|
+
*
|
|
33
|
+
* These mirror the legacy `LlmFeatureFlags` defaults so callers that bypass
|
|
34
|
+
* the profile system (rare — most run through `resolveImproveProfile`) get
|
|
35
|
+
* the same answer.
|
|
36
|
+
*/
|
|
37
|
+
const IMPROVE_PROCESS_DEFAULTS = {
|
|
38
|
+
reflect: true,
|
|
39
|
+
distill: true,
|
|
40
|
+
consolidate: true,
|
|
41
|
+
memoryInference: true,
|
|
42
|
+
graphExtraction: true,
|
|
43
|
+
validation: false,
|
|
44
|
+
// session-extraction reads native session files from claude-code / opencode
|
|
45
|
+
// and queues durable-insight proposals. Default on — opt out via
|
|
46
|
+
// profiles.improve.default.processes.extract.enabled: false.
|
|
47
|
+
extract: true,
|
|
48
|
+
// proposal-queue triage drains the standing backlog. Opt-in (default off),
|
|
49
|
+
// like `validation` — needs an explicit `enabled: true`.
|
|
50
|
+
triage: false,
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Compute the effective enabled-state for a named improve process.
|
|
54
|
+
*
|
|
55
|
+
* Resolution order: explicit `profile.processes.<name>.enabled` (boolean) →
|
|
56
|
+
* the built-in {@link IMPROVE_PROCESS_DEFAULTS} fallback → `false`.
|
|
57
|
+
*/
|
|
58
|
+
export function resolveProcessEnabled(processName, profile) {
|
|
59
|
+
const processes = profile.processes;
|
|
60
|
+
const entry = processes?.[processName];
|
|
61
|
+
if (entry && typeof entry.enabled === "boolean")
|
|
62
|
+
return entry.enabled;
|
|
63
|
+
return IMPROVE_PROCESS_DEFAULTS[processName] ?? false;
|
|
64
|
+
}
|
|
65
|
+
function deepMerge(base, override) {
|
|
66
|
+
if (typeof base !== "object" || base === null)
|
|
67
|
+
return override ?? base;
|
|
68
|
+
const result = { ...base };
|
|
69
|
+
for (const key of Object.keys(override)) {
|
|
70
|
+
const ov = override[key];
|
|
71
|
+
// Treat `null` the same as `undefined` so user overrides never wipe a
|
|
72
|
+
// built-in field with `null`. The on-disk parser already strips nulls,
|
|
73
|
+
// but the programmatic API exposes this path and callers occasionally
|
|
74
|
+
// pass JSON-shaped objects with explicit nulls.
|
|
75
|
+
if (ov !== undefined && ov !== null) {
|
|
76
|
+
const bv = base[key];
|
|
77
|
+
if (typeof bv === "object" && bv !== null && typeof ov === "object" && ov !== null && !Array.isArray(bv)) {
|
|
78
|
+
result[key] = deepMerge(bv, ov);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
result[key] = ov;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return result;
|
|
86
|
+
}
|
|
87
|
+
export function resolveImproveProfile(name, config) {
|
|
88
|
+
const requestedName = name ??
|
|
89
|
+
(typeof config.defaults?.improve === "string" ? config.defaults.improve : undefined) ??
|
|
90
|
+
FALLBACK_PROFILE_NAME;
|
|
91
|
+
const hasBuiltin = requestedName in BUILTIN_PROFILES;
|
|
92
|
+
const hasUserDefined = !!config.profiles?.improve?.[requestedName];
|
|
93
|
+
let effectiveName = requestedName;
|
|
94
|
+
if (!hasBuiltin && !hasUserDefined && requestedName !== FALLBACK_PROFILE_NAME) {
|
|
95
|
+
warn(`[akm] Improve profile "${requestedName}" not found in built-ins or config. ` +
|
|
96
|
+
`Falling back to "${FALLBACK_PROFILE_NAME}".`);
|
|
97
|
+
effectiveName = FALLBACK_PROFILE_NAME;
|
|
98
|
+
}
|
|
99
|
+
const builtin = BUILTIN_PROFILES[effectiveName] ?? BUILTIN_PROFILES[FALLBACK_PROFILE_NAME];
|
|
100
|
+
const userOverride = config.profiles?.improve?.[effectiveName] ?? {};
|
|
101
|
+
return deepMerge(builtin, userOverride);
|
|
102
|
+
}
|
|
103
|
+
export function shouldSkipRef(ref, processName, profile) {
|
|
104
|
+
const cfg = profile.processes?.[processName];
|
|
105
|
+
// Check if the process itself is disabled
|
|
106
|
+
if (cfg?.enabled === false)
|
|
107
|
+
return { skip: true, reason: "process-disabled" };
|
|
108
|
+
const parsed = parseAssetRef(ref);
|
|
109
|
+
const allowed = cfg?.allowedTypes ?? DEFAULT_ALLOWED_TYPES[processName];
|
|
110
|
+
if (!allowed.includes(parsed.type))
|
|
111
|
+
return { skip: true, reason: "type-filter" };
|
|
112
|
+
// Hardcoded: wiki raw directories are never processed by any improve process.
|
|
113
|
+
if (parsed.type === "wiki" && parsed.name.split("/")[1] === "raw") {
|
|
114
|
+
return { skip: true, reason: "raw-wiki" };
|
|
115
|
+
}
|
|
116
|
+
return { skip: false, reason: "" };
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Planner-level pre-filter: return `true` when every per-ref improve pass that
|
|
120
|
+
* participates in the in-loop dispatch (today: `reflect` and `distill`) would
|
|
121
|
+
* refuse this ref under the active profile. Such refs cannot produce any work
|
|
122
|
+
* downstream — they only generate synthetic skip actions and inflate
|
|
123
|
+
* `plannedRefs` by a constant factor per cron run.
|
|
124
|
+
*
|
|
125
|
+
* Companion to `shouldSkipRef`. The 2026-05-27 planner/profile/metrics deep
|
|
126
|
+
* analysis (`/tmp/akm-health-investigations/planner-profile-metrics-deep-analysis.md`)
|
|
127
|
+
* documents the 99.07% synthetic-skip emission rate this pre-filter eliminates.
|
|
128
|
+
*
|
|
129
|
+
* NOTE: passes that operate on their own candidate set (consolidate,
|
|
130
|
+
* memoryInference, graphExtraction) are deliberately excluded — they do not
|
|
131
|
+
* iterate `plannedRefs` per-ref, so a ref being profile-incompatible at the
|
|
132
|
+
* reflect+distill layer says nothing about their work.
|
|
133
|
+
*/
|
|
134
|
+
export function isProfileFilteredForAllPasses(ref, profile) {
|
|
135
|
+
const reflectSkip = shouldSkipRef(ref, "reflect", profile);
|
|
136
|
+
const distillSkip = shouldSkipRef(ref, "distill", profile);
|
|
137
|
+
return reflectSkip.skip && distillSkip.skip;
|
|
138
|
+
}
|