akm-cli 0.9.17 → 0.9.19-alpha.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/CHANGELOG.md +157 -0
- package/STABILITY.md +2 -1
- package/dist/assets/hints/cli-hints-full.md +4 -2
- package/dist/assets/hints/cli-hints-short.md +5 -3
- package/dist/assets/prompts/reflect-feedback-framing.md +1 -1
- package/dist/commands/feedback-cli.js +1 -1
- package/dist/commands/health/checks.js +6 -6
- package/dist/commands/health.js +3 -3
- package/dist/commands/improve/consolidate/coverage.js +132 -0
- package/dist/commands/improve/consolidate/pair-pass.js +31 -21
- package/dist/commands/improve/consolidate.js +46 -13
- package/dist/commands/improve/distill.js +8 -4
- package/dist/commands/improve/eligibility.js +39 -9
- package/dist/commands/improve/improve-cli.js +9 -6
- package/dist/commands/improve/improve.js +13 -4
- package/dist/commands/improve/ledger.js +2 -2
- package/dist/commands/improve/loop-stages.js +2 -0
- package/dist/commands/improve/preparation.js +3 -1
- package/dist/commands/improve/reflect.js +2 -2
- package/dist/commands/improve/stage.js +43 -12
- package/dist/commands/proposal/diff-format.js +21 -0
- package/dist/commands/proposal/proposal-cli.js +48 -10
- package/dist/commands/proposal/proposal-types.js +11 -0
- package/dist/commands/proposal/proposal.js +60 -5
- package/dist/commands/proposal/repository.js +250 -18
- package/dist/commands/read/knowledge.js +13 -11
- package/dist/commands/read/remember-cli.js +7 -3
- package/dist/commands/sources/source-clone.js +1 -1
- package/dist/commands/tasks/tasks-cli.js +1 -1
- package/dist/commands/tasks/tasks.js +10 -3
- package/dist/core/mutation-target.js +8 -3
- package/dist/core/write-source.js +3 -2
- package/dist/indexer/usage/usage-events.js +2 -1
- package/dist/output/shapes/helpers.js +7 -0
- package/dist/output/shapes/passthrough.js +1 -0
- package/dist/output/shapes/proposal/reopen.js +14 -0
- package/dist/output/shapes.js +2 -0
- package/dist/output/text/helpers.js +1 -1
- package/dist/output/text/proposal/proposal.js +3 -1
- package/dist/output/text/proposal-format.js +87 -32
- package/dist/scripts/akm-migrate-node.js +102 -25
- package/dist/scripts/akm-migrate.js +102 -25
- package/dist/storage/repositories/improve-ledger-repository.js +65 -6
- package/dist/storage/repositories/index-vec-repository.js +13 -8
- package/dist/storage/repositories/proposals-repository.js +23 -0
- package/dist/storage/sqlite-read-snapshot.js +46 -2
- package/dist/storage/state-db-integrity.js +12 -9
- package/dist/tasks/run/load-task.js +5 -1
- package/docs/migration/README.md +1 -0
- package/docs/migration/release-notes/0.9.19.md +134 -0
- package/docs/migration/release-notes/README.md +5 -0
- package/docs/migration/v0.7-to-v0.8.md +2 -2
- package/docs/migration/v0.8-to-v0.9.md +5 -1
- package/docs/reference/cli.md +189 -28
- package/docs/reference/configuration.md +9 -8
- package/docs/reference/data-and-telemetry.md +24 -16
- package/package.json +1 -1
|
@@ -168,11 +168,11 @@ function parseWriteRefs(rawRefs, flag) {
|
|
|
168
168
|
}
|
|
169
169
|
return parsedRefs;
|
|
170
170
|
}
|
|
171
|
-
function resolveWriteRefRoots(target) {
|
|
171
|
+
function resolveWriteRefRoots(target, targetFlag) {
|
|
172
172
|
const cfg = loadConfig();
|
|
173
173
|
let writeTarget;
|
|
174
174
|
try {
|
|
175
|
-
writeTarget = resolveWriteTarget(cfg, target);
|
|
175
|
+
writeTarget = resolveWriteTarget(cfg, target, { flag: targetFlag });
|
|
176
176
|
}
|
|
177
177
|
catch (error) {
|
|
178
178
|
if (!target)
|
|
@@ -297,11 +297,11 @@ export const XREF_SOFT_CAP = 5;
|
|
|
297
297
|
* {@link XREF_SOFT_CAP} refs emits a stderr warning (soft cap) but still
|
|
298
298
|
* returns them all.
|
|
299
299
|
*/
|
|
300
|
-
export function resolveXrefsForWrite(rawXrefs, target) {
|
|
300
|
+
export function resolveXrefsForWrite(rawXrefs, target, targetFlag) {
|
|
301
301
|
const parsedRefs = parseWriteRefs(rawXrefs, "--xref");
|
|
302
302
|
if (parsedRefs.length === 0)
|
|
303
303
|
return [];
|
|
304
|
-
const { roots } = resolveWriteRefRoots(target);
|
|
304
|
+
const { roots } = resolveWriteRefRoots(target, targetFlag);
|
|
305
305
|
const unresolved = [];
|
|
306
306
|
const xrefs = [];
|
|
307
307
|
for (const parsed of parsedRefs) {
|
|
@@ -386,13 +386,13 @@ function isParseableYamlMapping(frontmatter) {
|
|
|
386
386
|
}
|
|
387
387
|
}
|
|
388
388
|
/** Resolve any qualified supersedes ref as the mutation target for remember/import. */
|
|
389
|
-
export function resolveSupersedesWriteTarget(rawRefs, target) {
|
|
389
|
+
export function resolveSupersedesWriteTarget(rawRefs, target, targetFlag) {
|
|
390
390
|
const config = loadConfig();
|
|
391
391
|
let effectiveTarget = target;
|
|
392
392
|
for (const parsed of parseWriteRefs(rawRefs, "--supersedes")) {
|
|
393
393
|
if (!parsed.origin)
|
|
394
394
|
continue;
|
|
395
|
-
const resolved = resolveMutationTarget(config, { type: parsed.type, name: parsed.name, origin: parsed.origin }, effectiveTarget).target;
|
|
395
|
+
const resolved = resolveMutationTarget(config, { type: parsed.type, name: parsed.name, origin: parsed.origin }, effectiveTarget, { flag: targetFlag }).target;
|
|
396
396
|
effectiveTarget = resolved.selector ?? resolved.source.name;
|
|
397
397
|
}
|
|
398
398
|
return effectiveTarget;
|
|
@@ -428,17 +428,17 @@ const SUPERSEDE_REJECTED_TYPES = new Set(["secret", "env", "task", "script"]);
|
|
|
428
428
|
* constraint (and never dirtying a non-target source outside its boundary
|
|
429
429
|
* commit). A ref that resolves only in another configured source — read-only
|
|
430
430
|
* OR writable-but-not-the-target — is returned with `writable: false` and a
|
|
431
|
-
* reason (naming the
|
|
431
|
+
* reason (naming the target-flag remedy when the source is writable); the
|
|
432
432
|
* caller writes the correction anyway and reports the demotion as not
|
|
433
433
|
* applied.
|
|
434
434
|
*
|
|
435
435
|
* Returns the deduplicated plan in argv order; empty input returns [].
|
|
436
436
|
*/
|
|
437
|
-
export function resolveSupersedesForWrite(rawRefs, target) {
|
|
437
|
+
export function resolveSupersedesForWrite(rawRefs, target, targetFlag) {
|
|
438
438
|
const parsedRefs = parseWriteRefs(rawRefs, "--supersedes");
|
|
439
439
|
if (parsedRefs.length === 0)
|
|
440
440
|
return [];
|
|
441
|
-
const { roots } = resolveWriteRefRoots(target);
|
|
441
|
+
const { roots } = resolveWriteRefRoots(target, targetFlag);
|
|
442
442
|
const plan = [];
|
|
443
443
|
const unresolved = [];
|
|
444
444
|
for (const parsed of parsedRefs) {
|
|
@@ -491,7 +491,7 @@ export function resolveSupersedesForWrite(rawRefs, target) {
|
|
|
491
491
|
: {
|
|
492
492
|
reason: namedWritableSource
|
|
493
493
|
? `resolves outside the write target and the working stash, in writable source "${namedWritableSource}" at ${root}; ` +
|
|
494
|
-
`re-run with --target ${namedWritableSource} to demote it there`
|
|
494
|
+
`re-run with ${targetFlag ?? "--target"} ${namedWritableSource} to demote it there`
|
|
495
495
|
: `resolves outside the write target and the working stash, in a read-only source at ${root}; ` +
|
|
496
496
|
"demotion only applies to assets in the write target or the working stash",
|
|
497
497
|
}),
|
|
@@ -518,7 +518,9 @@ export async function writeMarkdownAsset(options) {
|
|
|
518
518
|
const subPath = normalizeCreateSubPath(options.path);
|
|
519
519
|
const baseName = normalizeMarkdownAssetName(options.name, inferAssetName(options.content, options.fallbackPrefix, options.preferredName));
|
|
520
520
|
const normalizedName = combineCreatePath(subPath, baseName);
|
|
521
|
-
const resolved = resolveMutationTarget(cfg, { type: options.type, name: normalizedName }, options.target
|
|
521
|
+
const resolved = resolveMutationTarget(cfg, { type: options.type, name: normalizedName }, options.target, {
|
|
522
|
+
flag: options.targetFlag,
|
|
523
|
+
});
|
|
522
524
|
const { target } = resolved;
|
|
523
525
|
const { source, config } = target;
|
|
524
526
|
const typeRoot = path.join(source.path, options.type === "knowledge" ? "knowledge" : "memories");
|
|
@@ -33,6 +33,8 @@ async function fetchSimilarMemories(query, excludeRef, eventSource) {
|
|
|
33
33
|
return [];
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
|
+
/** The flag `remember` takes for its destination, as its errors spell it. */
|
|
37
|
+
const TARGET_FLAG = "--bundle";
|
|
36
38
|
/**
|
|
37
39
|
* `--target` was renamed to `--bundle` on `remember` in 0.9 (S8). citty is
|
|
38
40
|
* non-strict, so the retired spelling is silently absorbed rather than
|
|
@@ -153,8 +155,8 @@ export const rememberCommand = defineJsonCommand({
|
|
|
153
155
|
// untouched. Refs resolvable only in a configured extra stash source are
|
|
154
156
|
// accepted (cross-stash provenance).
|
|
155
157
|
const rawSupersedes = parseAllFlagValues("--supersedes");
|
|
156
|
-
const writeTarget = resolveSupersedesWriteTarget(rawSupersedes, args.bundle);
|
|
157
|
-
const xrefs = resolveXrefsForWrite(parseAllFlagValues("--xref"), writeTarget);
|
|
158
|
+
const writeTarget = resolveSupersedesWriteTarget(rawSupersedes, args.bundle, TARGET_FLAG);
|
|
159
|
+
const xrefs = resolveXrefsForWrite(parseAllFlagValues("--xref"), writeTarget, TARGET_FLAG);
|
|
158
160
|
// Collect and validate --supersedes occurrences (repeatable). Same
|
|
159
161
|
// before-any-write validation contract: an unresolvable ref exits 2 with
|
|
160
162
|
// nothing written AND nothing demoted (no partial correction). The
|
|
@@ -162,7 +164,7 @@ export const rememberCommand = defineJsonCommand({
|
|
|
162
164
|
// (correction provenance per the back-linking conventions); the demotion
|
|
163
165
|
// itself runs inside writeMarkdownAsset, ordered before the git boundary
|
|
164
166
|
// commit.
|
|
165
|
-
const supersedes = resolveSupersedesForWrite(rawSupersedes, writeTarget);
|
|
167
|
+
const supersedes = resolveSupersedesForWrite(rawSupersedes, writeTarget, TARGET_FLAG);
|
|
166
168
|
for (const s of supersedes) {
|
|
167
169
|
if (!xrefs.includes(s.ref))
|
|
168
170
|
xrefs.push(s.ref);
|
|
@@ -206,6 +208,7 @@ export const rememberCommand = defineJsonCommand({
|
|
|
206
208
|
preferredName: inferAssetName(body, "memory"),
|
|
207
209
|
force: args.force,
|
|
208
210
|
target: writeTarget,
|
|
211
|
+
targetFlag: TARGET_FLAG,
|
|
209
212
|
path: args.path,
|
|
210
213
|
supersedes,
|
|
211
214
|
});
|
|
@@ -313,6 +316,7 @@ export const rememberCommand = defineJsonCommand({
|
|
|
313
316
|
preferredName: inferAssetName(body, "memory"),
|
|
314
317
|
force: args.force,
|
|
315
318
|
target: writeTarget,
|
|
319
|
+
targetFlag: TARGET_FLAG,
|
|
316
320
|
path: args.path,
|
|
317
321
|
supersedes,
|
|
318
322
|
});
|
|
@@ -39,7 +39,7 @@ export async function akmClone(options) {
|
|
|
39
39
|
// are not bundle slugs).
|
|
40
40
|
const parsed = parseQualifiedRefInput(options.sourceRef);
|
|
41
41
|
const config = hasUnmanagedDest ? undefined : loadConfig();
|
|
42
|
-
const resolvedWriteTarget = config ? resolveWriteTarget(config, options.target) : undefined;
|
|
42
|
+
const resolvedWriteTarget = config ? resolveWriteTarget(config, options.target, { flag: "--bundle" }) : undefined;
|
|
43
43
|
const writeTarget = resolvedWriteTarget ? prepareWriteTargetForMutation(resolvedWriteTarget) : undefined;
|
|
44
44
|
// An unmanaged --dest does not require any configured write target.
|
|
45
45
|
let allSources;
|
|
@@ -212,7 +212,7 @@ const tasksAddCommand = defineJsonCommand({
|
|
|
212
212
|
},
|
|
213
213
|
command: {
|
|
214
214
|
type: "string",
|
|
215
|
-
description: 'Exact shell string to run on the schedule (no AI agent), e.g. "akm improve --strategy
|
|
215
|
+
description: 'Exact shell string to run on the schedule (no AI agent), e.g. "akm improve --strategy reflect-distill".',
|
|
216
216
|
},
|
|
217
217
|
engine: { type: "string", description: "Engine to use for prompt targets (default: defaults.engine)" },
|
|
218
218
|
model: { type: "string", description: "Model override for prompt targets" },
|
|
@@ -688,7 +688,7 @@ function taskAssetRef(id) {
|
|
|
688
688
|
/** The bundle `task add` writes into: its write target, config, stash path, and name. */
|
|
689
689
|
function resolveTaskBundle(target) {
|
|
690
690
|
const config = loadConfig();
|
|
691
|
-
const resolved = prepareWriteTargetForMutation(resolveWriteTarget(config, target, { requireWritable: true }));
|
|
691
|
+
const resolved = prepareWriteTargetForMutation(resolveWriteTarget(config, target, { requireWritable: true, flag: "--bundle" }));
|
|
692
692
|
return { resolved, config, stashDir: resolved.source.path, bundleName: resolved.source.name };
|
|
693
693
|
}
|
|
694
694
|
function taskProjectionAssetResolver(config, bundleName, bundleRoot) {
|
|
@@ -696,7 +696,8 @@ function taskProjectionAssetResolver(config, bundleName, bundleRoot) {
|
|
|
696
696
|
if (bundle === bundleName) {
|
|
697
697
|
return { file: await resolveAssetPath(bundleRoot, type, name), bundleRoot };
|
|
698
698
|
}
|
|
699
|
-
|
|
699
|
+
// `bundle` is the qualifier of an asset ref in the task, not a flag.
|
|
700
|
+
const target = resolveWriteTarget(config, bundle, { requireWritable: false, flag: "The asset ref's bundle" });
|
|
700
701
|
return {
|
|
701
702
|
file: await resolveAssetPath(target.source.path, type, name),
|
|
702
703
|
bundleRoot: target.source.path,
|
|
@@ -723,7 +724,13 @@ export function resolveTaskReadBundle(refBundle, flagBundle) {
|
|
|
723
724
|
else {
|
|
724
725
|
const configured = resolveActiveConfiguredSources(config).some((source) => source.name === selector);
|
|
725
726
|
const implicit = configured ? undefined : resolveImplicitScheduledBundleTarget(config, selector);
|
|
726
|
-
resolved =
|
|
727
|
+
resolved =
|
|
728
|
+
implicit ??
|
|
729
|
+
resolveWriteTarget(config, selector, {
|
|
730
|
+
requireWritable: false,
|
|
731
|
+
// The selector is `--bundle` when given, else the task ref's own bundle qualifier.
|
|
732
|
+
flag: flagBundle !== undefined ? "--bundle" : "The task ref's bundle",
|
|
733
|
+
});
|
|
727
734
|
}
|
|
728
735
|
if (refBundle && resolved.source.name !== refBundle) {
|
|
729
736
|
throw new UsageError(`Task ref bundle ${JSON.stringify(refBundle)} does not match the resolved source.`, "INVALID_FLAG_VALUE");
|
|
@@ -50,9 +50,14 @@ function resolveExplicitMutationTarget(config, explicitTarget, options) {
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
|
-
/**
|
|
53
|
+
/**
|
|
54
|
+
* Reconcile a qualified mutation ref with the explicit target (`--target`, or
|
|
55
|
+
* the `options.flag` the caller's command spells), then resolve the write
|
|
56
|
+
* destination.
|
|
57
|
+
*/
|
|
54
58
|
export function resolveMutationTarget(config, ref, explicitTarget, options = {}) {
|
|
55
|
-
const
|
|
59
|
+
const flag = options.flag ?? "--target";
|
|
60
|
+
const writeOptions = { requireWritable: options.requireWritable, flag };
|
|
56
61
|
const qualifiedTarget = ref.origin ? resolveBundleWriteTarget(config, ref.origin, writeOptions) : undefined;
|
|
57
62
|
const explicitResolved = explicitTarget
|
|
58
63
|
? resolveExplicitMutationTarget(config, explicitTarget, writeOptions)
|
|
@@ -60,7 +65,7 @@ export function resolveMutationTarget(config, ref, explicitTarget, options = {})
|
|
|
60
65
|
if (qualifiedTarget &&
|
|
61
66
|
explicitResolved &&
|
|
62
67
|
path.resolve(qualifiedTarget.source.path) !== path.resolve(explicitResolved.source.path)) {
|
|
63
|
-
throw new UsageError(`Qualified ref bundle "${ref.origin}" conflicts with
|
|
68
|
+
throw new UsageError(`Qualified ref bundle "${ref.origin}" conflicts with ${flag} "${explicitTarget}".`, "INVALID_FLAG_VALUE", `Drop ${flag} or select the same bundle.`);
|
|
64
69
|
}
|
|
65
70
|
let target = qualifiedTarget ?? explicitResolved ?? resolveWriteTarget(config, undefined, writeOptions);
|
|
66
71
|
const bundleId = ref.origin ?? canonicalBundleIdForTarget(config, target);
|
|
@@ -59,16 +59,17 @@ export function resolveWriteTarget(akmConfig, explicitTarget, options = {}) {
|
|
|
59
59
|
const allConfiguredSources = resolveConfiguredSources(akmConfig);
|
|
60
60
|
const configuredSources = resolveActiveConfiguredSources(akmConfig);
|
|
61
61
|
const requireWritable = options.requireWritable !== false;
|
|
62
|
+
const flag = options.flag ?? "--target";
|
|
62
63
|
if (explicitTarget) {
|
|
63
64
|
const match = configuredSources.find((s) => s.name === explicitTarget);
|
|
64
65
|
if (!match) {
|
|
65
66
|
if (allConfiguredSources.some((source) => source.name === explicitTarget)) {
|
|
66
67
|
throw new UsageError(`Bundle "${explicitTarget}" is disabled.`, "INVALID_FLAG_VALUE");
|
|
67
68
|
}
|
|
68
|
-
throw new UsageError(
|
|
69
|
+
throw new UsageError(`${flag} must reference a source name from your config. No source named "${explicitTarget}" is configured. Run \`akm bundle list\` to see available sources.`, "INVALID_FLAG_VALUE");
|
|
69
70
|
}
|
|
70
71
|
if (requireWritable && !resolveWritable({ type: match.type, writable: match.writable })) {
|
|
71
|
-
throw new ConfigError(`source ${explicitTarget} is not writable`, "INVALID_CONFIG_FILE", `Set \`writable: true\` on the "${explicitTarget}" source in your config, or pass
|
|
72
|
+
throw new ConfigError(`source ${explicitTarget} is not writable`, "INVALID_CONFIG_FILE", `Set \`writable: true\` on the "${explicitTarget}" source in your config, or pass ${flag} to a different source.`);
|
|
72
73
|
}
|
|
73
74
|
return adaptConfiguredSource(match);
|
|
74
75
|
}
|
|
@@ -85,7 +85,8 @@ export function countFeedbackSignals(db, entryId) {
|
|
|
85
85
|
* Count usage events of a given `event_type`.
|
|
86
86
|
*
|
|
87
87
|
* Lifted verbatim from `akm improve` (improve.ts) where the show-event count
|
|
88
|
-
* was hand-rolled inline to drive the
|
|
88
|
+
* was hand-rolled inline to drive the warning that the retrieval scope matches
|
|
89
|
+
* only search-retrieved assets.
|
|
89
90
|
*/
|
|
90
91
|
export function countUsageEventsByType(db, eventType) {
|
|
91
92
|
return db.prepare("SELECT COUNT(*) AS cnt FROM usage_events WHERE event_type = ?").get(eventType)
|
|
@@ -86,6 +86,7 @@ export function shapeProposalEntry(entry, detail) {
|
|
|
86
86
|
"confidence",
|
|
87
87
|
"gateDecision",
|
|
88
88
|
"review",
|
|
89
|
+
"reviewHistory",
|
|
89
90
|
"retirement",
|
|
90
91
|
]);
|
|
91
92
|
}
|
|
@@ -102,6 +103,7 @@ export function shapeProposalEntry(entry, detail) {
|
|
|
102
103
|
"gateDecision",
|
|
103
104
|
"payload",
|
|
104
105
|
"review",
|
|
106
|
+
"reviewHistory",
|
|
105
107
|
"retirement",
|
|
106
108
|
"retiredArchive",
|
|
107
109
|
"promotionSource",
|
|
@@ -167,6 +169,11 @@ export function shapeProposalDiffOutput(result, detail) {
|
|
|
167
169
|
isNew: result.isNew,
|
|
168
170
|
unified: result.unified,
|
|
169
171
|
...(result.targetPath !== undefined ? { targetPath: result.targetPath } : {}),
|
|
172
|
+
// A retire proposal (#997): the diff alone is a file leaving, so the pair
|
|
173
|
+
// verdict and the accept/revert note travel with it at every detail level.
|
|
174
|
+
...(result.op !== undefined ? { op: result.op } : {}),
|
|
175
|
+
...(result.retirement !== undefined ? { retirement: result.retirement } : {}),
|
|
176
|
+
...(result.note !== undefined ? { note: result.note } : {}),
|
|
170
177
|
};
|
|
171
178
|
if (detail === "full") {
|
|
172
179
|
return { schemaVersion: result.schemaVersion, ...base };
|
|
@@ -0,0 +1,14 @@
|
|
|
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
|
+
// Output shape registration for `akm proposal reopen` (#997). One reopened
|
|
5
|
+
// proposal is the same envelope `reject` returns (`ok`, `id`, `ref`, an
|
|
6
|
+
// optional `reason` — here the reopen reason — and the shaped proposal), so it
|
|
7
|
+
// shares that shaper; several are the passthrough `proposal-reopen-batch`.
|
|
8
|
+
import { shapeProposalRejectOutput } from "../helpers.js";
|
|
9
|
+
export const proposalReopenShapes = [
|
|
10
|
+
{
|
|
11
|
+
command: "proposal-reopen",
|
|
12
|
+
handler: (result, detail) => shapeProposalRejectOutput(result, detail),
|
|
13
|
+
},
|
|
14
|
+
];
|
package/dist/output/shapes.js
CHANGED
|
@@ -29,6 +29,7 @@ import { proposalDiffShapes } from "./shapes/proposal/diff.js";
|
|
|
29
29
|
import { proposalListShapes } from "./shapes/proposal/list.js";
|
|
30
30
|
import { proposalProducerShapes } from "./shapes/proposal/producer.js";
|
|
31
31
|
import { proposalRejectShapes } from "./shapes/proposal/reject.js";
|
|
32
|
+
import { proposalReopenShapes } from "./shapes/proposal/reopen.js";
|
|
32
33
|
import { proposalShowShapes } from "./shapes/proposal/show.js";
|
|
33
34
|
import { getOutputShapeHandler, registerOutputShapes } from "./shapes/registry.js";
|
|
34
35
|
import { registrySearchShapes } from "./shapes/registry-search.js";
|
|
@@ -51,6 +52,7 @@ const BUILT_IN_OUTPUT_SHAPES = [
|
|
|
51
52
|
...proposalShowShapes,
|
|
52
53
|
...proposalAcceptShapes,
|
|
53
54
|
...proposalRejectShapes,
|
|
55
|
+
...proposalReopenShapes,
|
|
54
56
|
...proposalDiffShapes,
|
|
55
57
|
...proposalProducerShapes,
|
|
56
58
|
...envListShapes,
|
|
@@ -18,6 +18,6 @@
|
|
|
18
18
|
export { formatAddPlain, formatBundleRenamePlain, formatBundleShowPlain, formatClonePlain, formatConfigPlain, formatCuratePlain, formatEnvCreatePlain, formatEnvExportPlain, formatEnvListPlain, formatEnvRemovePlain, formatEventLine, formatEventsPlain, formatFeedbackPlain, formatImportPlain, formatIndexPlain, formatInfoPlain, formatInitPlain, formatListPlain, formatModelsListPlain, formatRegistryAddPlain, formatRegistryListPlain, formatRegistryRemovePlain, formatRegistrySearchPlain, formatRememberPlain, formatRemovePlain, formatSearchPlain, formatSyncPlain, formatUpdatePlain, formatUpgradePlain, } from "./command-format.js";
|
|
19
19
|
export { formatHealthPlain } from "./health-format.js";
|
|
20
20
|
export { formatLintPlain } from "./lint-format.js";
|
|
21
|
-
export { formatGateDecisionSummary, formatProposalAcceptPlain, formatProposalDiffPlain, formatProposalDrainPlain, formatProposalListPlain, formatProposalProducerPlain, formatProposalRejectPlain, formatProposalShowPlain, } from "./proposal-format.js";
|
|
21
|
+
export { formatGateDecisionSummary, formatProposalAcceptPlain, formatProposalDiffPlain, formatProposalDrainPlain, formatProposalListPlain, formatProposalProducerPlain, formatProposalRejectPlain, formatProposalReopenBatchPlain, formatProposalReopenPlain, formatProposalShowPlain, } from "./proposal-format.js";
|
|
22
22
|
export { formatShowPlain } from "./show-format.js";
|
|
23
23
|
export { formatWorkflowCreatePlain, formatWorkflowListPlain, formatWorkflowPlanPlain, formatWorkflowResumePlain, formatWorkflowRunPlain, formatWorkflowStatusPlain, } from "./workflow-format.js";
|
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
4
|
// Output text formatters for `akm proposal *` (#225).
|
|
5
|
-
import { formatProposalAcceptPlain, formatProposalDiffPlain, formatProposalDrainPlain, formatProposalListPlain, formatProposalRejectPlain, formatProposalShowPlain, } from "../helpers.js";
|
|
5
|
+
import { formatProposalAcceptPlain, formatProposalDiffPlain, formatProposalDrainPlain, formatProposalListPlain, formatProposalRejectPlain, formatProposalReopenBatchPlain, formatProposalReopenPlain, formatProposalShowPlain, } from "../helpers.js";
|
|
6
6
|
export const proposalFormatters = [
|
|
7
7
|
{ command: "proposal-list", handler: (r) => formatProposalListPlain(r) },
|
|
8
8
|
{ command: "proposal-show", handler: (r) => formatProposalShowPlain(r) },
|
|
9
9
|
{ command: "proposal-accept", handler: (r) => formatProposalAcceptPlain(r) },
|
|
10
10
|
{ command: "proposal-reject", handler: (r) => formatProposalRejectPlain(r) },
|
|
11
|
+
{ command: "proposal-reopen", handler: (r) => formatProposalReopenPlain(r) },
|
|
12
|
+
{ command: "proposal-reopen-batch", handler: (r) => formatProposalReopenBatchPlain(r) },
|
|
11
13
|
{ command: "proposal-diff", handler: (r) => formatProposalDiffPlain(r) },
|
|
12
14
|
{ command: "proposal-drain", handler: (r) => formatProposalDrainPlain(r) },
|
|
13
15
|
];
|
|
@@ -113,6 +113,47 @@ export function formatProposalListPlain(r) {
|
|
|
113
113
|
}
|
|
114
114
|
return lines.join("\n").trimEnd();
|
|
115
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* A retire proposal's verdict lines — the pair judge's label and reason, and
|
|
118
|
+
* the continuity check's failing queries — as `proposal show` and `proposal
|
|
119
|
+
* diff` both print them. `retirement` is the stored block, which `proposal
|
|
120
|
+
* diff` reports under the same keys.
|
|
121
|
+
*/
|
|
122
|
+
function retireVerdictLines(retirement) {
|
|
123
|
+
const lines = [
|
|
124
|
+
`retire.label: ${String(retirement.judgeLabel)} (cosine=${String(retirement.cosine)})`,
|
|
125
|
+
`retire.reason: ${String(retirement.judgeReason)}`,
|
|
126
|
+
];
|
|
127
|
+
// Item 1 (continuity check): flagged, but still minted — never swept by a
|
|
128
|
+
// bulk accept, only acceptable by id, so a reviewer must see it.
|
|
129
|
+
const continuityRisk = retirement.continuityRisk;
|
|
130
|
+
if (continuityRisk) {
|
|
131
|
+
const failingQueries = typeof continuityRisk.failingQueries === "number" ? continuityRisk.failingQueries : 0;
|
|
132
|
+
const unverifiedQueries = typeof continuityRisk.unverifiedQueries === "number" ? continuityRisk.unverifiedQueries : 0;
|
|
133
|
+
const summary = [];
|
|
134
|
+
if (failingQueries > 0) {
|
|
135
|
+
summary.push(`${failingQueries} of the retired asset's own quer${failingQueries === 1 ? "y" : "ies"} would not have found the successor top 10`);
|
|
136
|
+
}
|
|
137
|
+
// S2: a query the search call never ran, or that fell back to
|
|
138
|
+
// keyword-only ranking, is never silently trusted OR silently
|
|
139
|
+
// dropped — it excludes the proposal from bulk accept on its own.
|
|
140
|
+
if (unverifiedQueries > 0) {
|
|
141
|
+
summary.push(`${unverifiedQueries} quer${unverifiedQueries === 1 ? "y" : "ies"} unverified (search failed or used the keyword-only fallback)`);
|
|
142
|
+
}
|
|
143
|
+
lines.push(`retire.continuityRisk: ${summary.join("; ")} — excluded from bulk accept`);
|
|
144
|
+
// N3 / S4: the actual failing query text, not just the count — a
|
|
145
|
+
// reviewer deciding whether to accept by id needs to see what would
|
|
146
|
+
// stop resolving, not just how many queries.
|
|
147
|
+
const ranks = Array.isArray(continuityRisk.ranks) ? continuityRisk.ranks : [];
|
|
148
|
+
for (const rank of ranks) {
|
|
149
|
+
const successorRank = rank.successorRank === null || rank.successorRank === undefined
|
|
150
|
+
? "absent from top 10"
|
|
151
|
+
: `#${String(rank.successorRank)}`;
|
|
152
|
+
lines.push(` - "${String(rank.query)}": retired #${String(rank.retiredRank)}, successor ${successorRank}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return lines;
|
|
156
|
+
}
|
|
116
157
|
export function formatProposalShowPlain(r) {
|
|
117
158
|
const p = r.proposal;
|
|
118
159
|
const lines = [];
|
|
@@ -150,42 +191,24 @@ export function formatProposalShowPlain(r) {
|
|
|
150
191
|
if (review.decidedAt)
|
|
151
192
|
lines.push(`review.decidedAt: ${String(review.decidedAt)}`);
|
|
152
193
|
}
|
|
194
|
+
// `akm proposal reopen` (#997): the rejection each reopen undid, so a
|
|
195
|
+
// pending proposal that was once rejected says so.
|
|
196
|
+
const history = Array.isArray(p.reviewHistory) ? p.reviewHistory : [];
|
|
197
|
+
for (const entry of history) {
|
|
198
|
+
const undone = entry.review;
|
|
199
|
+
const was = undone
|
|
200
|
+
? `${String(undone.outcome ?? "?")}${undone.reason ? `: ${String(undone.reason)}` : ""} (${String(undone.decidedAt ?? "?")})`
|
|
201
|
+
: "an unrecorded review";
|
|
202
|
+
const why = entry.reopenReason ? ` (${String(entry.reopenReason)})` : "";
|
|
203
|
+
lines.push(`reopened: ${String(entry.reopenedAt)}${why}, undoing ${was}`);
|
|
204
|
+
}
|
|
153
205
|
// alpha.9: a consolidate retire proposal writes no content (`payload.content`
|
|
154
206
|
// is empty by design) — this is the reason a reviewer needs instead. `diff`
|
|
155
207
|
// shows the body being retired.
|
|
156
208
|
const retirement = p.retirement;
|
|
157
209
|
if (retirement) {
|
|
158
210
|
lines.push(`retire: ${String(retirement.retiredRef)} -> ${String(retirement.successorRef)}`);
|
|
159
|
-
lines.push(
|
|
160
|
-
lines.push(`retire.reason: ${String(retirement.judgeReason)}`);
|
|
161
|
-
// Item 1 (continuity check): flagged, but still minted — never swept by a
|
|
162
|
-
// bulk accept, only acceptable by id, so a reviewer must see it here.
|
|
163
|
-
const continuityRisk = retirement.continuityRisk;
|
|
164
|
-
if (continuityRisk) {
|
|
165
|
-
const failingQueries = typeof continuityRisk.failingQueries === "number" ? continuityRisk.failingQueries : 0;
|
|
166
|
-
const unverifiedQueries = typeof continuityRisk.unverifiedQueries === "number" ? continuityRisk.unverifiedQueries : 0;
|
|
167
|
-
const summary = [];
|
|
168
|
-
if (failingQueries > 0) {
|
|
169
|
-
summary.push(`${failingQueries} of the retired asset's own quer${failingQueries === 1 ? "y" : "ies"} would not have found the successor top 10`);
|
|
170
|
-
}
|
|
171
|
-
// S2: a query the search call never ran, or that fell back to
|
|
172
|
-
// keyword-only ranking, is never silently trusted OR silently
|
|
173
|
-
// dropped — it excludes the proposal from bulk accept on its own.
|
|
174
|
-
if (unverifiedQueries > 0) {
|
|
175
|
-
summary.push(`${unverifiedQueries} quer${unverifiedQueries === 1 ? "y" : "ies"} unverified (search failed or used the keyword-only fallback)`);
|
|
176
|
-
}
|
|
177
|
-
lines.push(`retire.continuityRisk: ${summary.join("; ")} — excluded from bulk accept`);
|
|
178
|
-
// N3 / S4: the actual failing query text, not just the count — a
|
|
179
|
-
// reviewer deciding whether to accept by id needs to see what would
|
|
180
|
-
// stop resolving, not just how many queries.
|
|
181
|
-
const ranks = Array.isArray(continuityRisk.ranks) ? continuityRisk.ranks : [];
|
|
182
|
-
for (const rank of ranks) {
|
|
183
|
-
const successorRank = rank.successorRank === null || rank.successorRank === undefined
|
|
184
|
-
? "absent from top 10"
|
|
185
|
-
: `#${String(rank.successorRank)}`;
|
|
186
|
-
lines.push(` - "${String(rank.query)}": retired #${String(rank.retiredRank)}, successor ${successorRank}`);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
211
|
+
lines.push(...retireVerdictLines(retirement));
|
|
189
212
|
}
|
|
190
213
|
const validation = r.validation;
|
|
191
214
|
if (validation) {
|
|
@@ -215,7 +238,10 @@ export function formatProposalShowPlain(r) {
|
|
|
215
238
|
}
|
|
216
239
|
}
|
|
217
240
|
const payload = p.payload;
|
|
218
|
-
|
|
241
|
+
// A retire proposal's payload is empty by design (it archives a file, it
|
|
242
|
+
// writes none): a bare `payload:` heading reads as "replaced by nothing" —
|
|
243
|
+
// the misreading #997 is about — so the retirement lines above stand alone.
|
|
244
|
+
if (!retirement && payload && typeof payload.content === "string") {
|
|
219
245
|
lines.push("");
|
|
220
246
|
lines.push("payload:");
|
|
221
247
|
lines.push(payload.content);
|
|
@@ -229,6 +255,18 @@ export function formatProposalRejectPlain(r) {
|
|
|
229
255
|
const reason = r.reason ? ` (${String(r.reason)})` : "";
|
|
230
256
|
return `Rejected proposal ${String(r.id)} (${String(r.ref)})${reason}`;
|
|
231
257
|
}
|
|
258
|
+
export function formatProposalReopenPlain(r) {
|
|
259
|
+
const reason = r.reason ? ` (${String(r.reason)})` : "";
|
|
260
|
+
return `Reopened proposal ${String(r.id)} (${String(r.ref)}) [pending]${reason}`;
|
|
261
|
+
}
|
|
262
|
+
export function formatProposalReopenBatchPlain(r) {
|
|
263
|
+
const results = Array.isArray(r.results) ? r.results : [];
|
|
264
|
+
const reason = results[0]?.reason ? ` (${String(results[0].reason)})` : "";
|
|
265
|
+
return [
|
|
266
|
+
`Reopened ${results.length} proposal(s) [pending]${reason}`,
|
|
267
|
+
...results.map((result) => ` ${String(result.id)} ${String(result.ref)}`),
|
|
268
|
+
].join("\n");
|
|
269
|
+
}
|
|
232
270
|
export function formatProposalDrainPlain(r) {
|
|
233
271
|
const applyMode = String(r.applyMode ?? "queue");
|
|
234
272
|
const promoted = Array.isArray(r.promoted) ? r.promoted : [];
|
|
@@ -257,10 +295,27 @@ export function formatProposalDrainPlain(r) {
|
|
|
257
295
|
return lines.join("\n").trimEnd();
|
|
258
296
|
}
|
|
259
297
|
export function formatProposalDiffPlain(r) {
|
|
298
|
+
const unified = typeof r.unified === "string" ? r.unified : "";
|
|
299
|
+
if (r.op === "delete") {
|
|
300
|
+
// #997: a retire proposal archives its target — it does not "update" it —
|
|
301
|
+
// so the header says so, and the pair verdict and the accept/revert note
|
|
302
|
+
// sit above the file that is leaving.
|
|
303
|
+
const retirement = r.retirement;
|
|
304
|
+
const subject = retirement
|
|
305
|
+
? `${String(retirement.retiredRef)} -> ${String(retirement.successorRef)}`
|
|
306
|
+
: String(r.ref);
|
|
307
|
+
const lines = [`# proposal ${String(r.id)} (retire: ${subject})`];
|
|
308
|
+
if (retirement)
|
|
309
|
+
lines.push(...retireVerdictLines(retirement));
|
|
310
|
+
if (typeof r.note === "string")
|
|
311
|
+
lines.push(`note: ${r.note}`);
|
|
312
|
+
if (unified)
|
|
313
|
+
lines.push(unified);
|
|
314
|
+
return lines.join("\n");
|
|
315
|
+
}
|
|
260
316
|
const header = r.isNew
|
|
261
317
|
? `# proposal ${String(r.id)} (new asset: ${String(r.ref)})`
|
|
262
318
|
: `# proposal ${String(r.id)} (update: ${String(r.ref)})`;
|
|
263
|
-
const unified = typeof r.unified === "string" ? r.unified : "";
|
|
264
319
|
if (!unified)
|
|
265
320
|
return `${header}\n(no changes)`;
|
|
266
321
|
return `${header}\n${unified}`;
|