@primitive.ai/prim 0.1.0-alpha.66 → 0.1.0-alpha.68
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/dist/chunk-I5BHJQH2.js +329 -0
- package/dist/chunk-K3AYW6VZ.js +300 -0
- package/dist/{chunk-CPX3BUOC.js → chunk-RTLFE6EZ.js} +6 -2
- package/dist/{chunk-KHZNYEUV.js → chunk-U6L2RU67.js} +1 -1
- package/dist/{chunk-HIMWM526.js → chunk-UN5O5SYG.js} +1 -1
- package/dist/chunk-XGADQPCI.js +26 -0
- package/dist/{chunk-DR66XM5L.js → chunk-XPVNH3J6.js} +1 -1
- package/dist/{chunk-WS3MCNFD.js → chunk-XUAP572Z.js} +1 -1
- package/dist/daemon/server.js +121 -26
- package/dist/hooks/post-commit.js +2 -2
- package/dist/hooks/post-rewrite.js +2 -2
- package/dist/hooks/post-tool-use.js +51 -21
- package/dist/hooks/pre-commit.js +2 -2
- package/dist/hooks/pre-tool-use.js +64 -25
- package/dist/hooks/prim-hook.js +67 -5
- package/dist/hooks/session-end.js +3 -3
- package/dist/hooks/session-start.js +28 -20
- package/dist/index.js +617 -107
- package/dist/statusline-main.js +16 -12
- package/package.json +1 -1
- package/dist/chunk-CZ24W5QU.js +0 -123
- package/dist/chunk-JQFN6LBO.js +0 -110
package/dist/index.js
CHANGED
|
@@ -17,25 +17,18 @@ import {
|
|
|
17
17
|
uninstallPostRewriteHookAtPath,
|
|
18
18
|
uninstallProjectPostCommitHook,
|
|
19
19
|
uninstallProjectPostRewriteHook
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-XPVNH3J6.js";
|
|
21
21
|
import {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
color,
|
|
25
|
-
colorForArea,
|
|
26
|
-
dim,
|
|
27
|
-
requireDurableIngestAcknowledgement,
|
|
28
|
-
stripAnsi,
|
|
29
|
-
stripControlChars
|
|
30
|
-
} from "./chunk-JQFN6LBO.js";
|
|
22
|
+
requireDurableIngestAcknowledgement
|
|
23
|
+
} from "./chunk-XGADQPCI.js";
|
|
31
24
|
import {
|
|
32
25
|
checkAffectedDecisions,
|
|
33
26
|
daemonOrDirectGet,
|
|
34
27
|
formatDecisionsWarning
|
|
35
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-XUAP572Z.js";
|
|
36
29
|
import {
|
|
37
30
|
fetchFeedbackCapability
|
|
38
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-U6L2RU67.js";
|
|
39
32
|
import {
|
|
40
33
|
JOURNAL_DIR,
|
|
41
34
|
SESSIONS_DIR,
|
|
@@ -44,18 +37,27 @@ import {
|
|
|
44
37
|
listFlushing,
|
|
45
38
|
pendingJournalStats,
|
|
46
39
|
readMovesFromPath
|
|
47
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-UN5O5SYG.js";
|
|
48
41
|
import {
|
|
49
42
|
inspectWorkspaceId
|
|
50
43
|
} from "./chunk-UVIWZWM6.js";
|
|
51
44
|
import {
|
|
52
45
|
binFile,
|
|
46
|
+
bold,
|
|
47
|
+
boundedHealthError,
|
|
48
|
+
color,
|
|
49
|
+
colorForArea,
|
|
53
50
|
commandMatchesBin,
|
|
54
51
|
detachedHookShimCommand,
|
|
52
|
+
dim,
|
|
53
|
+
formatStatusline,
|
|
54
|
+
formatTeammates,
|
|
55
55
|
packageVersion,
|
|
56
56
|
pinnedHookCommand,
|
|
57
|
+
stripAnsi,
|
|
58
|
+
stripControlChars,
|
|
57
59
|
warmBinCache
|
|
58
|
-
} from "./chunk-
|
|
60
|
+
} from "./chunk-K3AYW6VZ.js";
|
|
59
61
|
import {
|
|
60
62
|
canonicalGitRoot,
|
|
61
63
|
canonicalRepositoryPath,
|
|
@@ -80,7 +82,7 @@ import {
|
|
|
80
82
|
resolveAuthCredential,
|
|
81
83
|
setStoredToken,
|
|
82
84
|
withFileLock
|
|
83
|
-
} from "./chunk-
|
|
85
|
+
} from "./chunk-RTLFE6EZ.js";
|
|
84
86
|
import {
|
|
85
87
|
daemonIsLive,
|
|
86
88
|
daemonRequest
|
|
@@ -2131,53 +2133,6 @@ async function runLatestDaemonBootstrap(ensureCurrent, options = {}) {
|
|
|
2131
2133
|
}
|
|
2132
2134
|
}
|
|
2133
2135
|
|
|
2134
|
-
// src/lib/presence.ts
|
|
2135
|
-
var DECISION_ORIGIN = "https://app.getprimitive.ai";
|
|
2136
|
-
function validDecisionUrl(value) {
|
|
2137
|
-
if (typeof value !== "string" || stripControlChars(value) !== value) {
|
|
2138
|
-
return void 0;
|
|
2139
|
-
}
|
|
2140
|
-
try {
|
|
2141
|
-
const url = new URL(value);
|
|
2142
|
-
if (url.origin !== DECISION_ORIGIN || url.username !== "" || url.password !== "" || url.search !== "" || url.hash !== "" || !/^\/decisions\/[^/]+$/u.test(url.pathname)) {
|
|
2143
|
-
return void 0;
|
|
2144
|
-
}
|
|
2145
|
-
return url.href;
|
|
2146
|
-
} catch {
|
|
2147
|
-
return void 0;
|
|
2148
|
-
}
|
|
2149
|
-
}
|
|
2150
|
-
function decisionLink(label, decisionUrl) {
|
|
2151
|
-
const url = validDecisionUrl(decisionUrl);
|
|
2152
|
-
return url ? `\x1B]8;;${url}\x07\x1B[34;4m${label}\x1B[0m\x1B]8;;\x07` : label;
|
|
2153
|
-
}
|
|
2154
|
-
function formatLabeled(labels, cap) {
|
|
2155
|
-
if (labels === void 0) {
|
|
2156
|
-
return "\u2014";
|
|
2157
|
-
}
|
|
2158
|
-
if (labels.length === 0) {
|
|
2159
|
-
return "just you";
|
|
2160
|
-
}
|
|
2161
|
-
if (labels.length <= cap) {
|
|
2162
|
-
return labels.join(", ");
|
|
2163
|
-
}
|
|
2164
|
-
return `${labels.slice(0, cap).join(", ")} +${String(labels.length - cap)}`;
|
|
2165
|
-
}
|
|
2166
|
-
function formatTeammates(names, cap) {
|
|
2167
|
-
return formatLabeled(names?.map(stripControlChars), cap);
|
|
2168
|
-
}
|
|
2169
|
-
function formatTeammatesWithArea(teammates, cap) {
|
|
2170
|
-
return formatLabeled(
|
|
2171
|
-
teammates?.map((t) => {
|
|
2172
|
-
const name = stripControlChars(t.name);
|
|
2173
|
-
const area = stripControlChars(t.area ?? "").trim();
|
|
2174
|
-
const label = area ? `${name} - ${area}` : name;
|
|
2175
|
-
return decisionLink(label, t.decisionUrl);
|
|
2176
|
-
}),
|
|
2177
|
-
cap
|
|
2178
|
-
);
|
|
2179
|
-
}
|
|
2180
|
-
|
|
2181
2136
|
// src/commands/daemon.ts
|
|
2182
2137
|
var DAEMON_BIN = "prim-daemon-server";
|
|
2183
2138
|
var CONFIG_DIR = join5(homedir5(), ".config", "prim");
|
|
@@ -3077,7 +3032,7 @@ async function fetchRecent(args, deps = defaultDeps2) {
|
|
|
3077
3032
|
"decisions_recent",
|
|
3078
3033
|
`/api/cli/decisions/recent?${params.toString()}`,
|
|
3079
3034
|
client,
|
|
3080
|
-
RECENT_TIMEOUT_MS
|
|
3035
|
+
deps.timeoutMs ?? RECENT_TIMEOUT_MS
|
|
3081
3036
|
);
|
|
3082
3037
|
if (args.author !== void 0 && res.author === void 0 && res.unavailable === void 0) {
|
|
3083
3038
|
return {
|
|
@@ -3394,6 +3349,537 @@ function formatRelateJson(result) {
|
|
|
3394
3349
|
return JSON.stringify(result.outcome, null, 2);
|
|
3395
3350
|
}
|
|
3396
3351
|
|
|
3352
|
+
// src/decisions/repairs.ts
|
|
3353
|
+
var REPAIRS_TIMEOUT_MS = 1e4;
|
|
3354
|
+
var MAX_REPAIR_LIST_PAGES = 100;
|
|
3355
|
+
var MAX_REPAIR_DECISIONS_FOR_CONFIRM = 25;
|
|
3356
|
+
var MAX_REPAIRS_PER_PAGE = 10;
|
|
3357
|
+
var MAX_CURSOR_LENGTH = 4096;
|
|
3358
|
+
var MAX_INTENT_LENGTH = 1e3;
|
|
3359
|
+
var MAX_SIGNALS = 8;
|
|
3360
|
+
var MAX_SIGNAL_LENGTH = 160;
|
|
3361
|
+
var SHA_RE = /^[0-9a-f]{40}$/u;
|
|
3362
|
+
var SHA_INPUT_RE = /^[0-9a-f]{40}$/iu;
|
|
3363
|
+
var REVIEW_TOKEN_RE = /^[0-9a-f]{64}$/u;
|
|
3364
|
+
var REVIEW_TOKEN_INPUT_RE = /^[0-9a-f]{64}$/iu;
|
|
3365
|
+
var REPO_FULL_NAME_RE = /^[a-z0-9_.-]+\/[a-z0-9_.-]+$/u;
|
|
3366
|
+
var REVIEWED_CONFIRM_WIRE_ACTION = "confirm_reviewed_v2";
|
|
3367
|
+
var LEGACY_REPAIR_BODY_ERROR = "Body requires id, action (confirm or reject), and expectedProposedSha";
|
|
3368
|
+
var LEGACY_CONFIRM_PROTOCOL_ERROR = "Reviewed confirmation protocol required; legacy action confirm is not supported";
|
|
3369
|
+
var defaultDeps6 = { getClient };
|
|
3370
|
+
var RepairProposalNotFoundError = class extends Error {
|
|
3371
|
+
constructor(proposalId) {
|
|
3372
|
+
super(`Commit repair proposal not found: ${oneLine(proposalId)}`);
|
|
3373
|
+
this.name = "RepairProposalNotFoundError";
|
|
3374
|
+
}
|
|
3375
|
+
};
|
|
3376
|
+
var RepairAuthorizationError = class extends Error {
|
|
3377
|
+
constructor() {
|
|
3378
|
+
super(
|
|
3379
|
+
"Not authorized to review commit repairs; active organization membership and current repository authorization are required"
|
|
3380
|
+
);
|
|
3381
|
+
this.name = "RepairAuthorizationError";
|
|
3382
|
+
}
|
|
3383
|
+
};
|
|
3384
|
+
var RepairListContractError = class extends Error {
|
|
3385
|
+
constructor(detail) {
|
|
3386
|
+
super(`Commit repair response violated the reviewed CLI contract: ${oneLine(detail)}`);
|
|
3387
|
+
this.name = "RepairListContractError";
|
|
3388
|
+
}
|
|
3389
|
+
};
|
|
3390
|
+
var RepairEndpointVersionError = class extends Error {
|
|
3391
|
+
constructor() {
|
|
3392
|
+
super(
|
|
3393
|
+
"The selected server must be upgraded for the reviewed commit-repair contract; nothing was confirmed or queued (client/server version skew)"
|
|
3394
|
+
);
|
|
3395
|
+
this.name = "RepairEndpointVersionError";
|
|
3396
|
+
}
|
|
3397
|
+
};
|
|
3398
|
+
var RepairResolutionInputError = class extends Error {
|
|
3399
|
+
constructor(detail) {
|
|
3400
|
+
super(`Invalid commit repair resolution: ${oneLine(detail)}`);
|
|
3401
|
+
this.name = "RepairResolutionInputError";
|
|
3402
|
+
}
|
|
3403
|
+
};
|
|
3404
|
+
function contractError(path, detail) {
|
|
3405
|
+
throw new RepairListContractError(`${path} ${detail}`);
|
|
3406
|
+
}
|
|
3407
|
+
function asRecord(value, path) {
|
|
3408
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
3409
|
+
contractError(path, "must be an object");
|
|
3410
|
+
}
|
|
3411
|
+
return value;
|
|
3412
|
+
}
|
|
3413
|
+
function assertKeys(record, path, required, optional = []) {
|
|
3414
|
+
const allowed = /* @__PURE__ */ new Set([...required, ...optional]);
|
|
3415
|
+
const missing = required.find((key) => !Object.hasOwn(record, key));
|
|
3416
|
+
if (missing !== void 0) contractError(path, `is missing ${missing}`);
|
|
3417
|
+
const extra = Object.keys(record).find((key) => !allowed.has(key));
|
|
3418
|
+
if (extra !== void 0) contractError(path, `contains unexpected field ${extra}`);
|
|
3419
|
+
}
|
|
3420
|
+
function asString(value, path, options) {
|
|
3421
|
+
if (typeof value !== "string") contractError(path, "must be a string");
|
|
3422
|
+
if (options.nonempty && value.length === 0) contractError(path, "must not be empty");
|
|
3423
|
+
if (value.length > options.max) contractError(path, "exceeds its length bound");
|
|
3424
|
+
return value;
|
|
3425
|
+
}
|
|
3426
|
+
function asTimestamp(value, path) {
|
|
3427
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
|
|
3428
|
+
contractError(path, "must be a non-negative safe integer timestamp");
|
|
3429
|
+
}
|
|
3430
|
+
return value;
|
|
3431
|
+
}
|
|
3432
|
+
function asCount(value, path) {
|
|
3433
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
|
|
3434
|
+
contractError(path, "must be a non-negative safe integer");
|
|
3435
|
+
}
|
|
3436
|
+
return value;
|
|
3437
|
+
}
|
|
3438
|
+
function asBoolean(value, path) {
|
|
3439
|
+
if (typeof value !== "boolean") contractError(path, "must be a boolean");
|
|
3440
|
+
return value;
|
|
3441
|
+
}
|
|
3442
|
+
function asSha(value, path) {
|
|
3443
|
+
const sha = asString(value, path, { max: 40, nonempty: true });
|
|
3444
|
+
if (!SHA_RE.test(sha)) contractError(path, "must be a canonical lowercase Git commit SHA");
|
|
3445
|
+
return sha;
|
|
3446
|
+
}
|
|
3447
|
+
function optionalString(record, key, path, max) {
|
|
3448
|
+
if (!Object.hasOwn(record, key)) return void 0;
|
|
3449
|
+
return asString(record[key], `${path}.${key}`, { max, nonempty: true });
|
|
3450
|
+
}
|
|
3451
|
+
function parseProposal(value, path) {
|
|
3452
|
+
const record = asRecord(value, path);
|
|
3453
|
+
assertKeys(
|
|
3454
|
+
record,
|
|
3455
|
+
path,
|
|
3456
|
+
["_id", "repoFullName", "deadCommitSha", "status", "signals", "lastEvaluatedAt"],
|
|
3457
|
+
["proposedSha", "proposedTier", "nextEvaluateAt"]
|
|
3458
|
+
);
|
|
3459
|
+
const repoFullName = asString(record.repoFullName, `${path}.repoFullName`, {
|
|
3460
|
+
max: 200,
|
|
3461
|
+
nonempty: true
|
|
3462
|
+
});
|
|
3463
|
+
if (!REPO_FULL_NAME_RE.test(repoFullName)) {
|
|
3464
|
+
contractError(`${path}.repoFullName`, "must be canonical owner/repository syntax");
|
|
3465
|
+
}
|
|
3466
|
+
const status = record.status;
|
|
3467
|
+
if (status !== "proposed" && status !== "confirmed" && status !== "verification_failed") {
|
|
3468
|
+
contractError(`${path}.status`, "is not a review-visible proposal status");
|
|
3469
|
+
}
|
|
3470
|
+
if (!Array.isArray(record.signals) || record.signals.length > MAX_SIGNALS) {
|
|
3471
|
+
contractError(`${path}.signals`, "must be a bounded array");
|
|
3472
|
+
}
|
|
3473
|
+
const signals = record.signals.map(
|
|
3474
|
+
(signal, index) => asString(signal, `${path}.signals[${String(index)}]`, {
|
|
3475
|
+
max: MAX_SIGNAL_LENGTH,
|
|
3476
|
+
nonempty: true
|
|
3477
|
+
})
|
|
3478
|
+
);
|
|
3479
|
+
const proposedSha = Object.hasOwn(record, "proposedSha") ? asSha(record.proposedSha, `${path}.proposedSha`) : void 0;
|
|
3480
|
+
let proposedTier;
|
|
3481
|
+
if (Object.hasOwn(record, "proposedTier")) {
|
|
3482
|
+
if (record.proposedTier !== "exact_patch_body" && record.proposedTier !== "exact_file_set") {
|
|
3483
|
+
contractError(`${path}.proposedTier`, "is not a supported match tier");
|
|
3484
|
+
}
|
|
3485
|
+
proposedTier = record.proposedTier;
|
|
3486
|
+
}
|
|
3487
|
+
const nextEvaluateAt = Object.hasOwn(record, "nextEvaluateAt") ? asTimestamp(record.nextEvaluateAt, `${path}.nextEvaluateAt`) : void 0;
|
|
3488
|
+
return {
|
|
3489
|
+
_id: asString(record._id, `${path}._id`, { max: 256, nonempty: true }),
|
|
3490
|
+
repoFullName,
|
|
3491
|
+
deadCommitSha: asSha(record.deadCommitSha, `${path}.deadCommitSha`),
|
|
3492
|
+
status,
|
|
3493
|
+
proposedSha,
|
|
3494
|
+
proposedTier,
|
|
3495
|
+
signals,
|
|
3496
|
+
lastEvaluatedAt: asTimestamp(record.lastEvaluatedAt, `${path}.lastEvaluatedAt`),
|
|
3497
|
+
nextEvaluateAt
|
|
3498
|
+
};
|
|
3499
|
+
}
|
|
3500
|
+
function parseDecision(value, path) {
|
|
3501
|
+
const record = asRecord(value, path);
|
|
3502
|
+
assertKeys(record, path, ["_id", "intent", "status", "intentTruncated"], ["shortId"]);
|
|
3503
|
+
const status = record.status;
|
|
3504
|
+
if (status !== "provisional" && status !== "abandoned") {
|
|
3505
|
+
contractError(`${path}.status`, "is not a repairable decision status");
|
|
3506
|
+
}
|
|
3507
|
+
return {
|
|
3508
|
+
_id: asString(record._id, `${path}._id`, { max: 256, nonempty: true }),
|
|
3509
|
+
shortId: optionalString(record, "shortId", path, 128),
|
|
3510
|
+
intent: asString(record.intent, `${path}.intent`, { max: MAX_INTENT_LENGTH }),
|
|
3511
|
+
status,
|
|
3512
|
+
intentTruncated: asBoolean(record.intentTruncated, `${path}.intentTruncated`)
|
|
3513
|
+
};
|
|
3514
|
+
}
|
|
3515
|
+
function parseListItem(value, path) {
|
|
3516
|
+
const record = asRecord(value, path);
|
|
3517
|
+
assertKeys(record, path, [
|
|
3518
|
+
"proposal",
|
|
3519
|
+
"decisions",
|
|
3520
|
+
"affectedDecisionCount",
|
|
3521
|
+
"decisionsTruncated",
|
|
3522
|
+
"reviewToken"
|
|
3523
|
+
]);
|
|
3524
|
+
if (!Array.isArray(record.decisions) || record.decisions.length > MAX_REPAIR_DECISIONS_FOR_CONFIRM) {
|
|
3525
|
+
contractError(`${path}.decisions`, "must be a bounded array");
|
|
3526
|
+
}
|
|
3527
|
+
const decisions = record.decisions.map(
|
|
3528
|
+
(decision, index) => parseDecision(decision, `${path}.decisions[${String(index)}]`)
|
|
3529
|
+
);
|
|
3530
|
+
const proposal = parseProposal(record.proposal, `${path}.proposal`);
|
|
3531
|
+
const decisionsTruncated = asBoolean(record.decisionsTruncated, `${path}.decisionsTruncated`);
|
|
3532
|
+
const affectedDecisionCount = record.affectedDecisionCount === null ? null : asCount(record.affectedDecisionCount, `${path}.affectedDecisionCount`);
|
|
3533
|
+
let reviewToken;
|
|
3534
|
+
if (record.reviewToken === null) {
|
|
3535
|
+
reviewToken = null;
|
|
3536
|
+
} else {
|
|
3537
|
+
reviewToken = asString(record.reviewToken, `${path}.reviewToken`, {
|
|
3538
|
+
max: 64,
|
|
3539
|
+
nonempty: true
|
|
3540
|
+
});
|
|
3541
|
+
if (!REVIEW_TOKEN_RE.test(reviewToken)) {
|
|
3542
|
+
contractError(`${path}.reviewToken`, "must be a canonical lowercase SHA-256 token");
|
|
3543
|
+
}
|
|
3544
|
+
}
|
|
3545
|
+
if (!decisionsTruncated && affectedDecisionCount === null) {
|
|
3546
|
+
contractError(path, "cannot claim a complete decision list with an unknown count");
|
|
3547
|
+
}
|
|
3548
|
+
if (!decisionsTruncated && affectedDecisionCount !== decisions.length) {
|
|
3549
|
+
contractError(path, "has a complete decision count that does not match its projection");
|
|
3550
|
+
}
|
|
3551
|
+
if (decisionsTruncated && affectedDecisionCount !== null && affectedDecisionCount <= decisions.length) {
|
|
3552
|
+
contractError(path, "claims truncation without a larger affected-decision count");
|
|
3553
|
+
}
|
|
3554
|
+
if (decisionsTruncated && reviewToken !== null) {
|
|
3555
|
+
contractError(path, "must not expose a review token for a truncated decision set");
|
|
3556
|
+
}
|
|
3557
|
+
if (reviewToken !== null && (proposal.proposedSha === void 0 || proposal.proposedTier === void 0 || proposal.proposedSha === proposal.deadCommitSha)) {
|
|
3558
|
+
contractError(path, "must not expose a review token without a valid replacement candidate");
|
|
3559
|
+
}
|
|
3560
|
+
return {
|
|
3561
|
+
proposal,
|
|
3562
|
+
decisions,
|
|
3563
|
+
affectedDecisionCount,
|
|
3564
|
+
decisionsTruncated,
|
|
3565
|
+
reviewToken
|
|
3566
|
+
};
|
|
3567
|
+
}
|
|
3568
|
+
function parseListPage(value, pageNumber) {
|
|
3569
|
+
const path = `page ${String(pageNumber)}`;
|
|
3570
|
+
const record = asRecord(value, path);
|
|
3571
|
+
assertKeys(record, path, ["repairs", "isDone", "nextCursor", "truncated"]);
|
|
3572
|
+
if (!Array.isArray(record.repairs) || record.repairs.length > MAX_REPAIRS_PER_PAGE) {
|
|
3573
|
+
contractError(`${path}.repairs`, "must be a bounded array");
|
|
3574
|
+
}
|
|
3575
|
+
const isDone = asBoolean(record.isDone, `${path}.isDone`);
|
|
3576
|
+
const truncated = asBoolean(record.truncated, `${path}.truncated`);
|
|
3577
|
+
if (truncated === isDone) contractError(path, "has inconsistent isDone/truncated flags");
|
|
3578
|
+
let nextCursor;
|
|
3579
|
+
if (record.nextCursor === null) {
|
|
3580
|
+
nextCursor = null;
|
|
3581
|
+
} else {
|
|
3582
|
+
nextCursor = asString(record.nextCursor, `${path}.nextCursor`, {
|
|
3583
|
+
max: MAX_CURSOR_LENGTH,
|
|
3584
|
+
nonempty: true
|
|
3585
|
+
});
|
|
3586
|
+
}
|
|
3587
|
+
if (isDone && nextCursor !== null) contractError(path, "is done but includes a next cursor");
|
|
3588
|
+
if (!isDone && nextCursor === null) contractError(path, "is incomplete without a next cursor");
|
|
3589
|
+
return {
|
|
3590
|
+
repairs: record.repairs.map(
|
|
3591
|
+
(item, index) => parseListItem(item, `${path}.repairs[${String(index)}]`)
|
|
3592
|
+
),
|
|
3593
|
+
isDone,
|
|
3594
|
+
nextCursor,
|
|
3595
|
+
truncated
|
|
3596
|
+
};
|
|
3597
|
+
}
|
|
3598
|
+
function effectivePageLimit(deps) {
|
|
3599
|
+
if (deps.maxPages === void 0) return MAX_REPAIR_LIST_PAGES;
|
|
3600
|
+
if (!Number.isSafeInteger(deps.maxPages) || deps.maxPages < 1) {
|
|
3601
|
+
throw new RepairListContractError("configured page limit must be a positive safe integer");
|
|
3602
|
+
}
|
|
3603
|
+
return Math.min(deps.maxPages, MAX_REPAIR_LIST_PAGES);
|
|
3604
|
+
}
|
|
3605
|
+
function listPath(cursor) {
|
|
3606
|
+
if (cursor === null) return "/api/cli/decisions/repairs";
|
|
3607
|
+
const search = new URLSearchParams({ cursor });
|
|
3608
|
+
return `/api/cli/decisions/repairs?${search.toString()}`;
|
|
3609
|
+
}
|
|
3610
|
+
async function fetchRepairsWithClient(client, deps) {
|
|
3611
|
+
const repairs = [];
|
|
3612
|
+
const seenCursors = /* @__PURE__ */ new Set();
|
|
3613
|
+
const seenProposalIds = /* @__PURE__ */ new Set();
|
|
3614
|
+
const pageLimit = effectivePageLimit(deps);
|
|
3615
|
+
let cursor = null;
|
|
3616
|
+
for (let pageNumber = 1; pageNumber <= pageLimit; pageNumber += 1) {
|
|
3617
|
+
let rawPage;
|
|
3618
|
+
try {
|
|
3619
|
+
rawPage = await client.get(listPath(cursor), {
|
|
3620
|
+
signal: AbortSignal.timeout(REPAIRS_TIMEOUT_MS)
|
|
3621
|
+
});
|
|
3622
|
+
} catch (error) {
|
|
3623
|
+
if (error instanceof HttpError && error.status === 403) {
|
|
3624
|
+
throw new RepairAuthorizationError();
|
|
3625
|
+
}
|
|
3626
|
+
if (error instanceof HttpError && error.status === 404) {
|
|
3627
|
+
throw new RepairEndpointVersionError();
|
|
3628
|
+
}
|
|
3629
|
+
throw error;
|
|
3630
|
+
}
|
|
3631
|
+
const page = parseListPage(rawPage, pageNumber);
|
|
3632
|
+
for (const item of page.repairs) {
|
|
3633
|
+
if (seenProposalIds.has(item.proposal._id)) {
|
|
3634
|
+
contractError(`page ${String(pageNumber)}`, "repeats a proposal from an earlier page");
|
|
3635
|
+
}
|
|
3636
|
+
seenProposalIds.add(item.proposal._id);
|
|
3637
|
+
repairs.push(item);
|
|
3638
|
+
}
|
|
3639
|
+
if (page.isDone) {
|
|
3640
|
+
return { repairs, isDone: true, nextCursor: null, truncated: false };
|
|
3641
|
+
}
|
|
3642
|
+
const nextCursor = page.nextCursor;
|
|
3643
|
+
if (nextCursor === null)
|
|
3644
|
+
contractError(`page ${String(pageNumber)}`, "has no continuation cursor");
|
|
3645
|
+
if (nextCursor === cursor || seenCursors.has(nextCursor)) {
|
|
3646
|
+
contractError(`page ${String(pageNumber)}`, "has a repeated or non-advancing cursor");
|
|
3647
|
+
}
|
|
3648
|
+
seenCursors.add(nextCursor);
|
|
3649
|
+
cursor = nextCursor;
|
|
3650
|
+
}
|
|
3651
|
+
throw new RepairListContractError(
|
|
3652
|
+
`pagination did not finish within ${String(pageLimit)} page(s); no partial result was emitted`
|
|
3653
|
+
);
|
|
3654
|
+
}
|
|
3655
|
+
async function fetchRepairs(deps = defaultDeps6) {
|
|
3656
|
+
return fetchRepairsWithClient(deps.getClient(), deps);
|
|
3657
|
+
}
|
|
3658
|
+
function normalizedResolutionInput(proposalId, expectedProposedSha) {
|
|
3659
|
+
if (proposalId.length === 0 || proposalId.length > 256) {
|
|
3660
|
+
throw new RepairResolutionInputError("proposal ID must be a non-empty bounded string");
|
|
3661
|
+
}
|
|
3662
|
+
if (!SHA_INPUT_RE.test(expectedProposedSha)) {
|
|
3663
|
+
throw new RepairResolutionInputError("expected proposed SHA must be a 40-character Git SHA");
|
|
3664
|
+
}
|
|
3665
|
+
return { proposalId, expectedProposedSha: expectedProposedSha.toLowerCase() };
|
|
3666
|
+
}
|
|
3667
|
+
function parseResolutionOutcome(value, action) {
|
|
3668
|
+
const record = asRecord(value, "resolution response");
|
|
3669
|
+
const allowed = action === "confirm" ? ["confirmed", "already_applied", "disabled", "no_op", "stale"] : ["rejected", "already_applied", "no_op", "stale"];
|
|
3670
|
+
if (record.status === "backoff") {
|
|
3671
|
+
assertKeys(record, "resolution response", ["status", "retryAt"]);
|
|
3672
|
+
if (action !== "confirm") {
|
|
3673
|
+
contractError("resolution response.status", "backoff is impossible for reject");
|
|
3674
|
+
}
|
|
3675
|
+
return {
|
|
3676
|
+
status: "backoff",
|
|
3677
|
+
retryAt: asTimestamp(record.retryAt, "resolution response.retryAt")
|
|
3678
|
+
};
|
|
3679
|
+
}
|
|
3680
|
+
assertKeys(record, "resolution response", ["status"]);
|
|
3681
|
+
if (typeof record.status !== "string" || !allowed.includes(record.status)) {
|
|
3682
|
+
contractError("resolution response.status", "is not a supported outcome");
|
|
3683
|
+
}
|
|
3684
|
+
return { status: record.status };
|
|
3685
|
+
}
|
|
3686
|
+
function normalizedReviewToken(value) {
|
|
3687
|
+
if (value === void 0 || !REVIEW_TOKEN_INPUT_RE.test(value)) {
|
|
3688
|
+
throw new RepairResolutionInputError(
|
|
3689
|
+
"confirm requires --review-token with the 64-character token from the reviewed list"
|
|
3690
|
+
);
|
|
3691
|
+
}
|
|
3692
|
+
return value.toLowerCase();
|
|
3693
|
+
}
|
|
3694
|
+
function parseConflictOutcome(value, action) {
|
|
3695
|
+
const record = asRecord(value, "resolution conflict response");
|
|
3696
|
+
assertKeys(record, "resolution conflict response", ["status"]);
|
|
3697
|
+
if (action !== "confirm") {
|
|
3698
|
+
contractError(
|
|
3699
|
+
"resolution conflict response.status",
|
|
3700
|
+
"a review conflict is impossible for reject"
|
|
3701
|
+
);
|
|
3702
|
+
}
|
|
3703
|
+
if (record.status !== "review_too_large" && record.status !== "stale_review") {
|
|
3704
|
+
contractError("resolution conflict response.status", "is not a supported 409 outcome");
|
|
3705
|
+
}
|
|
3706
|
+
return { status: record.status };
|
|
3707
|
+
}
|
|
3708
|
+
async function resolveRepair(proposalId, expectedProposedSha, action, expectedReviewToken, deps = defaultDeps6) {
|
|
3709
|
+
const normalized = normalizedResolutionInput(proposalId, expectedProposedSha);
|
|
3710
|
+
const reviewToken = action === "confirm" ? normalizedReviewToken(expectedReviewToken) : void 0;
|
|
3711
|
+
if (action === "reject" && expectedReviewToken !== void 0) {
|
|
3712
|
+
throw new RepairResolutionInputError("--review-token is valid only with confirm");
|
|
3713
|
+
}
|
|
3714
|
+
const client = deps.getClient();
|
|
3715
|
+
const wireAction = action === "confirm" ? REVIEWED_CONFIRM_WIRE_ACTION : "reject";
|
|
3716
|
+
try {
|
|
3717
|
+
const rawOutcome = await client.post(
|
|
3718
|
+
"/api/cli/decisions/repairs/resolve",
|
|
3719
|
+
{
|
|
3720
|
+
id: normalized.proposalId,
|
|
3721
|
+
action: wireAction,
|
|
3722
|
+
expectedProposedSha: normalized.expectedProposedSha,
|
|
3723
|
+
...reviewToken === void 0 ? {} : { expectedReviewToken: reviewToken }
|
|
3724
|
+
},
|
|
3725
|
+
{ signal: AbortSignal.timeout(REPAIRS_TIMEOUT_MS) }
|
|
3726
|
+
);
|
|
3727
|
+
return {
|
|
3728
|
+
proposalId: normalized.proposalId,
|
|
3729
|
+
action,
|
|
3730
|
+
outcome: parseResolutionOutcome(rawOutcome, action)
|
|
3731
|
+
};
|
|
3732
|
+
} catch (error) {
|
|
3733
|
+
if (error instanceof HttpError && error.status === 404 && error.message === "Repair proposal not found") {
|
|
3734
|
+
throw new RepairProposalNotFoundError(normalized.proposalId);
|
|
3735
|
+
}
|
|
3736
|
+
if (error instanceof HttpError && error.status === 404) {
|
|
3737
|
+
throw new RepairEndpointVersionError();
|
|
3738
|
+
}
|
|
3739
|
+
if (error instanceof HttpError && error.status === 403) {
|
|
3740
|
+
throw new RepairAuthorizationError();
|
|
3741
|
+
}
|
|
3742
|
+
if (action === "confirm" && error instanceof HttpError && error.status === 400 && (error.message === LEGACY_REPAIR_BODY_ERROR || error.message === LEGACY_CONFIRM_PROTOCOL_ERROR)) {
|
|
3743
|
+
throw new RepairEndpointVersionError();
|
|
3744
|
+
}
|
|
3745
|
+
if (error instanceof HttpError && error.status === 409) {
|
|
3746
|
+
return {
|
|
3747
|
+
proposalId: normalized.proposalId,
|
|
3748
|
+
action,
|
|
3749
|
+
outcome: parseConflictOutcome(error.body, action)
|
|
3750
|
+
};
|
|
3751
|
+
}
|
|
3752
|
+
throw error;
|
|
3753
|
+
}
|
|
3754
|
+
}
|
|
3755
|
+
function shortSha(sha) {
|
|
3756
|
+
return sha === void 0 ? "(missing)" : sha.slice(0, 12);
|
|
3757
|
+
}
|
|
3758
|
+
function tierLabel(tier) {
|
|
3759
|
+
if (tier === "exact_patch_body") return "exact patch body";
|
|
3760
|
+
if (tier === "exact_file_set") return "exact file set";
|
|
3761
|
+
return "match tier missing";
|
|
3762
|
+
}
|
|
3763
|
+
function oneLine(value) {
|
|
3764
|
+
return stripControlChars(value.replace(/\s+/gu, " ")).trim();
|
|
3765
|
+
}
|
|
3766
|
+
function timestampLabel(value) {
|
|
3767
|
+
const timestamp = new Date(value);
|
|
3768
|
+
return Number.isNaN(timestamp.getTime()) ? String(value) : `${timestamp.toISOString()} (${String(value)})`;
|
|
3769
|
+
}
|
|
3770
|
+
function reviewLabel(item) {
|
|
3771
|
+
if (item.affectedDecisionCount === null) {
|
|
3772
|
+
return `showing ${String(item.decisions.length)}; total unknown; confirmation disabled`;
|
|
3773
|
+
}
|
|
3774
|
+
if (item.decisionsTruncated) {
|
|
3775
|
+
return `showing ${String(item.decisions.length)} of ${String(item.affectedDecisionCount)}; confirmation disabled`;
|
|
3776
|
+
}
|
|
3777
|
+
if (item.affectedDecisionCount > MAX_REPAIR_DECISIONS_FOR_CONFIRM) {
|
|
3778
|
+
return `${String(item.affectedDecisionCount)} affected; exceeds CLI review limit ${String(MAX_REPAIR_DECISIONS_FOR_CONFIRM)}; confirmation disabled`;
|
|
3779
|
+
}
|
|
3780
|
+
if (item.affectedDecisionCount === 0) {
|
|
3781
|
+
return "0 affected; confirmation disabled";
|
|
3782
|
+
}
|
|
3783
|
+
return `${String(item.affectedDecisionCount)} affected; complete`;
|
|
3784
|
+
}
|
|
3785
|
+
function canRenderConfirm(item) {
|
|
3786
|
+
return item.proposal.proposedSha !== void 0 && !item.decisionsTruncated && item.affectedDecisionCount !== null && item.affectedDecisionCount > 0 && item.affectedDecisionCount <= MAX_REPAIR_DECISIONS_FOR_CONFIRM && item.reviewToken !== null;
|
|
3787
|
+
}
|
|
3788
|
+
function shellArg(value) {
|
|
3789
|
+
if (/^[a-zA-Z0-9_./:@%+=,-]+$/u.test(value)) return value;
|
|
3790
|
+
return `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
3791
|
+
}
|
|
3792
|
+
function proposalReviewLabel(item) {
|
|
3793
|
+
if (item.proposal.status === "confirmed") {
|
|
3794
|
+
return canRenderConfirm(item) ? "already confirmed; a guarded application retry may be requested when eligible" : "already confirmed; a guarded retry is currently unavailable";
|
|
3795
|
+
}
|
|
3796
|
+
if (item.proposal.status === "verification_failed") {
|
|
3797
|
+
return canRenderConfirm(item) ? "prior verification failed; eligible for a fresh confirmation" : "prior verification failed; confirmation is currently unavailable";
|
|
3798
|
+
}
|
|
3799
|
+
return canRenderConfirm(item) ? "awaiting operator review" : "awaiting review; confirmation is currently unavailable";
|
|
3800
|
+
}
|
|
3801
|
+
function formatRepairsHuman(result) {
|
|
3802
|
+
if (result.repairs.length === 0) {
|
|
3803
|
+
return "[prim] decision repairs \xB7 0 review-visible proposals \xB7 complete paginated scan";
|
|
3804
|
+
}
|
|
3805
|
+
const lines = [
|
|
3806
|
+
`[prim] decision repairs \xB7 ${String(result.repairs.length)} review-visible proposal(s) \xB7 complete paginated scan`
|
|
3807
|
+
];
|
|
3808
|
+
for (const item of result.repairs) {
|
|
3809
|
+
const { proposal, decisions } = item;
|
|
3810
|
+
const renderedProposalId = oneLine(proposal._id);
|
|
3811
|
+
lines.push(
|
|
3812
|
+
` ${renderedProposalId} [${proposal.status}] \xB7 ${oneLine(proposal.repoFullName)} \xB7 ${shortSha(proposal.deadCommitSha)} -> ${shortSha(proposal.proposedSha)} \xB7 ${tierLabel(proposal.proposedTier)}`,
|
|
3813
|
+
` review state: ${proposalReviewLabel(item)}`
|
|
3814
|
+
);
|
|
3815
|
+
if (proposal.signals.length > 0) {
|
|
3816
|
+
lines.push(` signals: ${proposal.signals.map(oneLine).join("; ")}`);
|
|
3817
|
+
}
|
|
3818
|
+
if (proposal.nextEvaluateAt !== void 0) {
|
|
3819
|
+
lines.push(` next proposal evaluation: ${timestampLabel(proposal.nextEvaluateAt)}`);
|
|
3820
|
+
}
|
|
3821
|
+
lines.push(` decisions (${reviewLabel(item)}):`);
|
|
3822
|
+
for (const decision of decisions) {
|
|
3823
|
+
const id = renderIdentifier({
|
|
3824
|
+
shortId: decision.shortId === void 0 ? void 0 : oneLine(decision.shortId),
|
|
3825
|
+
id: oneLine(decision._id)
|
|
3826
|
+
});
|
|
3827
|
+
const truncation = decision.intentTruncated ? " [intent truncated]" : "";
|
|
3828
|
+
lines.push(` - ${id} [${decision.status}] ${oneLine(decision.intent)}${truncation}`);
|
|
3829
|
+
}
|
|
3830
|
+
if (canRenderConfirm(item) && proposal.status === "proposed") {
|
|
3831
|
+
lines.push(
|
|
3832
|
+
` confirm: prim decisions repairs confirm ${shellArg(renderedProposalId)} ${proposal.proposedSha} --review-token ${item.reviewToken}`
|
|
3833
|
+
);
|
|
3834
|
+
}
|
|
3835
|
+
if (canRenderConfirm(item) && proposal.status === "verification_failed") {
|
|
3836
|
+
lines.push(
|
|
3837
|
+
` retry verification: prim decisions repairs confirm ${shellArg(renderedProposalId)} ${proposal.proposedSha} --review-token ${item.reviewToken}`
|
|
3838
|
+
);
|
|
3839
|
+
}
|
|
3840
|
+
if (canRenderConfirm(item) && proposal.status === "confirmed") {
|
|
3841
|
+
lines.push(
|
|
3842
|
+
` guarded retry confirmation: prim decisions repairs confirm ${shellArg(renderedProposalId)} ${proposal.proposedSha} --review-token ${item.reviewToken}`
|
|
3843
|
+
);
|
|
3844
|
+
}
|
|
3845
|
+
if (proposal.proposedSha !== void 0) {
|
|
3846
|
+
lines.push(
|
|
3847
|
+
` reject: prim decisions repairs reject ${shellArg(renderedProposalId)} ${proposal.proposedSha}`
|
|
3848
|
+
);
|
|
3849
|
+
}
|
|
3850
|
+
}
|
|
3851
|
+
return lines.join("\n");
|
|
3852
|
+
}
|
|
3853
|
+
function formatRepairsJson(result) {
|
|
3854
|
+
return JSON.stringify(result, null, 2);
|
|
3855
|
+
}
|
|
3856
|
+
function formatRepairResolutionHuman(result) {
|
|
3857
|
+
const proposalId = oneLine(result.proposalId);
|
|
3858
|
+
switch (result.outcome.status) {
|
|
3859
|
+
case "confirmed":
|
|
3860
|
+
return `[prim] repair ${proposalId} confirmed; fresh landing verification queued. No Decision was changed by this response.`;
|
|
3861
|
+
case "rejected":
|
|
3862
|
+
return `[prim] repair ${proposalId} rejected.`;
|
|
3863
|
+
case "already_applied":
|
|
3864
|
+
return `[prim] repair ${proposalId} was already applied; nothing to change.`;
|
|
3865
|
+
case "backoff":
|
|
3866
|
+
return `[prim] repair ${proposalId} is in application retry backoff; nothing changed. Retry at ${timestampLabel(result.outcome.retryAt)}.`;
|
|
3867
|
+
case "disabled":
|
|
3868
|
+
return `[prim] repair ${proposalId} was not confirmed because repair application is disabled; nothing changed.`;
|
|
3869
|
+
case "review_too_large":
|
|
3870
|
+
return `[prim] repair ${proposalId} was refused because its complete review exceeds the safe bound; nothing changed.`;
|
|
3871
|
+
case "stale_review":
|
|
3872
|
+
return `[prim] repair ${proposalId} changed after review; re-list and review the current decision set before confirming with its new token. Nothing changed.`;
|
|
3873
|
+
case "stale":
|
|
3874
|
+
return `[prim] repair ${proposalId} has a different proposed SHA; refresh the repair list before acting. Nothing changed.`;
|
|
3875
|
+
case "no_op":
|
|
3876
|
+
return `[prim] repair ${proposalId} cannot be ${result.action === "confirm" ? "confirmed" : "rejected"} in its current state; nothing changed.`;
|
|
3877
|
+
}
|
|
3878
|
+
}
|
|
3879
|
+
function formatRepairResolutionJson(result) {
|
|
3880
|
+
return JSON.stringify(result.outcome, null, 2);
|
|
3881
|
+
}
|
|
3882
|
+
|
|
3397
3883
|
// src/decisions/show.ts
|
|
3398
3884
|
var NOT_FOUND_RE4 = /not found/i;
|
|
3399
3885
|
function colorStatus(status) {
|
|
@@ -3406,14 +3892,14 @@ function colorStatus(status) {
|
|
|
3406
3892
|
return color(status, "gray");
|
|
3407
3893
|
}
|
|
3408
3894
|
var SHOW_TIMEOUT_MS = 1e4;
|
|
3409
|
-
var
|
|
3895
|
+
var defaultDeps7 = { getClient };
|
|
3410
3896
|
var DecisionNotFoundError = class extends Error {
|
|
3411
3897
|
constructor(idOrShortId) {
|
|
3412
3898
|
super(`Decision not found: ${idOrShortId}`);
|
|
3413
3899
|
this.name = "DecisionNotFoundError";
|
|
3414
3900
|
}
|
|
3415
3901
|
};
|
|
3416
|
-
async function fetchShow(idOrShortId, deps =
|
|
3902
|
+
async function fetchShow(idOrShortId, deps = defaultDeps7) {
|
|
3417
3903
|
const params = new URLSearchParams({ id: idOrShortId });
|
|
3418
3904
|
const client = deps.getClient();
|
|
3419
3905
|
try {
|
|
@@ -3547,7 +4033,33 @@ async function askConfirmation(question, output = process.stdout) {
|
|
|
3547
4033
|
|
|
3548
4034
|
// src/commands/decisions.ts
|
|
3549
4035
|
var EXIT_NOT_FOUND = 4;
|
|
4036
|
+
var EXIT_FAILURE = 1;
|
|
3550
4037
|
var EXIT_USAGE = 2;
|
|
4038
|
+
function handleRepairCommandError(error) {
|
|
4039
|
+
if (error instanceof RepairProposalNotFoundError) {
|
|
4040
|
+
console.error(`[prim] ${error.message}`);
|
|
4041
|
+
process.exitCode = EXIT_NOT_FOUND;
|
|
4042
|
+
return true;
|
|
4043
|
+
}
|
|
4044
|
+
if (error instanceof RepairResolutionInputError) {
|
|
4045
|
+
console.error(`[prim] ${error.message}`);
|
|
4046
|
+
process.exitCode = EXIT_USAGE;
|
|
4047
|
+
return true;
|
|
4048
|
+
}
|
|
4049
|
+
if (error instanceof RepairAuthorizationError || error instanceof HttpError && error.status === 403) {
|
|
4050
|
+
console.error(
|
|
4051
|
+
`[prim] ${error instanceof RepairAuthorizationError ? error.message : "Not authorized to review commit repairs; active organization membership and current repository authorization are required"}`
|
|
4052
|
+
);
|
|
4053
|
+
process.exitCode = EXIT_FAILURE;
|
|
4054
|
+
return true;
|
|
4055
|
+
}
|
|
4056
|
+
if (error instanceof RepairEndpointVersionError || error instanceof RepairListContractError) {
|
|
4057
|
+
console.error(`[prim] ${error.message}`);
|
|
4058
|
+
process.exitCode = EXIT_FAILURE;
|
|
4059
|
+
return true;
|
|
4060
|
+
}
|
|
4061
|
+
return false;
|
|
4062
|
+
}
|
|
3551
4063
|
var CREATE_INACTIVE_PROMPT = "[prim] Decision ingestion is disabled here. Create this one Decision without enabling passive ingestion?";
|
|
3552
4064
|
var CREATE_INACTIVE_APPROVED = "[prim] one-time Decision creation approved; passive ingestion remains disabled here";
|
|
3553
4065
|
var CREATE_INACTIVE_REJECTED = "[prim] decision not created: Decision ingestion is disabled here; rerun with prim's --yes to approve this one Decision, or run `prim enable` in a Git project";
|
|
@@ -3626,6 +4138,46 @@ function registerDecisionsCommands(program2) {
|
|
|
3626
4138
|
throw err;
|
|
3627
4139
|
}
|
|
3628
4140
|
});
|
|
4141
|
+
const repairs = decisions.command("repairs").description("Review human-gated commit rewrite repair proposals").action(runRepairsList);
|
|
4142
|
+
async function runRepairsList() {
|
|
4143
|
+
try {
|
|
4144
|
+
const result = await fetchRepairs();
|
|
4145
|
+
console.error(formatRepairsHuman(result));
|
|
4146
|
+
console.log(formatRepairsJson(result));
|
|
4147
|
+
} catch (error) {
|
|
4148
|
+
if (handleRepairCommandError(error)) return;
|
|
4149
|
+
throw error;
|
|
4150
|
+
}
|
|
4151
|
+
}
|
|
4152
|
+
repairs.command("list").description("List review-visible commit rewrite repair proposals").action(runRepairsList);
|
|
4153
|
+
const registerRepairResolution = (action) => {
|
|
4154
|
+
const resolution = repairs.command(`${action} <proposalId> <proposedSha>`).description(
|
|
4155
|
+
action === "confirm" ? "Confirm the exact reviewed decision set and queue fresh landing verification" : "Reject a proposal and remember its proposed SHA"
|
|
4156
|
+
);
|
|
4157
|
+
if (action === "confirm") {
|
|
4158
|
+
resolution.option(
|
|
4159
|
+
"--review-token <token>",
|
|
4160
|
+
"64-character token printed with the complete reviewed decision set (required)"
|
|
4161
|
+
);
|
|
4162
|
+
}
|
|
4163
|
+
resolution.action(
|
|
4164
|
+
async (proposalId, proposedSha, options) => {
|
|
4165
|
+
try {
|
|
4166
|
+
const result = await resolveRepair(proposalId, proposedSha, action, options.reviewToken);
|
|
4167
|
+
console.error(formatRepairResolutionHuman(result));
|
|
4168
|
+
console.log(formatRepairResolutionJson(result));
|
|
4169
|
+
if (result.outcome.status === "review_too_large" || result.outcome.status === "stale_review") {
|
|
4170
|
+
process.exitCode = EXIT_USAGE;
|
|
4171
|
+
}
|
|
4172
|
+
} catch (error) {
|
|
4173
|
+
if (handleRepairCommandError(error)) return;
|
|
4174
|
+
throw error;
|
|
4175
|
+
}
|
|
4176
|
+
}
|
|
4177
|
+
);
|
|
4178
|
+
};
|
|
4179
|
+
registerRepairResolution("confirm");
|
|
4180
|
+
registerRepairResolution("reject");
|
|
3629
4181
|
decisions.command("create").description("Record a decision directly with its explicit user or agent origin").requiredOption("--intent <text>", "What was decided (required)").addOption(
|
|
3630
4182
|
new Option2(
|
|
3631
4183
|
"--attribution <origin>",
|
|
@@ -5647,48 +6199,6 @@ function registerSetupCommand(program2, dependencies = {}) {
|
|
|
5647
6199
|
import { readFileSync as readFileSync9 } from "fs";
|
|
5648
6200
|
import { dirname as dirname6, resolve as resolve3 } from "path";
|
|
5649
6201
|
import { fileURLToPath } from "url";
|
|
5650
|
-
|
|
5651
|
-
// src/lib/statusline-render.ts
|
|
5652
|
-
var STATUSLINE_NAME_CAP = 3;
|
|
5653
|
-
function formatStatusline(version, snapshot, resolveIngestionStatus) {
|
|
5654
|
-
if (!snapshot) {
|
|
5655
|
-
return `primitive ${version} (daemon: down)`;
|
|
5656
|
-
}
|
|
5657
|
-
if (snapshot.healthy === false) {
|
|
5658
|
-
if (snapshot.needsReauth) {
|
|
5659
|
-
return `primitive ${version} (daemon: paused \xB7 run \`prim auth login\`)`;
|
|
5660
|
-
}
|
|
5661
|
-
if (snapshot.ingestion?.healthy === false) {
|
|
5662
|
-
const pending = snapshot.ingestion.pendingCount;
|
|
5663
|
-
const qualifier = snapshot.ingestion.pendingSampled ? "at least " : "";
|
|
5664
|
-
return `primitive ${version} (daemon: degraded \xB7 delivery: stalled${typeof pending === "number" ? ` \xB7 ${qualifier}${String(pending)} pending` : ""})`;
|
|
5665
|
-
}
|
|
5666
|
-
if (snapshot.heartbeat?.healthy === false) {
|
|
5667
|
-
return `primitive ${version} (daemon: degraded \xB7 presence: unavailable)`;
|
|
5668
|
-
}
|
|
5669
|
-
return `primitive ${version} (daemon: starting)`;
|
|
5670
|
-
}
|
|
5671
|
-
const ingestionStatus = resolveIngestionStatus();
|
|
5672
|
-
if (snapshot.envMismatch) {
|
|
5673
|
-
return `primitive ${version} (daemon: live, Decision ingestion ${ingestionStatus} \xB7 presence: other env)`;
|
|
5674
|
-
}
|
|
5675
|
-
if (snapshot.presenceStale) {
|
|
5676
|
-
return `primitive ${version} (daemon: live, Decision ingestion ${ingestionStatus} \xB7 presence: stale)`;
|
|
5677
|
-
}
|
|
5678
|
-
let team;
|
|
5679
|
-
if (snapshot.onlineTeammates !== void 0) {
|
|
5680
|
-
team = `team: ${formatTeammatesWithArea(snapshot.onlineTeammates, STATUSLINE_NAME_CAP)}`;
|
|
5681
|
-
} else if (snapshot.onlineNames !== void 0) {
|
|
5682
|
-
team = `team: ${formatTeammates(snapshot.onlineNames, STATUSLINE_NAME_CAP)}`;
|
|
5683
|
-
} else if (typeof snapshot.onlineCount === "number") {
|
|
5684
|
-
team = `team: ${String(snapshot.onlineCount)} online`;
|
|
5685
|
-
} else {
|
|
5686
|
-
team = "team: \u2014";
|
|
5687
|
-
}
|
|
5688
|
-
return `primitive ${version} (daemon: live, Decision ingestion ${ingestionStatus} \xB7 ${team})`;
|
|
5689
|
-
}
|
|
5690
|
-
|
|
5691
|
-
// src/commands/statusline.ts
|
|
5692
6202
|
var STATUSLINE_TIMEOUT_MS = 200;
|
|
5693
6203
|
function readPackageVersion() {
|
|
5694
6204
|
try {
|