akm-cli 0.8.1 → 0.8.2
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 +71 -0
- package/dist/assets/stash-skeleton/README.md +76 -0
- package/dist/cli.js +8 -3
- package/dist/commands/consolidate.js +4 -4
- package/dist/commands/health.js +20 -0
- package/dist/commands/improve-cli.js +1 -1
- package/dist/commands/improve-result-file.js +9 -4
- package/dist/commands/improve.js +49 -25
- package/dist/commands/init.js +6 -1
- package/dist/commands/{proposal-drain-policies.js → proposal/drain-policies.js} +2 -2
- package/dist/commands/{proposal-drain.js → proposal/drain.js} +10 -10
- package/dist/commands/show.js +47 -0
- package/dist/commands/stash-skeleton.js +78 -0
- package/dist/{setup/ripgrep-install.js → core/ripgrep/install.js} +2 -2
- package/dist/{setup/ripgrep-resolve.js → core/ripgrep/resolve.js} +2 -2
- package/dist/core/stash-meta.js +110 -0
- package/dist/setup/detect.js +27 -0
- package/dist/setup/harness-config-import.js +170 -0
- package/dist/setup/registry-stash-loader.js +99 -0
- package/dist/setup/setup.js +229 -72
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,77 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
6
6
|
|
|
7
|
+
## [0.8.2] - 2026-06-05
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **LM Studio auto-detection in setup wizard** — `akm setup` now probes
|
|
12
|
+
`localhost:1234/v1/models` at startup and, when the server is running, pre-fills
|
|
13
|
+
the LLM backend with the active model list, mirroring the existing Ollama detection
|
|
14
|
+
flow (#522).
|
|
15
|
+
- **Agent harness config import** — `akm setup` detects installed AI coding harnesses
|
|
16
|
+
(currently Claude Code and OpenCode) and pre-populates LLM provider, model, and
|
|
17
|
+
base-URL fields from the harness configuration. The importer registry
|
|
18
|
+
(`HARNESS_CONFIG_IMPORTERS`) makes adding future harnesses a single append (#523).
|
|
19
|
+
API key *values* are never read or stored — only the environment variable name is
|
|
20
|
+
imported.
|
|
21
|
+
- **Registry-driven stash selection** — the "Add Sources" step now fetches available
|
|
22
|
+
stashes from the official AKM registry at startup. `DEFAULT_SELECTED_STASH_IDS`
|
|
23
|
+
in `src/setup/registry-stash-loader.ts` is the single edit point for changing
|
|
24
|
+
which stashes are pre-checked. Falls back to a hardcoded list on network error (#520).
|
|
25
|
+
- **`improve.autoAccept.{promoted,validationFailed}` health metrics** — auto-accepted
|
|
26
|
+
proposals that pass the confidence threshold but fail validation (truncated
|
|
27
|
+
description, invalid frontmatter) are now counted as `gateAutoAcceptFailedCount`
|
|
28
|
+
in the improve result envelope and surfaced as `improve.autoAccept.validationFailed`
|
|
29
|
+
in `akm health` reports.
|
|
30
|
+
- **`auto-accept-validation` health advisory** — heuristic advisory that warns when
|
|
31
|
+
`validationFailed > 0` so malformed proposals are visible before they pile up in
|
|
32
|
+
the queue.
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
|
|
36
|
+
- **`akm-improve` tasks recorded as failed on budget exhaustion** — the budget
|
|
37
|
+
exhaustion timer called `process.exit(1)`, causing every budget-limited run to be
|
|
38
|
+
recorded as a task failure. Changed to `process.exit(0)`; budget exhaustion is a
|
|
39
|
+
normal exit condition.
|
|
40
|
+
- **`improve_runs.started_at` always equal to `completed_at`** — `writeImproveResultFile`
|
|
41
|
+
was called at end-of-run, so `new Date()` captured the completion time and both
|
|
42
|
+
columns held the same value (649/661 real runs affected, regressed ~May 26).
|
|
43
|
+
`started_at` now uses the timestamp captured at process launch, passed in from the
|
|
44
|
+
CLI entry point. A regex-based fallback decodes the timestamp embedded in the run ID
|
|
45
|
+
for any call site that does not supply an explicit value (#524).
|
|
46
|
+
- **`akm-health-report` task fails on transient DNS errors** — the Discord webhook
|
|
47
|
+
script caught `HTTPError` but not the parent `URLError`, so DNS blips caused the
|
|
48
|
+
task runner to record the health report as failed. `URLError` is now caught and
|
|
49
|
+
logged as a warning with a clean exit.
|
|
50
|
+
|
|
51
|
+
### Added
|
|
52
|
+
|
|
53
|
+
- **Stash `.meta/` convention** — a stash may carry an optional, human-authored
|
|
54
|
+
`.meta/` directory at its root for orientation: purpose, key assets, conventions,
|
|
55
|
+
and maintainer info. Surface it on demand with `akm show meta` (the working
|
|
56
|
+
stash's `.meta/index.md`), `akm show meta:<name>` (e.g. `.meta/about.md`), or
|
|
57
|
+
scope it to a specific stash with `akm show <origin>//meta[:<name>]`. Because
|
|
58
|
+
`.meta/` is a dot-directory, the indexer already skips it, so these docs never
|
|
59
|
+
pollute search results — they are direct-read on demand. Owners extend the
|
|
60
|
+
convention by dropping new files (`.meta/about.md`, `.meta/conventions.md`,
|
|
61
|
+
`.meta/license`) with no code changes. `akm init` scaffolds a `.meta/index.md`
|
|
62
|
+
template into newly created stashes.
|
|
63
|
+
- **Default stash skeleton** — `akm init` (and `akm setup`) now copies
|
|
64
|
+
`src/assets/stash-skeleton/` into every newly created stash. Currently ships
|
|
65
|
+
a `README.md` covering what the stash contains and how agents use `akm` to
|
|
66
|
+
access assets. Existing files are never overwritten. Add files to
|
|
67
|
+
`src/assets/stash-skeleton/` to extend what ships with a fresh install.
|
|
68
|
+
|
|
69
|
+
### Improved
|
|
70
|
+
|
|
71
|
+
- **Setup wizard pre-populates from existing config** — on re-run, `akm setup`
|
|
72
|
+
initialises every prompt default from the current saved configuration so users
|
|
73
|
+
only need to change what has actually changed (#519).
|
|
74
|
+
- **Config backup before every setup write** — `backupExistingConfig()` is now called
|
|
75
|
+
before each `saveConfig` in the setup wizard, ensuring the previous config is always
|
|
76
|
+
recoverable if a wizard run is interrupted (#521).
|
|
77
|
+
|
|
7
78
|
## [0.8.1] - 2026-06-05
|
|
8
79
|
|
|
9
80
|
### Added
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# AKM Stash
|
|
2
|
+
|
|
3
|
+
This is an **AKM stash** — a structured knowledge repository that stores reusable
|
|
4
|
+
assets for you and your AI agents. AKM (Agent Knowledge Management) indexes, ranks,
|
|
5
|
+
and surfaces these assets at the right moment during coding sessions, improving
|
|
6
|
+
consistency and reducing repeated context-setting.
|
|
7
|
+
|
|
8
|
+
## What this stash contains
|
|
9
|
+
|
|
10
|
+
| Directory | Asset type | Purpose |
|
|
11
|
+
|-----------|-----------|---------|
|
|
12
|
+
| `skills/` | Skills | Step-by-step instructions agents follow for specific tasks |
|
|
13
|
+
| `knowledge/` | Knowledge | Reference documents, guides, architecture notes |
|
|
14
|
+
| `memories/` | Memories | Persistent facts and preferences learned over time |
|
|
15
|
+
| `commands/` | Commands | Parameterised prompt templates for common workflows |
|
|
16
|
+
| `agents/` | Agents | Agent definitions with system prompts and tool policies |
|
|
17
|
+
| `workflows/` | Workflows | Multi-step orchestration sequences |
|
|
18
|
+
| `tasks/` | Tasks | Scheduled or on-demand automation tasks |
|
|
19
|
+
| `lessons/` | Lessons | Durable lessons extracted from past sessions |
|
|
20
|
+
|
|
21
|
+
Add your own assets to any of these directories. AKM will index them automatically
|
|
22
|
+
on the next `akm index` run (or when the background improve pipeline picks them up).
|
|
23
|
+
|
|
24
|
+
## For agents: how to access this stash
|
|
25
|
+
|
|
26
|
+
All assets in this stash are searchable via the `akm` CLI. Use these commands to
|
|
27
|
+
find and read assets during a session:
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
# Find assets relevant to your current task (recommended first step)
|
|
31
|
+
akm curate "<task description including project name>"
|
|
32
|
+
|
|
33
|
+
# Full-text + semantic search
|
|
34
|
+
akm search "<query>"
|
|
35
|
+
akm search "<query>" --type skill
|
|
36
|
+
akm search "<query>" --type knowledge
|
|
37
|
+
|
|
38
|
+
# Show a specific asset by ref
|
|
39
|
+
akm show skill:<name>
|
|
40
|
+
akm show knowledge:<name>
|
|
41
|
+
akm show memory:<name>
|
|
42
|
+
akm show command:<name>
|
|
43
|
+
|
|
44
|
+
# List available assets by type
|
|
45
|
+
akm list --type skill
|
|
46
|
+
akm list --type knowledge
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Recording feedback and new knowledge
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
# Mark an asset as helpful (improves future rankings)
|
|
53
|
+
akm feedback <ref> --positive
|
|
54
|
+
|
|
55
|
+
# Capture a durable lesson or memory from the current session
|
|
56
|
+
akm remember "<fact or lesson>"
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Improving and maintaining the stash
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
# Run the self-improvement pipeline (extract, reflect, consolidate)
|
|
63
|
+
akm improve
|
|
64
|
+
|
|
65
|
+
# Check stash health and pipeline metrics
|
|
66
|
+
akm health
|
|
67
|
+
|
|
68
|
+
# Review pending improvement proposals
|
|
69
|
+
akm proposal list
|
|
70
|
+
akm proposal show <id>
|
|
71
|
+
akm proposal accept <id>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
*Created by `akm init`. See `akm --help` for full command reference.*
|
package/dist/cli.js
CHANGED
|
@@ -92,8 +92,8 @@ function resolveEventSource() {
|
|
|
92
92
|
}
|
|
93
93
|
import { resolveImproveProfile } from "./commands/improve-profiles";
|
|
94
94
|
import { akmProposalAccept, akmProposalDiff, akmProposalList, akmProposalReject, akmProposalRevert, akmProposalShow, } from "./commands/proposal";
|
|
95
|
-
import { drainProposals } from "./commands/proposal
|
|
96
|
-
import { resolveDrainPolicy } from "./commands/proposal
|
|
95
|
+
import { drainProposals } from "./commands/proposal/drain";
|
|
96
|
+
import { resolveDrainPolicy } from "./commands/proposal/drain-policies";
|
|
97
97
|
import { akmPropose } from "./commands/propose";
|
|
98
98
|
import { akmSearch, parseBeliefFilterMode, parseScopeFilterFlags, parseSearchSource } from "./commands/search";
|
|
99
99
|
import { checkForUpdate, performUpgrade } from "./commands/self-update";
|
|
@@ -107,6 +107,7 @@ import { DEFAULT_CONFIG, loadConfig, loadUserConfig, resolveConfiguredSources, s
|
|
|
107
107
|
import { ConfigError, NotFoundError, UsageError } from "./core/errors";
|
|
108
108
|
import { appendEvent } from "./core/events";
|
|
109
109
|
import { getCacheDir, getConfigPath, getDbPath, getDefaultStashDir } from "./core/paths";
|
|
110
|
+
import { parseMetaRef } from "./core/stash-meta";
|
|
110
111
|
import { plainize } from "./core/tty";
|
|
111
112
|
import { clearLogFile, info, isQuiet, isVerbose, setLogFile, setQuiet, setVerbose, warn } from "./core/warn";
|
|
112
113
|
import { closeDatabase, openExistingDatabase } from "./indexer/db";
|
|
@@ -872,7 +873,11 @@ const showCommand = defineCommand({
|
|
|
872
873
|
output("proposal-show", result);
|
|
873
874
|
return;
|
|
874
875
|
}
|
|
875
|
-
|
|
876
|
+
// `[origin//]meta[:name]` targets the stash `.meta/` convention, which is
|
|
877
|
+
// not a typed asset ref — skip ref validation and let akmShowUnified
|
|
878
|
+
// direct-read it. (`parseAssetRef` would reject the non-type `meta`.)
|
|
879
|
+
if (!parseMetaRef(args.ref))
|
|
880
|
+
parseAssetRef(args.ref);
|
|
876
881
|
// The knowledge-view positional syntax (`akm show knowledge:foo section "Auth"`)
|
|
877
882
|
// is rewritten to `--akmView` / `--akmHeading` / `--akmStart` / `--akmEnd`
|
|
878
883
|
// by `normalizeShowArgv` before citty parses argv. We read those values
|
|
@@ -170,7 +170,7 @@ export function isHotCapturedMemory(filePath) {
|
|
|
170
170
|
return false;
|
|
171
171
|
}
|
|
172
172
|
}
|
|
173
|
-
|
|
173
|
+
function consolidateGuardStatus(filePath) {
|
|
174
174
|
if (!fs.existsSync(filePath))
|
|
175
175
|
return "missing";
|
|
176
176
|
let content;
|
|
@@ -395,7 +395,7 @@ export function buildChunkPrompt(sourceName, memories, chunkIndex, totalChunks,
|
|
|
395
395
|
* trimmed). Empty set on any read/parse error — fail-safe to "annotate
|
|
396
396
|
* nothing" so the LLM still proposes, just slightly more wastefully.
|
|
397
397
|
*/
|
|
398
|
-
|
|
398
|
+
function loadPendingConsolidateProposalHashes(stashDir) {
|
|
399
399
|
const hashes = new Set();
|
|
400
400
|
try {
|
|
401
401
|
const pending = listProposals(stashDir, { status: "pending" }).filter((p) => p.source === "consolidate");
|
|
@@ -1924,7 +1924,7 @@ export function normalizeUpdatedField(fm) {
|
|
|
1924
1924
|
* Two slugs that normalise to the same string are considered the same asset
|
|
1925
1925
|
* for dedup purposes even if they don't share an exact ref.
|
|
1926
1926
|
*/
|
|
1927
|
-
|
|
1927
|
+
function normalizeSlugForDedup(ref) {
|
|
1928
1928
|
const slug = ref.replace(/^[^:]+:/, "");
|
|
1929
1929
|
const monthRe = /(?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)/i;
|
|
1930
1930
|
const tokens = slug
|
|
@@ -1962,7 +1962,7 @@ export function normalizeSlugForDedup(ref) {
|
|
|
1962
1962
|
* improve invocation — a different concern from the cross-run content-hash
|
|
1963
1963
|
* dedup, and cheap (no embeddings, no DB query).
|
|
1964
1964
|
*/
|
|
1965
|
-
|
|
1965
|
+
async function checkPreEmitDedup(opts) {
|
|
1966
1966
|
const normCandidate = normalizeSlugForDedup(opts.candidateRef);
|
|
1967
1967
|
// Pending consolidate proposals (slug match) — within the same improve run.
|
|
1968
1968
|
const pendingConsolidate = listProposals(opts.stashDir, { status: "pending" }).filter((p) => p.source === "consolidate");
|
package/dist/commands/health.js
CHANGED
|
@@ -71,6 +71,7 @@ function createUnknownImproveMetrics() {
|
|
|
71
71
|
graphExtraction: 0,
|
|
72
72
|
error: 0,
|
|
73
73
|
},
|
|
74
|
+
autoAccept: { promoted: 0, validationFailed: 0 },
|
|
74
75
|
reflectsWithErrorContext: 0,
|
|
75
76
|
coverageGapCount: 0,
|
|
76
77
|
evalCasesWritten: 0,
|
|
@@ -293,6 +294,8 @@ function projectRunMetrics(result) {
|
|
|
293
294
|
}
|
|
294
295
|
}
|
|
295
296
|
}
|
|
297
|
+
metrics.autoAccept.promoted += toFiniteNumber(result.gateAutoAcceptedCount);
|
|
298
|
+
metrics.autoAccept.validationFailed += toFiniteNumber(result.gateAutoAcceptFailedCount);
|
|
296
299
|
metrics.reflectsWithErrorContext += toFiniteNumber(result.reflectsWithErrorContext);
|
|
297
300
|
if (Array.isArray(result.coverageGaps))
|
|
298
301
|
metrics.coverageGapCount += result.coverageGaps.length;
|
|
@@ -481,6 +484,8 @@ function mergeImproveMetrics(dst, src) {
|
|
|
481
484
|
dst.actions.memoryInference += src.actions.memoryInference;
|
|
482
485
|
dst.actions.graphExtraction += src.actions.graphExtraction;
|
|
483
486
|
dst.actions.error += src.actions.error;
|
|
487
|
+
dst.autoAccept.promoted += src.autoAccept.promoted;
|
|
488
|
+
dst.autoAccept.validationFailed += src.autoAccept.validationFailed;
|
|
484
489
|
dst.reflectsWithErrorContext += src.reflectsWithErrorContext;
|
|
485
490
|
dst.coverageGapCount += src.coverageGapCount;
|
|
486
491
|
dst.evalCasesWritten += src.evalCasesWritten;
|
|
@@ -923,6 +928,8 @@ const INTERESTING_DELTA_PATHS = [
|
|
|
923
928
|
"improve.graphExtraction.failures",
|
|
924
929
|
"improve.sessionExtraction.sessionsScanned",
|
|
925
930
|
"improve.sessionExtraction.proposalsCreated",
|
|
931
|
+
"improve.autoAccept.promoted",
|
|
932
|
+
"improve.autoAccept.validationFailed",
|
|
926
933
|
"improve.wallTime.medianMs",
|
|
927
934
|
"improve.wallTime.p95Ms",
|
|
928
935
|
];
|
|
@@ -1183,6 +1190,19 @@ export function akmHealth(options = {}) {
|
|
|
1183
1190
|
durationMs: sx.durationMs,
|
|
1184
1191
|
},
|
|
1185
1192
|
});
|
|
1193
|
+
const aa = improveSummary.autoAccept;
|
|
1194
|
+
advisories.push({
|
|
1195
|
+
name: "auto-accept-validation",
|
|
1196
|
+
kind: "heuristic",
|
|
1197
|
+
status: aa.validationFailed > 0 ? "warn" : "pass",
|
|
1198
|
+
confidence: aa.promoted + aa.validationFailed > 0 ? "high" : "low",
|
|
1199
|
+
message: aa.validationFailed > 0
|
|
1200
|
+
? `${aa.validationFailed} proposal(s) passed confidence threshold but failed auto-accept validation (truncated description, invalid frontmatter, etc.) — they remain in the queue for manual review.`
|
|
1201
|
+
: aa.promoted > 0
|
|
1202
|
+
? `Auto-accept healthy: ${aa.promoted} proposal(s) promoted, 0 validation failures.`
|
|
1203
|
+
: "Auto-accept gate did not run (disabled or no proposals above threshold).",
|
|
1204
|
+
evidence: { promoted: aa.promoted, validationFailed: aa.validationFailed },
|
|
1205
|
+
});
|
|
1186
1206
|
const metrics = {
|
|
1187
1207
|
taskFailRate: roundRate(taskFailRate),
|
|
1188
1208
|
agentFailureRate: roundRate(agentFailureRate),
|
|
@@ -217,7 +217,7 @@ export const improveCommand = defineCommand({
|
|
|
217
217
|
runRecorded = true; // Suppress any late signal-handler write — the success path owns the row now.
|
|
218
218
|
if (primaryStashDir) {
|
|
219
219
|
try {
|
|
220
|
-
writeImproveResultFile(primaryStashDir, runId, improveResult);
|
|
220
|
+
writeImproveResultFile(primaryStashDir, runId, improveResult, startedAtIso);
|
|
221
221
|
}
|
|
222
222
|
catch (err) {
|
|
223
223
|
// Stderr warning on the failure path is preferable to crashing
|
|
@@ -73,14 +73,19 @@ export function relativeImproveResultPath(runId) {
|
|
|
73
73
|
* (closes the dry-run/real-run artifact-trap recorded in MEMORY.md
|
|
74
74
|
* `feedback_akm_dryrun_artifact_trap`).
|
|
75
75
|
*/
|
|
76
|
-
export function writeImproveResultFile(stashDir, runId, result) {
|
|
76
|
+
export function writeImproveResultFile(stashDir, runId, result, startedAt) {
|
|
77
77
|
const db = openStateDatabase();
|
|
78
78
|
try {
|
|
79
|
-
const
|
|
79
|
+
const completedAt = new Date().toISOString();
|
|
80
|
+
// startedAt is the ISO timestamp captured at process launch (passed from the
|
|
81
|
+
// CLI entry point). If omitted, fall back to the run-id's embedded timestamp
|
|
82
|
+
// so started_at != completed_at even on older call sites.
|
|
83
|
+
const resolvedStartedAt = startedAt ??
|
|
84
|
+
runId.slice(0, 24).replace(/^(\d{4}-\d{2}-\d{2}T)(\d{2})-(\d{2})-(\d{2})-(\d{3})Z$/, "$1$2:$3:$4.$5Z");
|
|
80
85
|
recordImproveRun(db, {
|
|
81
86
|
id: runId,
|
|
82
|
-
startedAt,
|
|
83
|
-
completedAt
|
|
87
|
+
startedAt: resolvedStartedAt,
|
|
88
|
+
completedAt,
|
|
84
89
|
stashDir,
|
|
85
90
|
dryRun: Boolean(result.dryRun),
|
|
86
91
|
profile: null,
|
package/dist/commands/improve.js
CHANGED
|
@@ -36,8 +36,8 @@ import { akmExtract } from "./extract";
|
|
|
36
36
|
import { makeGateConfig, resolveExtractConfidence, runAutoAcceptGate } from "./improve-auto-accept";
|
|
37
37
|
import { isProfileFilteredForAllPasses, resolveImproveProfile, resolveProcessEnabled, shouldSkipRef, } from "./improve-profiles";
|
|
38
38
|
import { akmLint } from "./lint/index";
|
|
39
|
-
import { drainProposals } from "./proposal
|
|
40
|
-
import { resolveDrainPolicy } from "./proposal
|
|
39
|
+
import { drainProposals } from "./proposal/drain";
|
|
40
|
+
import { resolveDrainPolicy } from "./proposal/drain-policies";
|
|
41
41
|
import { akmReflect } from "./reflect";
|
|
42
42
|
import { runSchemaRepairPass } from "./schema-repair";
|
|
43
43
|
import { checkDeadUrls } from "./url-checker";
|
|
@@ -678,7 +678,8 @@ export async function akmImprove(options = {}) {
|
|
|
678
678
|
budgetAbortController.abort("improve budget exhausted");
|
|
679
679
|
// Grace period: let finally run to release improve.lock, then hard-exit
|
|
680
680
|
// to prevent the process outliving the task timeout window (lock-cascade fix).
|
|
681
|
-
|
|
681
|
+
// Exit 0: budget exhaustion is a normal scheduled-task condition, not an error.
|
|
682
|
+
setTimeout(() => process.exit(0), 5_000);
|
|
682
683
|
}, budgetMs);
|
|
683
684
|
// Clear the timer when the run ends to avoid keeping the event loop alive.
|
|
684
685
|
clearBudgetTimer = () => clearTimeout(budgetTimer);
|
|
@@ -729,7 +730,7 @@ export async function akmImprove(options = {}) {
|
|
|
729
730
|
rejectedProposalsByRef.set(e.ref, e);
|
|
730
731
|
}
|
|
731
732
|
}
|
|
732
|
-
const { reflectsWithErrorContext, memoryRefsForInference, gateAutoAcceptedCount: loopGateCount, } = await runImproveLoopStage({
|
|
733
|
+
const { reflectsWithErrorContext, memoryRefsForInference, gateAutoAcceptedCount: loopGateCount, gateAutoAcceptFailedCount: loopGateFailedCount, } = await runImproveLoopStage({
|
|
733
734
|
scope,
|
|
734
735
|
options,
|
|
735
736
|
primaryStashDir,
|
|
@@ -748,7 +749,7 @@ export async function akmImprove(options = {}) {
|
|
|
748
749
|
eventsCtx,
|
|
749
750
|
improveProfile,
|
|
750
751
|
});
|
|
751
|
-
const { allWarnings, consolidation, deadUrls, memoryInference, graphExtraction, stalenessDetection, maintenanceActions, memoryInferenceDurationMs, graphExtractionDurationMs, orphansPurged, proposalsExpired, gateAutoAcceptedCount: postLoopGateCount, } = await runImprovePostLoopStage({
|
|
752
|
+
const { allWarnings, consolidation, deadUrls, memoryInference, graphExtraction, stalenessDetection, maintenanceActions, memoryInferenceDurationMs, graphExtractionDurationMs, orphansPurged, proposalsExpired, gateAutoAcceptedCount: postLoopGateCount, gateAutoAcceptFailedCount: postLoopGateFailedCount, } = await runImprovePostLoopStage({
|
|
752
753
|
scope,
|
|
753
754
|
options,
|
|
754
755
|
primaryStashDir,
|
|
@@ -833,6 +834,10 @@ export async function akmImprove(options = {}) {
|
|
|
833
834
|
const t = preparation.gateAutoAcceptedCount + loopGateCount + postLoopGateCount;
|
|
834
835
|
return t > 0 ? { gateAutoAcceptedCount: t } : {};
|
|
835
836
|
})(),
|
|
837
|
+
...(() => {
|
|
838
|
+
const f = preparation.gateAutoAcceptFailedCount + loopGateFailedCount + postLoopGateFailedCount;
|
|
839
|
+
return f > 0 ? { gateAutoAcceptFailedCount: f } : {};
|
|
840
|
+
})(),
|
|
836
841
|
};
|
|
837
842
|
if (!result.dryRun)
|
|
838
843
|
emitImproveCompletedEvent(result, {
|
|
@@ -1066,6 +1071,7 @@ async function runImprovePreparationStage(args) {
|
|
|
1066
1071
|
// The extract envelope's own `warnings` field surfaces what went wrong.
|
|
1067
1072
|
let extractResults;
|
|
1068
1073
|
let gateAutoAcceptedCount = 0;
|
|
1074
|
+
let gateAutoAcceptFailedCount = 0;
|
|
1069
1075
|
const extractConfig = options.config ?? loadConfig();
|
|
1070
1076
|
const extractGateCfg = makeGateConfig("extract", {
|
|
1071
1077
|
globalThreshold: options.autoAccept,
|
|
@@ -1087,12 +1093,16 @@ async function runImprovePreparationStage(args) {
|
|
|
1087
1093
|
dryRun: options.dryRun ?? false,
|
|
1088
1094
|
});
|
|
1089
1095
|
extractResults.push(result);
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
+
{
|
|
1097
|
+
const gr = await runAutoAcceptGate(primaryStashDir
|
|
1098
|
+
? result.proposals.map((proposalId) => {
|
|
1099
|
+
const proposal = getProposal(primaryStashDir, proposalId);
|
|
1100
|
+
return { proposalId, confidence: resolveExtractConfidence(proposal) };
|
|
1101
|
+
})
|
|
1102
|
+
: [], extractGateCfg);
|
|
1103
|
+
gateAutoAcceptedCount += gr.promoted.length;
|
|
1104
|
+
gateAutoAcceptFailedCount += gr.failed.length;
|
|
1105
|
+
}
|
|
1096
1106
|
}
|
|
1097
1107
|
catch (err) {
|
|
1098
1108
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -1118,7 +1128,9 @@ async function runImprovePreparationStage(args) {
|
|
|
1118
1128
|
proposalId: p.id,
|
|
1119
1129
|
confidence: resolveExtractConfidence(p),
|
|
1120
1130
|
}));
|
|
1121
|
-
|
|
1131
|
+
const backlogGr = await runAutoAcceptGate(backlogCandidates, extractGateCfg);
|
|
1132
|
+
gateAutoAcceptedCount += backlogGr.promoted.length;
|
|
1133
|
+
gateAutoAcceptFailedCount += backlogGr.failed.length;
|
|
1122
1134
|
}
|
|
1123
1135
|
}
|
|
1124
1136
|
// eligibleCount = raw pre-filter count (before cooldown/signal/cleanup filters).
|
|
@@ -1544,6 +1556,7 @@ async function runImprovePreparationStage(args) {
|
|
|
1544
1556
|
recentErrors,
|
|
1545
1557
|
utilityMap,
|
|
1546
1558
|
gateAutoAcceptedCount,
|
|
1559
|
+
gateAutoAcceptFailedCount,
|
|
1547
1560
|
};
|
|
1548
1561
|
}
|
|
1549
1562
|
// TODO(refactor): 13 args including `actions`/`recentErrors` mutation channels. Restructure into immutable plan + mutable context objects — deferred to dedicated refactor with isolated testing.
|
|
@@ -1627,6 +1640,7 @@ async function runImproveLoopStage(args) {
|
|
|
1627
1640
|
? listProposals(dedupeStashDirForProposals, { status: "pending" }).map((p) => p.ref)
|
|
1628
1641
|
: []);
|
|
1629
1642
|
let gateAutoAcceptedCount = 0;
|
|
1643
|
+
let gateAutoAcceptFailedCount = 0;
|
|
1630
1644
|
const reflectGateCfg = makeGateConfig("reflect", {
|
|
1631
1645
|
globalThreshold: options.autoAccept,
|
|
1632
1646
|
dryRun: options.dryRun ?? false,
|
|
@@ -1803,7 +1817,9 @@ async function runImproveLoopStage(args) {
|
|
|
1803
1817
|
},
|
|
1804
1818
|
}, eventsCtx);
|
|
1805
1819
|
if (reflectResult.ok) {
|
|
1806
|
-
|
|
1820
|
+
const reflectGr = await runAutoAcceptGate([{ proposalId: reflectResult.proposal.id, confidence: reflectResult.proposal.confidence }], reflectGateCfg);
|
|
1821
|
+
gateAutoAcceptedCount += reflectGr.promoted.length;
|
|
1822
|
+
gateAutoAcceptFailedCount += reflectGr.failed.length;
|
|
1807
1823
|
}
|
|
1808
1824
|
} // end else (reflect type/profile check)
|
|
1809
1825
|
}
|
|
@@ -1914,7 +1930,9 @@ async function runImproveLoopStage(args) {
|
|
|
1914
1930
|
});
|
|
1915
1931
|
actions.push({ ref: planned.ref, mode: "distill", result: distillResult });
|
|
1916
1932
|
if (distillResult.outcome === "queued" && distillResult.proposal) {
|
|
1917
|
-
|
|
1933
|
+
const distillGr = await runAutoAcceptGate([{ proposalId: distillResult.proposal.id, confidence: distillResult.proposal.confidence }], distillGateCfg);
|
|
1934
|
+
gateAutoAcceptedCount += distillGr.promoted.length;
|
|
1935
|
+
gateAutoAcceptFailedCount += distillGr.failed.length;
|
|
1918
1936
|
}
|
|
1919
1937
|
if (parsedPlannedRef.type === "memory") {
|
|
1920
1938
|
const promotedToKnowledge = distillResult.outcome === "queued" && distillResult.proposalKind === "knowledge";
|
|
@@ -1987,7 +2005,7 @@ async function runImproveLoopStage(args) {
|
|
|
1987
2005
|
completedCount++;
|
|
1988
2006
|
info(`[improve] ${completedCount}/${loopRefs.length} ${planned.ref}`);
|
|
1989
2007
|
}
|
|
1990
|
-
return { reflectsWithErrorContext, memoryRefsForInference, gateAutoAcceptedCount };
|
|
2008
|
+
return { reflectsWithErrorContext, memoryRefsForInference, gateAutoAcceptedCount, gateAutoAcceptFailedCount };
|
|
1991
2009
|
}
|
|
1992
2010
|
async function runImprovePostLoopStage(args) {
|
|
1993
2011
|
const { scope, options, primaryStashDir, actionableRefs, appliedCleanup, cleanupWarnings, memorySummary, memoryRefsForInference, reindexFn, eventsCtx, budgetSignal, improveProfile, } = args;
|
|
@@ -2083,6 +2101,7 @@ async function runImprovePostLoopStage(args) {
|
|
|
2083
2101
|
durationMs: 0,
|
|
2084
2102
|
};
|
|
2085
2103
|
let gateAutoAcceptedCount = 0;
|
|
2104
|
+
let gateAutoAcceptFailedCount = 0;
|
|
2086
2105
|
const consolidateGateCfg = makeGateConfig("consolidate", {
|
|
2087
2106
|
globalThreshold: options.autoAccept,
|
|
2088
2107
|
dryRun: options.dryRun ?? false,
|
|
@@ -2121,17 +2140,21 @@ async function runImprovePostLoopStage(args) {
|
|
|
2121
2140
|
// still wins because the spread above runs first.
|
|
2122
2141
|
autoAccept: options.consolidateOptions?.autoAccept ?? options.autoAccept,
|
|
2123
2142
|
});
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2143
|
+
{
|
|
2144
|
+
const consolidateGr = await runAutoAcceptGate(consolidation.promoted.map((proposalId) => {
|
|
2145
|
+
try {
|
|
2146
|
+
if (!primaryStashDir)
|
|
2147
|
+
return { proposalId, confidence: undefined };
|
|
2148
|
+
const proposal = getProposal(primaryStashDir, proposalId);
|
|
2149
|
+
return { proposalId, confidence: proposal.confidence };
|
|
2150
|
+
}
|
|
2151
|
+
catch {
|
|
2127
2152
|
return { proposalId, confidence: undefined };
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
}
|
|
2134
|
-
}), consolidateGateCfg)).promoted.length;
|
|
2153
|
+
}
|
|
2154
|
+
}), consolidateGateCfg);
|
|
2155
|
+
gateAutoAcceptedCount += consolidateGr.promoted.length;
|
|
2156
|
+
gateAutoAcceptFailedCount += consolidateGr.failed.length;
|
|
2157
|
+
}
|
|
2135
2158
|
if (consolidation.processed > 0) {
|
|
2136
2159
|
appendEvent({
|
|
2137
2160
|
eventType: "consolidate_completed",
|
|
@@ -2206,6 +2229,7 @@ async function runImprovePostLoopStage(args) {
|
|
|
2206
2229
|
orphansPurged: maintenanceResult.orphansPurged,
|
|
2207
2230
|
proposalsExpired: maintenanceResult.proposalsExpired,
|
|
2208
2231
|
gateAutoAcceptedCount,
|
|
2232
|
+
gateAutoAcceptFailedCount,
|
|
2209
2233
|
};
|
|
2210
2234
|
}
|
|
2211
2235
|
// TODO(refactor): mutates the passed-in `allWarnings` array as a hidden side channel. Return warnings in ImproveMaintenanceResult and merge in caller — invasive signature change deferred to next refactor pass.
|
package/dist/commands/init.js
CHANGED
|
@@ -14,7 +14,8 @@ import { TYPE_DIRS } from "../core/asset-spec";
|
|
|
14
14
|
import { loadUserConfig, saveConfig } from "../core/config";
|
|
15
15
|
import { ConfigError } from "../core/errors";
|
|
16
16
|
import { assertSafeStashDir, getBinDir, getConfigPath, getDefaultStashDir } from "../core/paths";
|
|
17
|
-
import { ensureRg } from "../
|
|
17
|
+
import { ensureRg } from "../core/ripgrep/install";
|
|
18
|
+
import { copyStashSkeleton, scaffoldStashMeta } from "./stash-skeleton";
|
|
18
19
|
/**
|
|
19
20
|
* Refuse to persist a temporary-directory stashDir to the user's config when
|
|
20
21
|
* running under a test runner AND `--dir <tempdir>` was passed explicitly.
|
|
@@ -74,6 +75,10 @@ export async function akmInit(options) {
|
|
|
74
75
|
}
|
|
75
76
|
// Ensure the default stash is a local git repo (no remote required)
|
|
76
77
|
ensureGitRepo(stashDir);
|
|
78
|
+
if (created) {
|
|
79
|
+
copyStashSkeleton(stashDir);
|
|
80
|
+
scaffoldStashMeta(stashDir);
|
|
81
|
+
}
|
|
77
82
|
// Persist stashDir in config.json
|
|
78
83
|
const configPath = getConfigPath();
|
|
79
84
|
const existing = loadUserConfig();
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import fs from "node:fs";
|
|
18
18
|
import { z } from "zod";
|
|
19
|
-
import { UsageError } from "
|
|
20
|
-
import { PROPOSAL_SOURCES } from "
|
|
19
|
+
import { UsageError } from "../../core/errors";
|
|
20
|
+
import { PROPOSAL_SOURCES } from "../../core/proposals";
|
|
21
21
|
// Valid `generator` values for a drain rule are exactly the canonical proposal
|
|
22
22
|
// `source` values (see {@link PROPOSAL_SOURCES} in src/core/proposals.ts). The
|
|
23
23
|
// engine matches rules via `policy.accept.find(r => r.generator === proposal.source)`,
|
|
@@ -36,16 +36,16 @@
|
|
|
36
36
|
*/
|
|
37
37
|
import fs from "node:fs";
|
|
38
38
|
import path from "node:path";
|
|
39
|
-
import { parseAssetRef } from "
|
|
40
|
-
import { resolveAssetPathFromName, TYPE_DIRS } from "
|
|
41
|
-
import { appendEvent } from "
|
|
42
|
-
import { parseFrontmatter } from "
|
|
43
|
-
import { listProposals } from "
|
|
44
|
-
import { info, warn } from "
|
|
45
|
-
import { runAgent } from "
|
|
46
|
-
import { runOpencodeSdk } from "
|
|
47
|
-
import { chatCompletion, stripJsonFences } from "
|
|
48
|
-
import { akmProposalAccept, akmProposalReject } from "
|
|
39
|
+
import { parseAssetRef } from "../../core/asset-ref";
|
|
40
|
+
import { resolveAssetPathFromName, TYPE_DIRS } from "../../core/asset-spec";
|
|
41
|
+
import { appendEvent } from "../../core/events";
|
|
42
|
+
import { parseFrontmatter } from "../../core/frontmatter";
|
|
43
|
+
import { listProposals } from "../../core/proposals";
|
|
44
|
+
import { info, warn } from "../../core/warn";
|
|
45
|
+
import { runAgent } from "../../integrations/agent";
|
|
46
|
+
import { runOpencodeSdk } from "../../integrations/agent/sdk-runner";
|
|
47
|
+
import { chatCompletion, stripJsonFences } from "../../llm/client";
|
|
48
|
+
import { akmProposalAccept, akmProposalReject } from "../proposal";
|
|
49
49
|
// ---------------------------------------------------------------------------
|
|
50
50
|
// Content helpers
|
|
51
51
|
// ---------------------------------------------------------------------------
|
package/dist/commands/show.js
CHANGED
|
@@ -24,6 +24,7 @@ import { loadConfig } from "../core/config";
|
|
|
24
24
|
import { NotFoundError, rethrowIfTestIsolationError, UsageError } from "../core/errors";
|
|
25
25
|
import { appendEvent, readEvents } from "../core/events";
|
|
26
26
|
import { parseFrontmatter } from "../core/frontmatter";
|
|
27
|
+
import { META_DIR, parseMetaRef, resolveMetaFilePath } from "../core/stash-meta";
|
|
27
28
|
import { closeDatabase, findEntryIdByRef, openExistingDatabase } from "../indexer/db";
|
|
28
29
|
import { ensureIndex } from "../indexer/ensure-index";
|
|
29
30
|
import { buildFileContext, buildRenderContext, getRenderer, runMatchers } from "../indexer/file-context";
|
|
@@ -105,6 +106,16 @@ function resolveRegisteredWikiAssetPath(wikiRoot, wikiName, assetName) {
|
|
|
105
106
|
*/
|
|
106
107
|
export async function akmShowUnified(input) {
|
|
107
108
|
const ref = input.ref.trim();
|
|
109
|
+
// 0a. Stash `.meta/` convention: `[origin//]meta[:name]` direct-reads a
|
|
110
|
+
// human-authored orientation doc from the stash's `.meta/` directory.
|
|
111
|
+
// These files are not indexed (the walker skips dot-dirs), so they are
|
|
112
|
+
// resolved here before the index lookup and the `type:name` parser,
|
|
113
|
+
// which would otherwise reject the non-asset-type `meta`.
|
|
114
|
+
{
|
|
115
|
+
const metaRef = parseMetaRef(ref);
|
|
116
|
+
if (metaRef)
|
|
117
|
+
return showStashMeta(metaRef);
|
|
118
|
+
}
|
|
108
119
|
// 0. Wiki-root shortcut: `wiki:<name>` with no page path routes to the
|
|
109
120
|
// wiki summary (same payload as `akm wiki show <name>`). Honour
|
|
110
121
|
// `parsed.origin` by resolving against the matching stash source(s),
|
|
@@ -152,6 +163,42 @@ export async function akmShowUnified(input) {
|
|
|
152
163
|
}
|
|
153
164
|
return result;
|
|
154
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* Resolve a stash `.meta/` doc and return it as a lightweight ShowResponse.
|
|
168
|
+
*
|
|
169
|
+
* With no origin the working stash (and other configured sources, in order)
|
|
170
|
+
* is searched and the first hit wins. With an origin the lookup is narrowed
|
|
171
|
+
* to that stash; an uninstalled origin yields an actionable "not installed"
|
|
172
|
+
* error. The file is read directly from disk — `.meta/` is never indexed.
|
|
173
|
+
*/
|
|
174
|
+
async function showStashMeta(metaRef) {
|
|
175
|
+
const allSources = resolveSourceEntries();
|
|
176
|
+
const sources = resolveSourcesForOrigin(metaRef.origin, allSources);
|
|
177
|
+
if (metaRef.origin && sources.length === 0) {
|
|
178
|
+
throw new NotFoundError(`Stash "${metaRef.origin}" is not installed, so its ${META_DIR}/ docs are unavailable. ` +
|
|
179
|
+
`Run: akm add ${metaRef.origin}`);
|
|
180
|
+
}
|
|
181
|
+
const config = loadConfig();
|
|
182
|
+
for (const source of sources) {
|
|
183
|
+
const filePath = resolveMetaFilePath(source.path, metaRef.name);
|
|
184
|
+
if (!filePath)
|
|
185
|
+
continue;
|
|
186
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
187
|
+
const editable = isEditable(filePath, config);
|
|
188
|
+
appendEvent({ eventType: "show", ref: `meta:${metaRef.name}`, metadata: { type: "meta", name: metaRef.name } });
|
|
189
|
+
return {
|
|
190
|
+
type: "meta",
|
|
191
|
+
name: metaRef.name,
|
|
192
|
+
path: filePath,
|
|
193
|
+
content,
|
|
194
|
+
origin: source.registryId ?? null,
|
|
195
|
+
editable,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
throw new NotFoundError(`No ${META_DIR}/${metaRef.name} doc found${metaRef.origin ? ` in "${metaRef.origin}"` : ""}. ` +
|
|
199
|
+
`Stash maintainers can create ${META_DIR}/${metaRef.name}.md to describe this stash ` +
|
|
200
|
+
`(purpose, key assets, conventions, maintainer).`);
|
|
201
|
+
}
|
|
155
202
|
function hasAnyScopeKey(scope) {
|
|
156
203
|
return Boolean(scope.user || scope.agent || scope.run || scope.channel);
|
|
157
204
|
}
|