akm-cli 0.10.261004-beta → 0.10.261006-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 +58 -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/improve.js +17 -4
- package/dist/commands/improve/reflect-noise.js +4 -0
- package/dist/commands/improve/reflect.js +14 -5
- package/dist/commands/proposal/drain.js +13 -1
- package/dist/commands/proposal/proposal-cli.js +23 -6
- package/dist/commands/proposal/proposal.js +58 -11
- package/dist/commands/proposal/repository.js +67 -31
- package/dist/commands/read/search.js +2 -1
- package/dist/commands/read/show.js +8 -6
- 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/write-source.js +9 -5
- package/dist/indexer/search/db-search.js +2 -1
- package/dist/output/format-exempt.js +3 -0
- package/dist/sources/providers/git-stash.js +46 -15
- package/dist/storage/repositories/improve-runs-repository.js +6 -0
- package/dist/storage/repositories/metrics-repository.js +17 -0
- package/docs/architecture/0.10-process-rules.md +4 -5
- package/docs/architecture/improvement.md +9 -5
- package/docs/architecture/internals/improve-workflow.md +2 -2
- package/docs/reference/README.md +1 -0
- package/docs/reference/cli.md +62 -3
- package/docs/reference/ui-api.md +576 -0
- package/package.json +1 -1
|
@@ -436,34 +436,36 @@ function requireProposal(db, stashDir, id) {
|
|
|
436
436
|
* proposal (then most recent archived); else by a unique pending id prefix.
|
|
437
437
|
*/
|
|
438
438
|
export function resolveProposalId(stashDir, idOrRef, ctx) {
|
|
439
|
-
return withProposalsDb(ctx, (db) =>
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
439
|
+
return withProposalsDb(ctx, (db) => resolveProposalIdIn(db, stashDir, idOrRef));
|
|
440
|
+
}
|
|
441
|
+
/** {@link resolveProposalId} over a connection the caller holds (`akm ui` reads state.db read-only). */
|
|
442
|
+
export function resolveProposalIdIn(db, stashDir, idOrRef) {
|
|
443
|
+
const exact = getStateProposal(db, idOrRef, stashDir);
|
|
444
|
+
if (exact)
|
|
445
|
+
return exact;
|
|
446
|
+
if (idOrRef.includes(":") || idOrRef.includes("/")) {
|
|
447
|
+
const wantRef = filterRefIdentity(idOrRef);
|
|
448
|
+
// Should-fix 7: by-ref resolution never picks a retire proposal — the
|
|
449
|
+
// newest pending proposal for a ref could be a `consolidate-pair`
|
|
450
|
+
// retirement rather than the reflect/distill edit a person typed the
|
|
451
|
+
// ref to accept, and accepting it archives the asset instead. A retire
|
|
452
|
+
// is reached by its own proposal id, or by the explicit generator
|
|
453
|
+
// `consolidate-pair` (bulk accept/reject).
|
|
454
|
+
const newest = (status) => listStateProposals(db, { stashDir, ...(status !== undefined ? { status } : {}) })
|
|
455
|
+
.filter((p) => proposalMatchesRef(p.ref, wantRef) && !isRetireProposal(p))
|
|
456
|
+
.sort((a, b) => new Date(b.createdAt ?? 0).getTime() - new Date(a.createdAt ?? 0).getTime())[0];
|
|
457
|
+
const found = newest("pending") ?? newest();
|
|
458
|
+
if (found)
|
|
459
|
+
return found;
|
|
460
|
+
throw new NotFoundError(`No proposal found for ref "${idOrRef}".`, "PROPOSAL_NOT_FOUND");
|
|
461
|
+
}
|
|
462
|
+
const prefixMatches = listStateProposalIdsByPrefix(db, stashDir, idOrRef);
|
|
463
|
+
if (prefixMatches.length === 1)
|
|
464
|
+
return requireProposal(db, stashDir, prefixMatches[0]);
|
|
465
|
+
if (prefixMatches.length > 1) {
|
|
466
|
+
throw new UsageError(`Ambiguous prefix "${idOrRef}" — matches: ${prefixMatches.join(", ")}`, "INVALID_FLAG_VALUE");
|
|
467
|
+
}
|
|
468
|
+
throw new NotFoundError(`Proposal "${idOrRef}" not found.`, "PROPOSAL_NOT_FOUND");
|
|
467
469
|
}
|
|
468
470
|
/**
|
|
469
471
|
* The ledger outcome of a decision. A procedural refusal (retention expiry, a
|
|
@@ -1060,6 +1062,37 @@ function requireAcceptedTarget(proposal) {
|
|
|
1060
1062
|
function acceptCommitMessage(proposal) {
|
|
1061
1063
|
return `akm accept: ${proposal.source} ${proposal.id.slice(0, 8)} ${proposal.ref}`;
|
|
1062
1064
|
}
|
|
1065
|
+
function commitAccept(collector, target, proposal, paths) {
|
|
1066
|
+
if (collector)
|
|
1067
|
+
collector.push({ target, proposal, paths });
|
|
1068
|
+
else
|
|
1069
|
+
commitAcceptedPaths(target, acceptCommitMessage(proposal), paths);
|
|
1070
|
+
}
|
|
1071
|
+
/**
|
|
1072
|
+
* Commit everything `collector` holds, one commit per target repository: a lone
|
|
1073
|
+
* accept keeps its own subject; several get `<label>: N accepted (source count,
|
|
1074
|
+
* ...)` with one `<id8> <source> <ref>` body line per proposal. Empties the
|
|
1075
|
+
* collector. A failed commit only warns, as for a single accept.
|
|
1076
|
+
*/
|
|
1077
|
+
export function commitCollectedAccepts(collector, label) {
|
|
1078
|
+
const byRepo = new Map();
|
|
1079
|
+
for (const entry of collector.splice(0)) {
|
|
1080
|
+
const key = path.resolve(entry.target.source.repoPath ?? entry.target.source.path);
|
|
1081
|
+
byRepo.set(key, [...(byRepo.get(key) ?? []), entry]);
|
|
1082
|
+
}
|
|
1083
|
+
for (const entries of byRepo.values()) {
|
|
1084
|
+
const first = entries[0];
|
|
1085
|
+
if (entries.length === 1) {
|
|
1086
|
+
commitAcceptedPaths(first.target, acceptCommitMessage(first.proposal), first.paths);
|
|
1087
|
+
continue;
|
|
1088
|
+
}
|
|
1089
|
+
const counts = new Map();
|
|
1090
|
+
for (const { proposal } of entries)
|
|
1091
|
+
counts.set(proposal.source, (counts.get(proposal.source) ?? 0) + 1);
|
|
1092
|
+
const bySource = [...counts].sort((a, b) => b[1] - a[1]).map(([source, count]) => `${source} ${count}`);
|
|
1093
|
+
commitAcceptedPaths(first.target, `${label}: ${entries.length} accepted (${bySource.join(", ")})`, entries.flatMap((entry) => entry.paths), entries.map(({ proposal }) => `${proposal.id.slice(0, 8)} ${proposal.source} ${proposal.ref}`));
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1063
1096
|
/**
|
|
1064
1097
|
* O1 (alpha.9): an accepted consolidate PROMOTION retires its source memory
|
|
1065
1098
|
* (and its `.derived` twin), so promotion no longer leaves a memory/
|
|
@@ -1163,7 +1196,7 @@ async function promoteProposalWithLease(stashDir, config, id, options, ctx) {
|
|
|
1163
1196
|
const paths = [assetPath];
|
|
1164
1197
|
if (accepted.status === "accepted" && accepted.source === "consolidate")
|
|
1165
1198
|
retirePromotionSource(mutationTarget, accepted, paths);
|
|
1166
|
-
|
|
1199
|
+
commitAccept(options.commitCollector, mutationTarget, accepted, paths);
|
|
1167
1200
|
return { proposal: accepted, assetPath, ref: accepted.ref };
|
|
1168
1201
|
}
|
|
1169
1202
|
/**
|
|
@@ -1422,7 +1455,7 @@ async function retireProposalWithLease(stashDir, config, proposal, options, ctx)
|
|
|
1422
1455
|
paths.push(twinPath, path.join(mutationTarget.source.path, twinRecord.archivedPath), path.join(mutationTarget.source.path, twinRecord.auditPath));
|
|
1423
1456
|
}
|
|
1424
1457
|
if (paths.length > 0)
|
|
1425
|
-
|
|
1458
|
+
commitAccept(options.commitCollector, mutationTarget, proposal, paths);
|
|
1426
1459
|
if (archiveDirs.length === 0) {
|
|
1427
1460
|
// Recorded intent, but neither file is at its original location NOR
|
|
1428
1461
|
// archived under this proposal's id: something else removed the target
|
|
@@ -1838,7 +1871,10 @@ export function reopenProposals(stashDir, config, ids, options = {}, ctx) {
|
|
|
1838
1871
|
}
|
|
1839
1872
|
/** The proposal against the asset its accept would overwrite (same target resolution as accept). */
|
|
1840
1873
|
export function diffProposal(stashDir, config, id, options = {}, ctx) {
|
|
1841
|
-
|
|
1874
|
+
return diffLoadedProposal(config, getProposal(stashDir, id, ctx), options);
|
|
1875
|
+
}
|
|
1876
|
+
/** {@link diffProposal} for a proposal the caller already read (`akm ui` reads state.db read-only). */
|
|
1877
|
+
export function diffLoadedProposal(config, proposal, options = {}) {
|
|
1842
1878
|
const target = resolveProposalWriteTarget(config, proposal, options.target, options.queueTarget);
|
|
1843
1879
|
const targetPath = resolveAssetFilePathSafe(target.source, parseRefInput(proposal.ref));
|
|
1844
1880
|
const existing = targetPath && fs.existsSync(targetPath) ? fs.readFileSync(targetPath, "utf8") : null;
|
|
@@ -123,6 +123,7 @@ export async function akmSearch(input) {
|
|
|
123
123
|
// would leak hits from sources the caller did not request.
|
|
124
124
|
restrictToSources: namedSourceName !== undefined,
|
|
125
125
|
includeExcludedTypes: input.includeSessions === true,
|
|
126
|
+
readOnly: input.readOnly,
|
|
126
127
|
});
|
|
127
128
|
const registryResult = source === "local"
|
|
128
129
|
? undefined
|
|
@@ -201,7 +202,7 @@ function usageSearchMode(mode) {
|
|
|
201
202
|
return mode === "semantic" ? "semantic" : "keyword";
|
|
202
203
|
}
|
|
203
204
|
function maybeLogSearchEvent(input, query, response, mode) {
|
|
204
|
-
if (input.skipLogging)
|
|
205
|
+
if (input.skipLogging || input.readOnly)
|
|
205
206
|
return;
|
|
206
207
|
logSearchEvent(query, response, mode, input.eventSource, input.attributionProjection);
|
|
207
208
|
}
|
|
@@ -65,7 +65,7 @@ export async function akmShowUnified(input) {
|
|
|
65
65
|
{
|
|
66
66
|
const metaRef = parseMetaRef(ref);
|
|
67
67
|
if (metaRef)
|
|
68
|
-
return showStashMeta(metaRef);
|
|
68
|
+
return showStashMeta(metaRef, input.readOnly === true);
|
|
69
69
|
}
|
|
70
70
|
// Env/secret bodies have no safe fragment surface, and a fragment cannot
|
|
71
71
|
// widen what the env/secret renderers expose: both always omit the body
|
|
@@ -77,7 +77,7 @@ export async function akmShowUnified(input) {
|
|
|
77
77
|
// safe substrate below is its source of truth. Friendly heading selectors
|
|
78
78
|
// intentionally retain the normal source-live read behavior.
|
|
79
79
|
const parsedRef = parseBundleRef(ref);
|
|
80
|
-
if (!parsedRef.fragment?.startsWith("akm-fragment-")) {
|
|
80
|
+
if (!input.readOnly && !parsedRef.fragment?.startsWith("akm-fragment-")) {
|
|
81
81
|
const { primarySource } = resolveReadSources();
|
|
82
82
|
await ensurePrimaryIndexForRead(primarySource);
|
|
83
83
|
}
|
|
@@ -91,7 +91,7 @@ export async function akmShowUnified(input) {
|
|
|
91
91
|
enforceScopeOrThrow(result.path, ref, input.scope);
|
|
92
92
|
}
|
|
93
93
|
// Count prior shows of this ref before logging the current one.
|
|
94
|
-
if (!input.skipLogging) {
|
|
94
|
+
if (!input.skipLogging && !input.readOnly) {
|
|
95
95
|
const consumedRef = result.ref ?? makeBundleRef(undefined, parseBundleRef(ref).conceptId);
|
|
96
96
|
const priorShowCount = recentShowCount(consumedRef);
|
|
97
97
|
recordShowUsage(consumedRef, result.type, result.name, input.eventSource, result.path);
|
|
@@ -110,7 +110,7 @@ export async function akmShowUnified(input) {
|
|
|
110
110
|
* to that stash; an uninstalled origin yields an actionable "not installed"
|
|
111
111
|
* error. The file is read directly from disk — `.meta/` is never indexed.
|
|
112
112
|
*/
|
|
113
|
-
async function showStashMeta(metaRef) {
|
|
113
|
+
async function showStashMeta(metaRef, readOnly = false) {
|
|
114
114
|
const allSources = resolveSourceEntries();
|
|
115
115
|
const sources = resolveSourcesForOrigin(metaRef.origin, allSources);
|
|
116
116
|
if (metaRef.origin && sources.length === 0) {
|
|
@@ -123,7 +123,9 @@ async function showStashMeta(metaRef) {
|
|
|
123
123
|
if (!metaFile)
|
|
124
124
|
continue;
|
|
125
125
|
const editable = isEditable(metaFile.path, config, allSources);
|
|
126
|
-
|
|
126
|
+
if (!readOnly) {
|
|
127
|
+
appendEvent({ eventType: "show", ref: `meta:${metaRef.name}`, metadata: { type: "meta", name: metaRef.name } });
|
|
128
|
+
}
|
|
127
129
|
return {
|
|
128
130
|
type: "meta",
|
|
129
131
|
name: metaRef.name,
|
|
@@ -366,7 +368,7 @@ export async function showLocal(input) {
|
|
|
366
368
|
...(!editable ? { editHint: buildEditHint(canonicalRef) } : {}),
|
|
367
369
|
...showLinks(indexedEntry.itemRef, displayDefaultBundle),
|
|
368
370
|
};
|
|
369
|
-
const activeRun = await getActiveWorkflowRun(getCurrentWorkflowScopeKey());
|
|
371
|
+
const activeRun = input.readOnly ? null : await getActiveWorkflowRun(getCurrentWorkflowScopeKey());
|
|
370
372
|
if (activeRun) {
|
|
371
373
|
fullResponse.activeRun = activeRun;
|
|
372
374
|
}
|