akm-cli 0.10.261005-beta → 0.10.261007-beta
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 +65 -0
- package/STABILITY.md +9 -0
- package/dist/assets/ui/app.js +91 -0
- package/dist/assets/ui/asset.js +67 -0
- package/dist/assets/ui/bundles.js +80 -0
- package/dist/assets/ui/changes.js +32 -0
- package/dist/assets/ui/charts.js +93 -0
- package/dist/assets/ui/config.js +69 -0
- package/dist/assets/ui/diff-view.js +202 -0
- package/dist/assets/ui/diff.js +261 -0
- package/dist/assets/ui/index.html +30 -0
- package/dist/assets/ui/lib.js +238 -0
- package/dist/assets/ui/markdown.js +103 -0
- package/dist/assets/ui/overview.js +145 -0
- package/dist/assets/ui/proposal-actions.js +273 -0
- package/dist/assets/ui/proposals.js +219 -0
- package/dist/assets/ui/search.js +62 -0
- package/dist/assets/ui/styles.css +1415 -0
- package/dist/cli.js +3 -0
- package/dist/commands/health/checks.js +5 -3
- package/dist/commands/health/egress.js +5 -1
- package/dist/commands/health/engine-usage.js +2 -2
- package/dist/commands/health/llm-usage.js +2 -2
- package/dist/commands/health/windows.js +5 -5
- package/dist/commands/health.js +40 -8
- package/dist/commands/improve/reflect-noise.js +4 -0
- package/dist/commands/improve/reflect.js +14 -5
- package/dist/commands/metrics/metrics-cli.js +3 -3
- package/dist/commands/proposal/propose-cli.js +5 -0
- package/dist/commands/proposal/propose.js +6 -2
- package/dist/commands/proposal/repository.js +34 -29
- package/dist/commands/read/search.js +2 -1
- package/dist/commands/read/show.js +18 -12
- package/dist/commands/sources/source-clone.js +123 -11
- package/dist/commands/ui/api.js +556 -0
- package/dist/commands/ui/git-read.js +101 -0
- package/dist/commands/ui/open-browser.js +30 -0
- package/dist/commands/ui/proposal-actions.js +179 -0
- package/dist/commands/ui/redact.js +88 -0
- package/dist/commands/ui/server.js +352 -0
- package/dist/commands/ui/ui-cli.js +62 -0
- package/dist/core/asset/resolve-ref.js +7 -3
- package/dist/indexer/indexer.js +14 -2
- package/dist/indexer/search/db-search.js +7 -3
- package/dist/output/format-exempt.js +3 -0
- package/dist/storage/repositories/improve-runs-repository.js +6 -0
- package/dist/storage/repositories/index-db.js +10 -2
- package/dist/storage/repositories/metrics-repository.js +17 -0
- package/docs/architecture/0.10-process-rules.md +3 -4
- package/docs/architecture/improvement.md +2 -1
- package/docs/architecture/internals/improve-workflow.md +2 -2
- package/docs/reference/README.md +1 -0
- package/docs/reference/cli.md +68 -0
- package/docs/reference/ui-api.md +576 -0
- package/package.json +1 -1
|
@@ -232,13 +232,17 @@ function opaqueRefParts(conceptId, allowRoot = false) {
|
|
|
232
232
|
* that `resolveSourcesForOrigin` matches on `registryId`. The SHORT form (no
|
|
233
233
|
* bundle) leaves `origin` undefined = search-all-sources.
|
|
234
234
|
* - `#fragment` → rejected. No input boundary consumes an export fragment.
|
|
235
|
+
*
|
|
236
|
+
* `originGiven` — the caller already split a (non-slug) origin off the ref, so a
|
|
237
|
+
* single-segment conceptId (a flat skill repo's `domain-name-brainstormer`) is
|
|
238
|
+
* qualified and parses as opaque, exactly as it does behind a slug `bundle//`.
|
|
235
239
|
*/
|
|
236
|
-
export function parseRefInput(raw) {
|
|
240
|
+
export function parseRefInput(raw, originGiven = false) {
|
|
237
241
|
const ref = parseBundleRef(raw);
|
|
238
242
|
if (ref.fragment !== undefined) {
|
|
239
243
|
throw new UsageError(`Export fragment "#${ref.fragment}" is not accepted here — drop it from "${raw.trim()}".`, "INVALID_FLAG_VALUE");
|
|
240
244
|
}
|
|
241
|
-
const parts = typeNameFromConceptId(ref.conceptId) ?? opaqueRefParts(ref.conceptId, ref.bundle !== undefined);
|
|
245
|
+
const parts = typeNameFromConceptId(ref.conceptId) ?? opaqueRefParts(ref.conceptId, originGiven || ref.bundle !== undefined);
|
|
242
246
|
if (parts === undefined) {
|
|
243
247
|
throw new NotFoundError(`Unrecognized asset ref "${raw.trim()}": conceptId "${ref.conceptId}" has no known asset-type prefix.`, "ASSET_NOT_FOUND");
|
|
244
248
|
}
|
|
@@ -265,7 +269,7 @@ export function parseQualifiedRefInput(raw) {
|
|
|
265
269
|
if (boundary > 0) {
|
|
266
270
|
const origin = trimmed.slice(0, boundary);
|
|
267
271
|
if (!isBundleSlug(origin)) {
|
|
268
|
-
return { ...parseRefInput(trimmed.slice(boundary + 2)), origin };
|
|
272
|
+
return { ...parseRefInput(trimmed.slice(boundary + 2), true), origin };
|
|
269
273
|
}
|
|
270
274
|
}
|
|
271
275
|
return parseRefInput(trimmed);
|
package/dist/indexer/indexer.js
CHANGED
|
@@ -1196,6 +1196,18 @@ borrowed = false) {
|
|
|
1196
1196
|
export async function lookupBundleRefWithResolution(ref) {
|
|
1197
1197
|
return lookupBundleRefWithResolutionUsing(ref, openExistingDatabase);
|
|
1198
1198
|
}
|
|
1199
|
+
/**
|
|
1200
|
+
* {@link lookupBundleRefWithResolution} on a plain read-only connection (`akm ui`): a read-write one can checkpoint
|
|
1201
|
+
* the WAL into the main file when it closes, changing the file although nothing was written.
|
|
1202
|
+
*/
|
|
1203
|
+
export async function lookupBundleRefWithResolutionReadonly(ref) {
|
|
1204
|
+
return lookupBundleRefWithResolutionUsing(ref, (dbPath) => {
|
|
1205
|
+
const db = openReadonlyExistingDatabase(dbPath);
|
|
1206
|
+
if (!db)
|
|
1207
|
+
throw new Error(`Index database not found at ${dbPath}. Run 'akm index' to build it.`);
|
|
1208
|
+
return db;
|
|
1209
|
+
});
|
|
1210
|
+
}
|
|
1199
1211
|
/** Resolve an adapter-owned `[bundle//]conceptId` without interpreting its path as an AKM type. */
|
|
1200
1212
|
export async function lookupBundleRef(ref) {
|
|
1201
1213
|
const resolution = await lookupBundleRefWithResolution(ref);
|
|
@@ -1223,12 +1235,12 @@ export async function lookupBundleRefReadonly(ref) {
|
|
|
1223
1235
|
* index, which is copied once rather than once per ref. A ref that does not
|
|
1224
1236
|
* resolve, or an index that cannot be read, gives `null` for that ref.
|
|
1225
1237
|
*/
|
|
1226
|
-
export async function lookupBundleRefsReadonly(refs) {
|
|
1238
|
+
export async function lookupBundleRefsReadonly(refs, options = {}) {
|
|
1227
1239
|
if (refs.length === 0)
|
|
1228
1240
|
return [];
|
|
1229
1241
|
let db;
|
|
1230
1242
|
try {
|
|
1231
|
-
db = openReadonlyExistingDatabase(getDbPath(), { isolatedSnapshot: true });
|
|
1243
|
+
db = openReadonlyExistingDatabase(getDbPath(), { isolatedSnapshot: options.isolatedSnapshot ?? true });
|
|
1232
1244
|
}
|
|
1233
1245
|
catch {
|
|
1234
1246
|
// Unreadable index: every ref stays unresolved.
|
|
@@ -21,7 +21,7 @@ import { presentationFor } from "../../core/type-presentation.js";
|
|
|
21
21
|
import { embed } from "../../llm/embedder.js";
|
|
22
22
|
import { applyEmbeddingTemplate, resolveEmbeddingProfile } from "../../llm/embedders/profile.js";
|
|
23
23
|
import { normalizeEmbeddingEndpoint } from "../../llm/embedders/remote.js";
|
|
24
|
-
import { assertIndexPathReadable, closeDatabase, openExistingDatabase, } from "../../storage/repositories/index-connection.js";
|
|
24
|
+
import { assertIndexPathReadable, closeDatabase, openExistingDatabase, openReadonlyExistingDatabase, } from "../../storage/repositories/index-connection.js";
|
|
25
25
|
import { getAllEntries, getBaseBeliefStatesForDerivedTwins, getEntryById, getEntryCount, getEntryRefsAndTypes, } from "../../storage/repositories/index-entries-repository.js";
|
|
26
26
|
import { searchFts } from "../../storage/repositories/index-fts-repository.js";
|
|
27
27
|
import { getMeta } from "../../storage/repositories/index-meta-repository.js";
|
|
@@ -112,7 +112,8 @@ export async function searchLocal(input) {
|
|
|
112
112
|
// Bootstrap-only: builds the index inline when it cannot serve this stash.
|
|
113
113
|
// Content freshness is the writers' job (indexWrittenAssets + full runs);
|
|
114
114
|
// reads serve the existing index as-is.
|
|
115
|
-
|
|
115
|
+
if (!input.readOnly)
|
|
116
|
+
await ensureIndex(stashDir);
|
|
116
117
|
const dbPath = getDbPath();
|
|
117
118
|
// An index we cannot READ is not an index that does not exist (#791). Saying
|
|
118
119
|
// "No search index available" for a populated index the caller merely lacks
|
|
@@ -127,7 +128,10 @@ export async function searchLocal(input) {
|
|
|
127
128
|
mode: "keyword",
|
|
128
129
|
};
|
|
129
130
|
}
|
|
130
|
-
|
|
131
|
+
// A read-only search (`akm ui`) must not open the index for write: closing a
|
|
132
|
+
// read-write connection can checkpoint the WAL into the main file, which
|
|
133
|
+
// changes it even though nothing was written.
|
|
134
|
+
const db = input.readOnly ? openReadonlyExistingDatabase(dbPath) : openExistingDatabase(dbPath);
|
|
131
135
|
try {
|
|
132
136
|
const entryCount = getEntryCount(db);
|
|
133
137
|
if (entryCount === 0) {
|
|
@@ -41,6 +41,9 @@ const EXEMPT_COMMANDS = new Set([
|
|
|
41
41
|
"help",
|
|
42
42
|
// Embedded agent guide document (`src/output/cli-hints.ts`).
|
|
43
43
|
"hints",
|
|
44
|
+
// Prints the URL it is serving and then runs until interrupted; the data is
|
|
45
|
+
// the web UI's own JSON API, not a result envelope.
|
|
46
|
+
"ui",
|
|
44
47
|
]);
|
|
45
48
|
/**
|
|
46
49
|
* `<command> <subcommand>` pairs that are exempt while the rest of the group
|
|
@@ -121,6 +121,12 @@ export function getLatestImproveRun(db) {
|
|
|
121
121
|
.prepare(`SELECT ${IMPROVE_RUN_ROW_COLUMNS} FROM improve_runs WHERE dry_run = 0 ORDER BY started_at DESC LIMIT 1`)
|
|
122
122
|
.get();
|
|
123
123
|
}
|
|
124
|
+
/** The `limit` most recent real (non-dry-run) improve runs, newest first (`akm ui`'s run list). */
|
|
125
|
+
export function listRecentImproveRuns(db, limit) {
|
|
126
|
+
return db
|
|
127
|
+
.prepare("SELECT id, started_at, completed_at, ok, scope_mode, scope_value, strategy, result_json FROM improve_runs WHERE dry_run = 0 ORDER BY started_at DESC LIMIT ?")
|
|
128
|
+
.all(limit);
|
|
129
|
+
}
|
|
124
130
|
/**
|
|
125
131
|
* #950: count real (non-dry-run) improve_runs rows whose `started_at` falls
|
|
126
132
|
* in `[since, now)` — same filter as {@link queryImproveRuns}, but a `SELECT
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
4
|
import { resolveStorageLocations } from "../locations.js";
|
|
5
5
|
import { withManagedDb } from "../managed-db.js";
|
|
6
|
-
import { openExistingDatabase } from "./index-connection.js";
|
|
6
|
+
import { openExistingDatabase, openReadonlyExistingDatabase } from "./index-connection.js";
|
|
7
7
|
/**
|
|
8
8
|
* Busy-timeout (ms) for read-path telemetry writers. Small on purpose: a
|
|
9
9
|
* usage-event insert contending with a background reindex should be dropped,
|
|
@@ -41,7 +41,15 @@ export const TELEMETRY_BUSY_TIMEOUT_MS = 250;
|
|
|
41
41
|
* @returns Whatever `fn` returns.
|
|
42
42
|
*/
|
|
43
43
|
export function withIndexDb(fn, opts) {
|
|
44
|
-
return withManagedDb(() =>
|
|
44
|
+
return withManagedDb(() => {
|
|
45
|
+
const dbPath = resolveStorageLocations().indexDb;
|
|
46
|
+
if (!opts?.readOnly)
|
|
47
|
+
return openExistingDatabase(dbPath);
|
|
48
|
+
const db = openReadonlyExistingDatabase(dbPath);
|
|
49
|
+
if (!db)
|
|
50
|
+
throw new Error(`Index database not found at ${dbPath}. Run 'akm index' to build it.`);
|
|
51
|
+
return db;
|
|
52
|
+
}, (db) => {
|
|
45
53
|
if (opts?.busyTimeoutMs !== undefined) {
|
|
46
54
|
db.exec(`PRAGMA busy_timeout = ${Math.max(0, Math.floor(opts.busyTimeoutMs))}`);
|
|
47
55
|
}
|
|
@@ -17,6 +17,23 @@ export function listUsageEventRows(db, filter) {
|
|
|
17
17
|
ORDER BY datetime(created_at), id`)
|
|
18
18
|
.all(filter.sinceIso, untilBound, ...sourceParams);
|
|
19
19
|
}
|
|
20
|
+
/** One asset's feedback rows (`entry_ref` is the durable `bundle//conceptId`), newest first (`akm ui`'s asset view). */
|
|
21
|
+
export function listFeedbackEventRows(db, entryRef, limit) {
|
|
22
|
+
return db
|
|
23
|
+
.prepare(`SELECT id, event_type, query, entry_id, entry_ref, signal, metadata, source, created_at
|
|
24
|
+
FROM usage_events
|
|
25
|
+
WHERE event_type = 'feedback' AND entry_ref = ?
|
|
26
|
+
ORDER BY datetime(created_at) DESC, id DESC LIMIT ?`)
|
|
27
|
+
.all(entryRef, limit);
|
|
28
|
+
}
|
|
29
|
+
/** Feedback counts by signal for one asset, all sources (`akm ui`'s asset view). */
|
|
30
|
+
export function countFeedbackBySignal(db, entryRef) {
|
|
31
|
+
const rows = db
|
|
32
|
+
.prepare(`SELECT COALESCE(signal, 'none') AS signal, COUNT(*) AS n FROM usage_events
|
|
33
|
+
WHERE event_type = 'feedback' AND entry_ref = ? GROUP BY signal`)
|
|
34
|
+
.all(entryRef);
|
|
35
|
+
return Object.fromEntries(rows.map((row) => [row.signal, row.n]));
|
|
36
|
+
}
|
|
20
37
|
/**
|
|
21
38
|
* `select` events (a show within 60 s of a search that returned the ref) in the
|
|
22
39
|
* window. The events stream stores the ref as the user typed it (often
|
|
@@ -11,7 +11,7 @@ This version adds the policy batch: Policy decisions 1, 3, 4, 5, 7 and 9 are imp
|
|
|
11
11
|
## TLDR
|
|
12
12
|
|
|
13
13
|
- **The improve run** plans from fresh feedback plus a capped proactive lane, works only on the write-target bundle, and only touches assets someone retrieved in the last 90 days or new material. It takes a PID lock, syncs a git-backed bundle like `akm sync` (commit pending managed-path edits, fetch, merge, push) before it reads anything, has a 2-hour budget, commits and pushes only the files it wrote, and records every run in `improve_runs`.
|
|
14
|
-
- **Reflect** runs only on fresh negative feedback (or the proactive lane) and may change only `description`, `when_to_use` and a title,
|
|
14
|
+
- **Reflect** runs only on fresh negative feedback (or the proactive lane) and may change only `description`, `when_to_use` and a title, and the title edits the body only by adding a missing `# heading`; the body is otherwise byte for byte unchanged. The judge must score every criterion 4 or higher, and nothing is checked after it. A non-pass with a mean of 3.5 or more and a judge outage go to a person as a pending proposal; a lower non-pass or a defect is refused with no proposal and a hard 14-day window.
|
|
15
15
|
- **Distill** needs feedback in the last 30 days, and skips a memory whose only feedback is a positive with no reason or note (10 of 11 lessons from such memories were rejected). Lessons that pass the judge still go to a person, never the drain. The proactive lane never feeds distill.
|
|
16
16
|
- **Consolidate** only promotes memories to knowledge; the pair pass retires a duplicate only when the claim lists show nothing would be lost. A retirement is staged for the drain only if the claims are clean, there is no continuity risk and the second look agrees. There is no confidence cutoff. A `captureMode: hot` memory is retired, promoted and archived like any other. The pair pass, the coverage gate and the continuity check all need embeddings, which are off by default.
|
|
17
17
|
- **Triage** accepts only what a deterministic gate passed (a proposal whose judge stamp still matches its content hash, or a retirement the pair pass staged), up to 200 per run. The judgment step is off by default and may only accept consolidate promotions. Proactive-lane proposals, distill lessons and uncertain judge verdicts wait for a person.
|
|
@@ -211,7 +211,7 @@ These 14 bugs are fixed on this branch; the rules below describe the fixed behav
|
|
|
211
211
|
- **Ranking inside the lane** — Priority is the salience rank score computed from type-stub encoding, retrieval frequency, last use and file size. It has no outcome term. Ties break by staleness (never-touched first), then ref ascending. There is no randomisation, so at 15 a day and 30 days between visits at most about 450 distinct assets ever cycle, and among never-retrieved assets of one type priority differs only by file size, so the same smallest files recur. The lane has no memory of futility: a `no_change` reflect records `unchanged` and resets the 30-day clock, and the no-op damper applies only to the merged loop ranking, not to the lane's own priority (`rankScore` is used raw), so an asset reflect already found sound returns at full priority every 30 days. (`proactive-maintenance.ts:94-123`, `preparation.ts:963-966`)
|
|
212
212
|
- **Lane attribution** — Each planned ref is tagged `proactive` < `signal-delta` < `scope`; the strongest lane wins. The tag goes on the dry-run plan, the events, and the proposal as `eligibilitySource`. *Why:* so outcomes can be sliced by lane. (`preparation.ts:948-954`, `src/commands/proposal/proposal-types.ts:15-27`)
|
|
213
213
|
- **Events and plan** — A live run appends a `proactive_selected` event (count, dueTotal, neverReflected). The plan reports `configured`, `effective` (`dueDays`, `maxPerRun`), `candidatePool` and `selectedRefs`. (`preparation.ts:1119-1146`)
|
|
214
|
-
- **What it may propose** — Reflect proposals in practice: reflect
|
|
214
|
+
- **What it may propose** — Reflect proposals in practice: reflect edits frontmatter (`description`, `when_to_use`, `title`), plus at most a missing level-1 heading; the body is otherwise byte for byte unchanged. A proactively picked memory is never distilled, because it has no feedback signal (`memory_distill_requires_feedback`; non-memory picks fail distill's type filter); the only link to distill is that a distill attempt resets the lane's due clock. *Why:* on 396 labelled edits, frontmatter-only fixes were good 24/26 times, and edits that also rewrote the body were bad 175 of 224. (`docs/architecture/improvement.md` "Quality judge", `loop-stages.ts:302-310`)
|
|
215
215
|
- **Its proposals wait for a person** — `proposal drain` never accepts a proposal with `eligibilitySource:"proactive"`, whether a deterministic gate, an earlier staged accept or the judgment tier would take it. It is deferred with reason `proactive-needs-review` and is not sent to the judgment tier. Empty diffs are still rejected. Unattended, then, the lane cannot fix anything: the proposal expires after 90 days while the asset is due again after 30, so it is re-reflected at about day 30 and day 60 before the first proposal expires, and up to three identical pending proposals can exist for one asset (nothing blocks a second pending reflect proposal for a ref; see Reflect: Nothing prevents a second pending reflect proposal). *Why:* with the lane on and the drain promoting, reflect edits on assets with no feedback were accepted unattended. The nightly eval showed `when_to_use`, `type` and provenance stamps and a title heading added to assets no one asked about (#1147). (`src/commands/proposal/drain.ts:12-17,593-596`, CHANGELOG 0.10.26101001 "A proposal from the proactive lane waits for a person", #1147)
|
|
216
216
|
- **Unreviewed proactive proposals expire** — A pending proposal older than `archiveRetentionDays` (default 90; 0 disables) is archived as rejected (`expired`) in post-loop hygiene. Counting starts from the last reopen. Retire proposals expire the same way (fixed in this PR, #10). (`src/commands/proposal/repository.ts:872-927`)
|
|
217
217
|
|
|
@@ -296,7 +296,7 @@ All paths are relative to the `rules-010` worktree. Citation form is `path:line`
|
|
|
296
296
|
|
|
297
297
|
### What reflect may change
|
|
298
298
|
|
|
299
|
-
- **Only `description`, `when_to_use` and a title. The body text is the source's own, except that a title adds a `# title` heading.** The engine returns `confidence` plus `frontmatterPatch {description, when_to_use, title}`, each a non-empty single-line string or `null`. akm applies the patch itself. When the patch carries a title and the body has no level-1 heading, akm
|
|
299
|
+
- **Only `description`, `when_to_use` and a title. The body text is the source's own, byte for byte, except that a title adds a missing `# title` heading.** The engine returns `confidence` plus `frontmatterPatch {description, when_to_use, title}`, each a non-empty single-line string or `null`. akm applies the patch itself. When the patch carries a title and the body has no level-1 heading, akm inserts `# <title>` and one blank line at the body's first non-blank line, in the body's own line ending, and changes nothing else: leading blank lines stay, so the only body difference is the inserted heading. A missing H1 is a valid reflect edit and the only body change reflect may make, so a gate or reviewer that treats a proposal as frontmatter-only must accept it (#1183). Of 21 pending proactive-lane proposals on 2026-10-14, 17 differed from their source only by the inserted heading and the stripped blank lines, and all 17 were rejected as body edits; the title is kept because with the problem list in the prompt the model fixed 189 of 193 missing titles. After the judge, the proposal mint also splices `type:` and `updated:` lines into the frontmatter when they are missing (next rules and Proposal outcome). *Why:* on 396 labelled reflect edits, frontmatter-only fixes were good 24 of 26 times, and edits that also rewrote the body were bad 175 of 224. (`reflect.ts:5-10,229-235,237-259,313-322,428-446`; CHANGELOG 0.9.25-alpha.3)
|
|
300
300
|
- **A title becomes a `# title` heading only if the body has no level-1 heading.** Otherwise the title is ignored. The test is a multi-line regex that also matches a `# comment` line inside a fenced shell block, which suppresses the title. (`reflect.ts:257-259`)
|
|
301
301
|
- **Only the changed keys' frontmatter lines are rewritten.** Every other line is kept, and an unchanged-equal field is dropped from the patch. The mint then adds `type:` and `updated:` lines (see Mint below). *Why:* a source whose YAML the parser reads only partly (a broken description beside a list) must lose nothing. (`reflect.ts:243-245,274,289-304`)
|
|
302
302
|
- **A source whose frontmatter fence is opened but never closed gets no proposal.** It is reported as `no_change` ("empty diff"), recorded `unchanged` and counted by the plasticity counter; the person the comment says should fix it is never told. *Why:* it would leave the old block in the body under a new one, so a person fixes it. (`reflect.ts:253-255,1042-1059`)
|
|
@@ -413,7 +413,6 @@ All paths are relative to the `rules-010` worktree. Citation form is `path:line`
|
|
|
413
413
|
- `salience.ts:129-134` documents the no-op dampener as "Consolidation selection only", but `preparation.ts:956-969` applies it when ranking the reflect/distill loop.
|
|
414
414
|
- The `content_policy_reject` / `reflect-guard-rejected` classification still exists, but the reflect size rail it was named for was removed in 0.9.25-alpha.3.
|
|
415
415
|
- Reflect's judge labels a mean in [2.5, 4) as "review" (`runQualityJudge`), but reflect sends only a mean of 3.5 or more to a person and refuses 2.5 to 3.5; the cut is `REFLECT_REVIEW_MIN_SCORE`, not the judge's label (changed in this PR, decision 4). (`stage.ts:643`, `reflect.ts:1018,1104-1116`)
|
|
416
|
-
- The code comment at `reflect.ts:239-241` and the judge intro (`stage.ts:464`) say the body is kept byte for byte / unchanged apart from an added title heading; neither mentions that every leading blank line of the body is stripped when the heading is prepended (`reflect.ts:258-259`).
|
|
417
416
|
|
|
418
417
|
## Distill
|
|
419
418
|
|
|
@@ -241,7 +241,8 @@ it for a person too. On 2026-10-05 the gate had
|
|
|
241
241
|
staged 12 lessons and 10 were bad (they restated the memory, claimed what it
|
|
242
242
|
does not say, or filed a dated status as a lesson), and no score separated them
|
|
243
243
|
from the two good ones. Reflect revises only an asset's
|
|
244
|
-
`description`, `when_to_use` and title,
|
|
244
|
+
`description`, `when_to_use` and title, and edits the body only to add a missing level-1
|
|
245
|
+
heading; the rest of the body stays byte for byte. On 396 labelled edits,
|
|
245
246
|
those that fixed a frontmatter defect and left the body alone were good 24
|
|
246
247
|
times in 26, and those that also rewrote the body were bad 175 times in 224.
|
|
247
248
|
|
|
@@ -187,7 +187,7 @@ flowchart TD
|
|
|
187
187
|
|
|
188
188
|
`akmReflect` is the agent-invocation subprocess. It always emits a `reflect_invoked` event at entry, regardless of success or failure.
|
|
189
189
|
|
|
190
|
-
Reflect changes only an asset's `description`, `when_to_use` and title. The engine replies with those fields (each `null` when it needs no fix), and akm applies them to the asset it read: the body is kept byte for byte, apart from a `# <title>` heading akm
|
|
190
|
+
Reflect changes only an asset's `description`, `when_to_use` and title. The engine replies with those fields (each `null` when it needs no fix), and akm applies them to the asset it read: the body is kept byte for byte, apart from a `# <title>` heading and one blank line akm inserts when the body has no level-1 heading (the only body change reflect makes). A reply that changes nothing creates no proposal.
|
|
191
191
|
|
|
192
192
|
**Internal steps:**
|
|
193
193
|
|
|
@@ -208,7 +208,7 @@ Reflect changes only an asset's `description`, `when_to_use` and title. The engi
|
|
|
208
208
|
LLM does. An LLM endpoint that rejects JSON Schema gets the framed-markdown
|
|
209
209
|
contract instead.
|
|
210
210
|
6. Validate the reply with `parseSchemaReflectOutput` (`parseFramedReflectOutput` for the framed contract), which strips `<think>` blocks and code fences, then requires exactly the contract's fields: `confidence` and a `frontmatterPatch` of `description`, `when_to_use` and `title` (each a non-empty single-line string, or `null` for no change), plus `ref` when no target was given. A reply that fails gets one repair turn, shared across self-refine passes: the conversation so far, and a request to reformat the reply in the contract (`reflect-output-repair.md`). A reply still invalid fails with `parse_error` and queues nothing. A failed agent or SDK dispatch is reported as it ran, with its exit code and stderr.
|
|
211
|
-
7. Apply the patch (`applyReflectPatch`) to the source asset: its frontmatter with the non-null `description` and `when_to_use` set, and its body unchanged. A non-null `title` becomes a `# <title>` heading and one blank line at the
|
|
211
|
+
7. Apply the patch (`applyReflectPatch`) to the source asset: its frontmatter with the non-null `description` and `when_to_use` set, and its body unchanged. A non-null `title` becomes a `# <title>` heading and one blank line inserted at the body's first non-blank line, in the body's line ending, only when the body has no level-1 heading; otherwise it is ignored. Leading blank lines and every other body byte are kept. A source that requires a `description` and has none gets one derived from its own text when the patch gave none (#636). A patch that changes nothing (every field `null`, or equal to the source's, and no `description` to derive) is the `no_change` outcome (`reflect_skipped_noop`), as is a source with no content to patch; a result that differs only cosmetically is `reflect_skipped_cosmetic`. Neither creates a proposal.
|
|
212
212
|
8. Quality gate (`processes.reflect.qualityGate`, on unless disabled): one judge call scores the revision against the source on **need**, **preservation** and **quality** ([Quality judge](../improvement.md#quality-judge)), and passes it only when every criterion scores 4 or more. A score that does not pass is refused (`quality_rejected`, no proposal) unless its mean is 3.5 or more: that is uncertain, not bad, and the proposal is deferred for a person (`judge-uncertain`, gate `quality-gate`, with the criterion `scores` and the judge's reason) and records no rejection window. A judge that times out, errors or replies unparseably gives no verdict, and the proposal is deferred for review too (`judge-error`, gate `quality-gate`). Before the judge, with the gate on or off, reflect refuses (`quality_rejected`, no proposal, no judge call; the rule is the event's `reflectDefect`) a revision that adds placeholder text (`placeholder_added`: "please confirm", "to be confirmed"; not `TODO`, `TBD` or `FIXME`), talks about its own edit (`meta_commentary_added`: "the feedback says", "this revision") or copies frontmatter into its body (`frontmatter_copied_into_body`: `sources:` or `updated:` lines outside code, a `sources` value). Each rule counts only what the revision adds to its source (`findReflectDefect`), and its wording is a list that `processes.reflect.defectFilter` replaces or, when empty, turns off ([Configuration](../../reference/configuration.md#strategies)).
|
|
213
213
|
9. Write the proposal (`mintProposal`). A pass the judge passed is stamped `staged`, and the triage drain accepts it; with the gate off the proposal is minted unstamped for the drain to decide. One made with no judge configured (`no-judge-configured`) is deferred for review (gate `reflect`), and so is one whose judge gave no verdict (`judge-error`) and one in the judge's uncertain band (`judge-uncertain`). `akm proposal list` shows the reason; the drain leaves every deferral for a person.
|
|
214
214
|
|
package/docs/reference/README.md
CHANGED
|
@@ -5,6 +5,7 @@ Authoritative reference documentation for the akm CLI and its data.
|
|
|
5
5
|
- [Programmatic API](api.md) -- `akm-cli/api`: in-process `curate()` for the akm plugins
|
|
6
6
|
- [Bundle Types](bundle-types.md) -- Supported bundle formats and compatibility
|
|
7
7
|
- [CLI](cli.md) -- All `akm` commands and flags
|
|
8
|
+
- [UI API](ui-api.md) -- The read-only JSON API behind `akm ui`
|
|
8
9
|
- [Configuration](configuration.md) -- Engines, strategies, bundles, and settings
|
|
9
10
|
- [Supported Formats](supported-formats.md) -- Formats akm can index, from its own bundle layout to other tools' existing asset directories
|
|
10
11
|
- [Tasks](tasks.md) -- Task source v4 grammar, executable targets, scheduling, migration, and scheduler operations
|
package/docs/reference/cli.md
CHANGED
|
@@ -490,6 +490,55 @@ Notes you may see:
|
|
|
490
490
|
- Selects are read from the events stream, which records no source, so they are
|
|
491
491
|
counted whatever the usage source is.
|
|
492
492
|
|
|
493
|
+
### ui
|
|
494
|
+
|
|
495
|
+
**Experimental** (see [STABILITY.md](../../STABILITY.md)): the command, the page and the API it reads may change in any
|
|
496
|
+
release.
|
|
497
|
+
|
|
498
|
+
Serve a local web UI over your whole akm environment and open it in your browser. It is read-only except that you can accept and reject proposals in it: health and metrics, bundles
|
|
499
|
+
(with git status and asset counts), the effective config with where each value comes from, the proposal queue with
|
|
500
|
+
diffs, recent changes to the write-target stash, and search with an asset view. Every page reads through the functions
|
|
501
|
+
the matching CLI commands use, so the UI and the CLI report the same numbers.
|
|
502
|
+
|
|
503
|
+
| Page | Shows |
|
|
504
|
+
| --- | --- |
|
|
505
|
+
| Overview | Overall health, failing and warning check counts and the akm version; the health checks grouped by status with expandable evidence; metrics tiles; the last 10 improve runs (result, duration, model calls and failures, sync, triage) with bars; and a 14-day stacked usage chart (search, curate, show, feedback). |
|
|
506
|
+
| Bundles | One card per bundle: kind, path or source, writable, git branch, ahead/behind, last commit time and asset counts by type; plus the index state. |
|
|
507
|
+
| Config | The effective config as a collapsible tree with a `default` / `file` / `extends:<ref>` source badge on every key, the engines table and the `AKM_*` environment. |
|
|
508
|
+
| Proposals | The pending queue grouped by source (filter by source) with age and gate decision, a Recent tab of accepted, rejected and reverted proposals with reasons, and a detail page with the unified diff and, for retirements, the pair judgment. The detail page has Accept (after an inline confirmation) and Reject (with a required reason); the pending list has checkboxes and an Accept selected bar that accepts several in one commit. |
|
|
509
|
+
| Changes | Recent stash commits as a timeline by day, each tagged by origin (`improve-sync`, `accept`, `remember`, `sync`, `other`) with its changed paths. |
|
|
510
|
+
| Search | A query box with type and bundle filters. A result opens the asset view: frontmatter, the body as safe markdown, links, and feedback counts and history. |
|
|
511
|
+
|
|
512
|
+
The pages are plain HTML, CSS and JavaScript modules in `src/assets/ui/`: no framework, no build step, and no request
|
|
513
|
+
beyond the local server. All text from the API is escaped before it reaches the page.
|
|
514
|
+
|
|
515
|
+
```sh
|
|
516
|
+
akm ui
|
|
517
|
+
akm ui --port 8123 --no-open
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
| Flag | Description |
|
|
521
|
+
| --- | --- |
|
|
522
|
+
| `--port <n>` | Port to listen on. Default: a random free port. |
|
|
523
|
+
| `--host <addr>` | Address to bind. Default `127.0.0.1`. Anything else is reachable from other machines and prints a warning. |
|
|
524
|
+
| `--no-open` | Print the URL without opening a browser. A failed open also falls back to printing it. |
|
|
525
|
+
|
|
526
|
+
The command prints `akm ui: http://127.0.0.1:<port>/?token=<token>` and keeps serving until Ctrl-C (SIGINT or SIGTERM).
|
|
527
|
+
Each launch generates a new random token. Every `/api/` request must send it as the `X-Akm-Ui-Token` header or a `token`
|
|
528
|
+
query parameter, otherwise it gets a 401; the page keeps it in `sessionStorage`. The `Host` header must be the address
|
|
529
|
+
the server is bound to, which stops a web page from reaching it through DNS rebinding.
|
|
530
|
+
|
|
531
|
+
Browsing is read-only: `state.db` and `index.db` are opened read-only and never created or migrated, searching and
|
|
532
|
+
showing in the UI record no usage events (so browsing is never counted as demand) and build no index, and nothing is
|
|
533
|
+
written to a bundle or to config. The only writes are accepting and rejecting proposals (and accepting several at once),
|
|
534
|
+
through three `POST` routes that call the functions `akm proposal accept` and `reject` call, so a decision made in the
|
|
535
|
+
UI is recorded and committed exactly like one made on the command line. Those routes need the token in the
|
|
536
|
+
`X-Akm-Ui-Token` header (not the query string), a JSON content type and an `Origin` equal to the server's own address.
|
|
537
|
+
A read is short and the server holds no lock between requests, so browsing never blocks `akm improve`, `akm index` or
|
|
538
|
+
`akm sync`; a decision takes the same write lease the CLI takes and answers `409` at once if another process holds it. Secrets are redacted: config values under secret-like keys and URL credentials
|
|
539
|
+
are masked, and env and secret assets are shown as a stub, never with their values. The endpoints are documented in
|
|
540
|
+
[ui-api.md](ui-api.md). `--format` has no effect on this command.
|
|
541
|
+
|
|
493
542
|
### search
|
|
494
543
|
|
|
495
544
|
Search bundle assets, registries, or both.
|
|
@@ -1509,6 +1558,23 @@ akm clone "npm:@scope/pkg//scripts/deploy.sh" --dest /tmp/preview
|
|
|
1509
1558
|
|
|
1510
1559
|
Skills (directories) are copied recursively. Other types copy a single file.
|
|
1511
1560
|
|
|
1561
|
+
**Refs outside the type directories.** A ref whose concept id is not under a
|
|
1562
|
+
type directory (a skill in a flat skills repo, `my-repo//domain-name-brainstormer`;
|
|
1563
|
+
a page from a website or wiki bundle) is placed by what the source file is: a
|
|
1564
|
+
`SKILL.md` directory clones to `skills/<name>/`, any other recognized type to its
|
|
1565
|
+
own directory, and markdown of any other type to `knowledge/<name>.md`. `<name>`
|
|
1566
|
+
is the last segment of the concept id, slugified; `--name` overrides it. An asset
|
|
1567
|
+
that is not markdown and has no place in a bundle fails with a usage error that
|
|
1568
|
+
points to `akm import <path>`.
|
|
1569
|
+
|
|
1570
|
+
**Provenance.** A cloned markdown asset (a skill's `SKILL.md` included) records
|
|
1571
|
+
where it came from: the origin ref, `<bundle>//<concept-id>`, is added to its
|
|
1572
|
+
`sources:` frontmatter, keeping any entries already listed. `sources:` is free-form
|
|
1573
|
+
provenance that `akm lint` does not resolve, so a clone shared where the origin
|
|
1574
|
+
bundle is not installed does not report a missing ref. Other bytes of the file
|
|
1575
|
+
stay as written, non-markdown files and workflows are copied unchanged, and the
|
|
1576
|
+
origin is never modified.
|
|
1577
|
+
|
|
1512
1578
|
**Remote clone:** When the origin in the ref points to a package that is not
|
|
1513
1579
|
installed locally (e.g. an npm package or local path not in your bundle
|
|
1514
1580
|
sources), akm fetches it to the cache automatically and extracts the
|
|
@@ -2981,11 +3047,13 @@ akm proposal new <type> <name> --file ./prompt.md
|
|
|
2981
3047
|
akm proposal new skill code-review --task "PR-style review skill"
|
|
2982
3048
|
akm proposal new lesson docker-cleanup --file ./prompts/docker-cleanup.md
|
|
2983
3049
|
akm proposal new skill code-review --path team --task "PR-style review skill" # writes under skills/team/
|
|
3050
|
+
akm proposal new skill code-review --bundle team --task "PR-style review skill" # queued for the `team` bundle
|
|
2984
3051
|
```
|
|
2985
3052
|
|
|
2986
3053
|
| Flag | Description |
|
|
2987
3054
|
| --- | --- |
|
|
2988
3055
|
| `--path` | Relative subdirectory under the type dir to place the proposed asset in (e.g. `release`). The filename comes from `<name>`. |
|
|
3056
|
+
| `--bundle` | Configured writable bundle to queue the proposal for. The prompt takes its convention facts from that bundle, the proposal records it as its target (so `akm proposal accept` writes there), and it is queued in that bundle's queue (`akm proposal list --queue <bundle>`). An unknown, disabled or read-only bundle is refused, as for `akm remember --bundle`. Default: the working bundle |
|
|
2989
3057
|
| `--task` | Inline task text |
|
|
2990
3058
|
| `--file` | Read task text from a UTF-8 file |
|
|
2991
3059
|
| `--engine` | Override the default execution engine. Any kind works: an LLM engine, an agent CLI or `opencode-sdk` |
|