@primitive.ai/prim 0.1.0-alpha.66 → 0.1.0-alpha.67
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-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-DR66XM5L.js → chunk-XPVNH3J6.js} +1 -1
- package/dist/{chunk-WS3MCNFD.js → chunk-XUAP572Z.js} +1 -1
- package/dist/daemon/server.js +6 -2
- package/dist/hooks/post-commit.js +2 -2
- package/dist/hooks/post-rewrite.js +2 -2
- package/dist/hooks/post-tool-use.js +2 -2
- package/dist/hooks/pre-commit.js +2 -2
- package/dist/hooks/pre-tool-use.js +1 -1
- package/dist/hooks/prim-hook.js +3 -3
- package/dist/hooks/session-start.js +3 -3
- package/dist/index.js +604 -7
- package/package.json +1 -1
|
@@ -422,10 +422,12 @@ function refreshToken(options = {}) {
|
|
|
422
422
|
}
|
|
423
423
|
var HttpError = class extends Error {
|
|
424
424
|
status;
|
|
425
|
-
|
|
425
|
+
body;
|
|
426
|
+
constructor(status, message, body) {
|
|
426
427
|
super(message);
|
|
427
428
|
this.name = "HttpError";
|
|
428
429
|
this.status = status;
|
|
430
|
+
this.body = body;
|
|
429
431
|
}
|
|
430
432
|
};
|
|
431
433
|
var _cachedCredential;
|
|
@@ -486,7 +488,9 @@ async function request(method, path, body, options) {
|
|
|
486
488
|
throw new HttpError(401, AUTH_EXPIRED_MESSAGE);
|
|
487
489
|
}
|
|
488
490
|
const errorBody = await response.json().catch(() => null);
|
|
489
|
-
|
|
491
|
+
const errorRecord = typeof errorBody === "object" && errorBody !== null && !Array.isArray(errorBody) ? errorBody : void 0;
|
|
492
|
+
const message = typeof errorRecord?.error === "string" ? errorRecord.error : `HTTP ${response.status}`;
|
|
493
|
+
throw new HttpError(response.status, message, errorBody);
|
|
490
494
|
}
|
|
491
495
|
return response.json();
|
|
492
496
|
}
|
package/dist/daemon/server.js
CHANGED
|
@@ -394,10 +394,12 @@ function refreshToken(options = {}) {
|
|
|
394
394
|
}
|
|
395
395
|
var HttpError = class extends Error {
|
|
396
396
|
status;
|
|
397
|
-
|
|
397
|
+
body;
|
|
398
|
+
constructor(status, message, body) {
|
|
398
399
|
super(message);
|
|
399
400
|
this.name = "HttpError";
|
|
400
401
|
this.status = status;
|
|
402
|
+
this.body = body;
|
|
401
403
|
}
|
|
402
404
|
};
|
|
403
405
|
var _cachedCredential;
|
|
@@ -458,7 +460,9 @@ async function request(method, path, body, options) {
|
|
|
458
460
|
throw new HttpError(401, AUTH_EXPIRED_MESSAGE);
|
|
459
461
|
}
|
|
460
462
|
const errorBody = await response.json().catch(() => null);
|
|
461
|
-
|
|
463
|
+
const errorRecord = typeof errorBody === "object" && errorBody !== null && !Array.isArray(errorBody) ? errorBody : void 0;
|
|
464
|
+
const message = typeof errorRecord?.error === "string" ? errorRecord.error : `HTTP ${response.status}`;
|
|
465
|
+
throw new HttpError(response.status, message, errorBody);
|
|
462
466
|
}
|
|
463
467
|
return response.json();
|
|
464
468
|
}
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
import {
|
|
7
7
|
appendMove,
|
|
8
8
|
resolveOrg
|
|
9
|
-
} from "../chunk-
|
|
9
|
+
} from "../chunk-UN5O5SYG.js";
|
|
10
10
|
import {
|
|
11
11
|
getOrCreateWorkspaceId
|
|
12
12
|
} from "../chunk-UVIWZWM6.js";
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
repoSyncId,
|
|
17
17
|
resolveRepositoryContext
|
|
18
18
|
} from "../chunk-TDRXHBEX.js";
|
|
19
|
-
import "../chunk-
|
|
19
|
+
import "../chunk-RTLFE6EZ.js";
|
|
20
20
|
|
|
21
21
|
// src/hooks/post-commit.ts
|
|
22
22
|
import { execFileSync, spawn } from "child_process";
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
import {
|
|
7
7
|
appendMove,
|
|
8
8
|
resolveOrg
|
|
9
|
-
} from "../chunk-
|
|
9
|
+
} from "../chunk-UN5O5SYG.js";
|
|
10
10
|
import {
|
|
11
11
|
getOrCreateWorkspaceId
|
|
12
12
|
} from "../chunk-UVIWZWM6.js";
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
repoSyncId,
|
|
17
17
|
resolveRepositoryContext
|
|
18
18
|
} from "../chunk-TDRXHBEX.js";
|
|
19
|
-
import "../chunk-
|
|
19
|
+
import "../chunk-RTLFE6EZ.js";
|
|
20
20
|
|
|
21
21
|
// src/hooks/post-rewrite.ts
|
|
22
22
|
import { execFileSync, spawn } from "child_process";
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
import {
|
|
17
17
|
appendMove,
|
|
18
18
|
resolveOrg
|
|
19
|
-
} from "../chunk-
|
|
19
|
+
} from "../chunk-UN5O5SYG.js";
|
|
20
20
|
import {
|
|
21
21
|
getOrCreateWorkspaceId
|
|
22
22
|
} from "../chunk-UVIWZWM6.js";
|
|
@@ -33,7 +33,7 @@ import {
|
|
|
33
33
|
} from "../chunk-TDRXHBEX.js";
|
|
34
34
|
import {
|
|
35
35
|
getClient
|
|
36
|
-
} from "../chunk-
|
|
36
|
+
} from "../chunk-RTLFE6EZ.js";
|
|
37
37
|
import {
|
|
38
38
|
normalizeEnvelope,
|
|
39
39
|
parseAgent
|
package/dist/hooks/pre-commit.js
CHANGED
package/dist/hooks/prim-hook.js
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
acknowledgeDecisionFeedback,
|
|
9
9
|
leaseDecisionFeedback,
|
|
10
10
|
renderFeedback
|
|
11
|
-
} from "../chunk-
|
|
11
|
+
} from "../chunk-U6L2RU67.js";
|
|
12
12
|
import {
|
|
13
13
|
enrichHookPayloadWithFileRefs,
|
|
14
14
|
preserveHookFileMetadata
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
import {
|
|
22
22
|
appendMove,
|
|
23
23
|
resolveOrg
|
|
24
|
-
} from "../chunk-
|
|
24
|
+
} from "../chunk-UN5O5SYG.js";
|
|
25
25
|
import {
|
|
26
26
|
getOrCreateWorkspaceId
|
|
27
27
|
} from "../chunk-UVIWZWM6.js";
|
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
repoSyncId,
|
|
37
37
|
resolveRepositoryContext
|
|
38
38
|
} from "../chunk-TDRXHBEX.js";
|
|
39
|
-
import "../chunk-
|
|
39
|
+
import "../chunk-RTLFE6EZ.js";
|
|
40
40
|
import {
|
|
41
41
|
normalizeEnvelope,
|
|
42
42
|
parseAgent
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
ensureEffectivePostRewriteHook,
|
|
6
6
|
hasUsableCodexGuidance,
|
|
7
7
|
refreshClaudePlugins
|
|
8
|
-
} from "../chunk-
|
|
8
|
+
} from "../chunk-XPVNH3J6.js";
|
|
9
9
|
import {
|
|
10
10
|
buildHookOutput,
|
|
11
11
|
handoffHookOutput
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
acknowledgeDecisionFeedback,
|
|
16
16
|
leaseDecisionFeedback,
|
|
17
17
|
renderFeedback
|
|
18
|
-
} from "../chunk-
|
|
18
|
+
} from "../chunk-U6L2RU67.js";
|
|
19
19
|
import {
|
|
20
20
|
getOrCreateWorkspaceId
|
|
21
21
|
} from "../chunk-UVIWZWM6.js";
|
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
import {
|
|
33
33
|
getSiteUrl,
|
|
34
34
|
isSessionEnded
|
|
35
|
-
} from "../chunk-
|
|
35
|
+
} from "../chunk-RTLFE6EZ.js";
|
|
36
36
|
import {
|
|
37
37
|
daemonRequest
|
|
38
38
|
} from "../chunk-UTKQTZHL.js";
|
package/dist/index.js
CHANGED
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
uninstallPostRewriteHookAtPath,
|
|
18
18
|
uninstallProjectPostCommitHook,
|
|
19
19
|
uninstallProjectPostRewriteHook
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-XPVNH3J6.js";
|
|
21
21
|
import {
|
|
22
22
|
bold,
|
|
23
23
|
boundedHealthError,
|
|
@@ -32,10 +32,10 @@ import {
|
|
|
32
32
|
checkAffectedDecisions,
|
|
33
33
|
daemonOrDirectGet,
|
|
34
34
|
formatDecisionsWarning
|
|
35
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-XUAP572Z.js";
|
|
36
36
|
import {
|
|
37
37
|
fetchFeedbackCapability
|
|
38
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-U6L2RU67.js";
|
|
39
39
|
import {
|
|
40
40
|
JOURNAL_DIR,
|
|
41
41
|
SESSIONS_DIR,
|
|
@@ -44,7 +44,7 @@ import {
|
|
|
44
44
|
listFlushing,
|
|
45
45
|
pendingJournalStats,
|
|
46
46
|
readMovesFromPath
|
|
47
|
-
} from "./chunk-
|
|
47
|
+
} from "./chunk-UN5O5SYG.js";
|
|
48
48
|
import {
|
|
49
49
|
inspectWorkspaceId
|
|
50
50
|
} from "./chunk-UVIWZWM6.js";
|
|
@@ -80,7 +80,7 @@ import {
|
|
|
80
80
|
resolveAuthCredential,
|
|
81
81
|
setStoredToken,
|
|
82
82
|
withFileLock
|
|
83
|
-
} from "./chunk-
|
|
83
|
+
} from "./chunk-RTLFE6EZ.js";
|
|
84
84
|
import {
|
|
85
85
|
daemonIsLive,
|
|
86
86
|
daemonRequest
|
|
@@ -3394,6 +3394,537 @@ function formatRelateJson(result) {
|
|
|
3394
3394
|
return JSON.stringify(result.outcome, null, 2);
|
|
3395
3395
|
}
|
|
3396
3396
|
|
|
3397
|
+
// src/decisions/repairs.ts
|
|
3398
|
+
var REPAIRS_TIMEOUT_MS = 1e4;
|
|
3399
|
+
var MAX_REPAIR_LIST_PAGES = 100;
|
|
3400
|
+
var MAX_REPAIR_DECISIONS_FOR_CONFIRM = 25;
|
|
3401
|
+
var MAX_REPAIRS_PER_PAGE = 10;
|
|
3402
|
+
var MAX_CURSOR_LENGTH = 4096;
|
|
3403
|
+
var MAX_INTENT_LENGTH = 1e3;
|
|
3404
|
+
var MAX_SIGNALS = 8;
|
|
3405
|
+
var MAX_SIGNAL_LENGTH = 160;
|
|
3406
|
+
var SHA_RE = /^[0-9a-f]{40}$/u;
|
|
3407
|
+
var SHA_INPUT_RE = /^[0-9a-f]{40}$/iu;
|
|
3408
|
+
var REVIEW_TOKEN_RE = /^[0-9a-f]{64}$/u;
|
|
3409
|
+
var REVIEW_TOKEN_INPUT_RE = /^[0-9a-f]{64}$/iu;
|
|
3410
|
+
var REPO_FULL_NAME_RE = /^[a-z0-9_.-]+\/[a-z0-9_.-]+$/u;
|
|
3411
|
+
var REVIEWED_CONFIRM_WIRE_ACTION = "confirm_reviewed_v2";
|
|
3412
|
+
var LEGACY_REPAIR_BODY_ERROR = "Body requires id, action (confirm or reject), and expectedProposedSha";
|
|
3413
|
+
var LEGACY_CONFIRM_PROTOCOL_ERROR = "Reviewed confirmation protocol required; legacy action confirm is not supported";
|
|
3414
|
+
var defaultDeps6 = { getClient };
|
|
3415
|
+
var RepairProposalNotFoundError = class extends Error {
|
|
3416
|
+
constructor(proposalId) {
|
|
3417
|
+
super(`Commit repair proposal not found: ${oneLine(proposalId)}`);
|
|
3418
|
+
this.name = "RepairProposalNotFoundError";
|
|
3419
|
+
}
|
|
3420
|
+
};
|
|
3421
|
+
var RepairAuthorizationError = class extends Error {
|
|
3422
|
+
constructor() {
|
|
3423
|
+
super(
|
|
3424
|
+
"Not authorized to review commit repairs; active organization membership and current repository authorization are required"
|
|
3425
|
+
);
|
|
3426
|
+
this.name = "RepairAuthorizationError";
|
|
3427
|
+
}
|
|
3428
|
+
};
|
|
3429
|
+
var RepairListContractError = class extends Error {
|
|
3430
|
+
constructor(detail) {
|
|
3431
|
+
super(`Commit repair response violated the reviewed CLI contract: ${oneLine(detail)}`);
|
|
3432
|
+
this.name = "RepairListContractError";
|
|
3433
|
+
}
|
|
3434
|
+
};
|
|
3435
|
+
var RepairEndpointVersionError = class extends Error {
|
|
3436
|
+
constructor() {
|
|
3437
|
+
super(
|
|
3438
|
+
"The selected server must be upgraded for the reviewed commit-repair contract; nothing was confirmed or queued (client/server version skew)"
|
|
3439
|
+
);
|
|
3440
|
+
this.name = "RepairEndpointVersionError";
|
|
3441
|
+
}
|
|
3442
|
+
};
|
|
3443
|
+
var RepairResolutionInputError = class extends Error {
|
|
3444
|
+
constructor(detail) {
|
|
3445
|
+
super(`Invalid commit repair resolution: ${oneLine(detail)}`);
|
|
3446
|
+
this.name = "RepairResolutionInputError";
|
|
3447
|
+
}
|
|
3448
|
+
};
|
|
3449
|
+
function contractError(path, detail) {
|
|
3450
|
+
throw new RepairListContractError(`${path} ${detail}`);
|
|
3451
|
+
}
|
|
3452
|
+
function asRecord(value, path) {
|
|
3453
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
3454
|
+
contractError(path, "must be an object");
|
|
3455
|
+
}
|
|
3456
|
+
return value;
|
|
3457
|
+
}
|
|
3458
|
+
function assertKeys(record, path, required, optional = []) {
|
|
3459
|
+
const allowed = /* @__PURE__ */ new Set([...required, ...optional]);
|
|
3460
|
+
const missing = required.find((key) => !Object.hasOwn(record, key));
|
|
3461
|
+
if (missing !== void 0) contractError(path, `is missing ${missing}`);
|
|
3462
|
+
const extra = Object.keys(record).find((key) => !allowed.has(key));
|
|
3463
|
+
if (extra !== void 0) contractError(path, `contains unexpected field ${extra}`);
|
|
3464
|
+
}
|
|
3465
|
+
function asString(value, path, options) {
|
|
3466
|
+
if (typeof value !== "string") contractError(path, "must be a string");
|
|
3467
|
+
if (options.nonempty && value.length === 0) contractError(path, "must not be empty");
|
|
3468
|
+
if (value.length > options.max) contractError(path, "exceeds its length bound");
|
|
3469
|
+
return value;
|
|
3470
|
+
}
|
|
3471
|
+
function asTimestamp(value, path) {
|
|
3472
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
|
|
3473
|
+
contractError(path, "must be a non-negative safe integer timestamp");
|
|
3474
|
+
}
|
|
3475
|
+
return value;
|
|
3476
|
+
}
|
|
3477
|
+
function asCount(value, path) {
|
|
3478
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
|
|
3479
|
+
contractError(path, "must be a non-negative safe integer");
|
|
3480
|
+
}
|
|
3481
|
+
return value;
|
|
3482
|
+
}
|
|
3483
|
+
function asBoolean(value, path) {
|
|
3484
|
+
if (typeof value !== "boolean") contractError(path, "must be a boolean");
|
|
3485
|
+
return value;
|
|
3486
|
+
}
|
|
3487
|
+
function asSha(value, path) {
|
|
3488
|
+
const sha = asString(value, path, { max: 40, nonempty: true });
|
|
3489
|
+
if (!SHA_RE.test(sha)) contractError(path, "must be a canonical lowercase Git commit SHA");
|
|
3490
|
+
return sha;
|
|
3491
|
+
}
|
|
3492
|
+
function optionalString(record, key, path, max) {
|
|
3493
|
+
if (!Object.hasOwn(record, key)) return void 0;
|
|
3494
|
+
return asString(record[key], `${path}.${key}`, { max, nonempty: true });
|
|
3495
|
+
}
|
|
3496
|
+
function parseProposal(value, path) {
|
|
3497
|
+
const record = asRecord(value, path);
|
|
3498
|
+
assertKeys(
|
|
3499
|
+
record,
|
|
3500
|
+
path,
|
|
3501
|
+
["_id", "repoFullName", "deadCommitSha", "status", "signals", "lastEvaluatedAt"],
|
|
3502
|
+
["proposedSha", "proposedTier", "nextEvaluateAt"]
|
|
3503
|
+
);
|
|
3504
|
+
const repoFullName = asString(record.repoFullName, `${path}.repoFullName`, {
|
|
3505
|
+
max: 200,
|
|
3506
|
+
nonempty: true
|
|
3507
|
+
});
|
|
3508
|
+
if (!REPO_FULL_NAME_RE.test(repoFullName)) {
|
|
3509
|
+
contractError(`${path}.repoFullName`, "must be canonical owner/repository syntax");
|
|
3510
|
+
}
|
|
3511
|
+
const status = record.status;
|
|
3512
|
+
if (status !== "proposed" && status !== "confirmed" && status !== "verification_failed") {
|
|
3513
|
+
contractError(`${path}.status`, "is not a review-visible proposal status");
|
|
3514
|
+
}
|
|
3515
|
+
if (!Array.isArray(record.signals) || record.signals.length > MAX_SIGNALS) {
|
|
3516
|
+
contractError(`${path}.signals`, "must be a bounded array");
|
|
3517
|
+
}
|
|
3518
|
+
const signals = record.signals.map(
|
|
3519
|
+
(signal, index) => asString(signal, `${path}.signals[${String(index)}]`, {
|
|
3520
|
+
max: MAX_SIGNAL_LENGTH,
|
|
3521
|
+
nonempty: true
|
|
3522
|
+
})
|
|
3523
|
+
);
|
|
3524
|
+
const proposedSha = Object.hasOwn(record, "proposedSha") ? asSha(record.proposedSha, `${path}.proposedSha`) : void 0;
|
|
3525
|
+
let proposedTier;
|
|
3526
|
+
if (Object.hasOwn(record, "proposedTier")) {
|
|
3527
|
+
if (record.proposedTier !== "exact_patch_body" && record.proposedTier !== "exact_file_set") {
|
|
3528
|
+
contractError(`${path}.proposedTier`, "is not a supported match tier");
|
|
3529
|
+
}
|
|
3530
|
+
proposedTier = record.proposedTier;
|
|
3531
|
+
}
|
|
3532
|
+
const nextEvaluateAt = Object.hasOwn(record, "nextEvaluateAt") ? asTimestamp(record.nextEvaluateAt, `${path}.nextEvaluateAt`) : void 0;
|
|
3533
|
+
return {
|
|
3534
|
+
_id: asString(record._id, `${path}._id`, { max: 256, nonempty: true }),
|
|
3535
|
+
repoFullName,
|
|
3536
|
+
deadCommitSha: asSha(record.deadCommitSha, `${path}.deadCommitSha`),
|
|
3537
|
+
status,
|
|
3538
|
+
proposedSha,
|
|
3539
|
+
proposedTier,
|
|
3540
|
+
signals,
|
|
3541
|
+
lastEvaluatedAt: asTimestamp(record.lastEvaluatedAt, `${path}.lastEvaluatedAt`),
|
|
3542
|
+
nextEvaluateAt
|
|
3543
|
+
};
|
|
3544
|
+
}
|
|
3545
|
+
function parseDecision(value, path) {
|
|
3546
|
+
const record = asRecord(value, path);
|
|
3547
|
+
assertKeys(record, path, ["_id", "intent", "status", "intentTruncated"], ["shortId"]);
|
|
3548
|
+
const status = record.status;
|
|
3549
|
+
if (status !== "provisional" && status !== "abandoned") {
|
|
3550
|
+
contractError(`${path}.status`, "is not a repairable decision status");
|
|
3551
|
+
}
|
|
3552
|
+
return {
|
|
3553
|
+
_id: asString(record._id, `${path}._id`, { max: 256, nonempty: true }),
|
|
3554
|
+
shortId: optionalString(record, "shortId", path, 128),
|
|
3555
|
+
intent: asString(record.intent, `${path}.intent`, { max: MAX_INTENT_LENGTH }),
|
|
3556
|
+
status,
|
|
3557
|
+
intentTruncated: asBoolean(record.intentTruncated, `${path}.intentTruncated`)
|
|
3558
|
+
};
|
|
3559
|
+
}
|
|
3560
|
+
function parseListItem(value, path) {
|
|
3561
|
+
const record = asRecord(value, path);
|
|
3562
|
+
assertKeys(record, path, [
|
|
3563
|
+
"proposal",
|
|
3564
|
+
"decisions",
|
|
3565
|
+
"affectedDecisionCount",
|
|
3566
|
+
"decisionsTruncated",
|
|
3567
|
+
"reviewToken"
|
|
3568
|
+
]);
|
|
3569
|
+
if (!Array.isArray(record.decisions) || record.decisions.length > MAX_REPAIR_DECISIONS_FOR_CONFIRM) {
|
|
3570
|
+
contractError(`${path}.decisions`, "must be a bounded array");
|
|
3571
|
+
}
|
|
3572
|
+
const decisions = record.decisions.map(
|
|
3573
|
+
(decision, index) => parseDecision(decision, `${path}.decisions[${String(index)}]`)
|
|
3574
|
+
);
|
|
3575
|
+
const proposal = parseProposal(record.proposal, `${path}.proposal`);
|
|
3576
|
+
const decisionsTruncated = asBoolean(record.decisionsTruncated, `${path}.decisionsTruncated`);
|
|
3577
|
+
const affectedDecisionCount = record.affectedDecisionCount === null ? null : asCount(record.affectedDecisionCount, `${path}.affectedDecisionCount`);
|
|
3578
|
+
let reviewToken;
|
|
3579
|
+
if (record.reviewToken === null) {
|
|
3580
|
+
reviewToken = null;
|
|
3581
|
+
} else {
|
|
3582
|
+
reviewToken = asString(record.reviewToken, `${path}.reviewToken`, {
|
|
3583
|
+
max: 64,
|
|
3584
|
+
nonempty: true
|
|
3585
|
+
});
|
|
3586
|
+
if (!REVIEW_TOKEN_RE.test(reviewToken)) {
|
|
3587
|
+
contractError(`${path}.reviewToken`, "must be a canonical lowercase SHA-256 token");
|
|
3588
|
+
}
|
|
3589
|
+
}
|
|
3590
|
+
if (!decisionsTruncated && affectedDecisionCount === null) {
|
|
3591
|
+
contractError(path, "cannot claim a complete decision list with an unknown count");
|
|
3592
|
+
}
|
|
3593
|
+
if (!decisionsTruncated && affectedDecisionCount !== decisions.length) {
|
|
3594
|
+
contractError(path, "has a complete decision count that does not match its projection");
|
|
3595
|
+
}
|
|
3596
|
+
if (decisionsTruncated && affectedDecisionCount !== null && affectedDecisionCount <= decisions.length) {
|
|
3597
|
+
contractError(path, "claims truncation without a larger affected-decision count");
|
|
3598
|
+
}
|
|
3599
|
+
if (decisionsTruncated && reviewToken !== null) {
|
|
3600
|
+
contractError(path, "must not expose a review token for a truncated decision set");
|
|
3601
|
+
}
|
|
3602
|
+
if (reviewToken !== null && (proposal.proposedSha === void 0 || proposal.proposedTier === void 0 || proposal.proposedSha === proposal.deadCommitSha)) {
|
|
3603
|
+
contractError(path, "must not expose a review token without a valid replacement candidate");
|
|
3604
|
+
}
|
|
3605
|
+
return {
|
|
3606
|
+
proposal,
|
|
3607
|
+
decisions,
|
|
3608
|
+
affectedDecisionCount,
|
|
3609
|
+
decisionsTruncated,
|
|
3610
|
+
reviewToken
|
|
3611
|
+
};
|
|
3612
|
+
}
|
|
3613
|
+
function parseListPage(value, pageNumber) {
|
|
3614
|
+
const path = `page ${String(pageNumber)}`;
|
|
3615
|
+
const record = asRecord(value, path);
|
|
3616
|
+
assertKeys(record, path, ["repairs", "isDone", "nextCursor", "truncated"]);
|
|
3617
|
+
if (!Array.isArray(record.repairs) || record.repairs.length > MAX_REPAIRS_PER_PAGE) {
|
|
3618
|
+
contractError(`${path}.repairs`, "must be a bounded array");
|
|
3619
|
+
}
|
|
3620
|
+
const isDone = asBoolean(record.isDone, `${path}.isDone`);
|
|
3621
|
+
const truncated = asBoolean(record.truncated, `${path}.truncated`);
|
|
3622
|
+
if (truncated === isDone) contractError(path, "has inconsistent isDone/truncated flags");
|
|
3623
|
+
let nextCursor;
|
|
3624
|
+
if (record.nextCursor === null) {
|
|
3625
|
+
nextCursor = null;
|
|
3626
|
+
} else {
|
|
3627
|
+
nextCursor = asString(record.nextCursor, `${path}.nextCursor`, {
|
|
3628
|
+
max: MAX_CURSOR_LENGTH,
|
|
3629
|
+
nonempty: true
|
|
3630
|
+
});
|
|
3631
|
+
}
|
|
3632
|
+
if (isDone && nextCursor !== null) contractError(path, "is done but includes a next cursor");
|
|
3633
|
+
if (!isDone && nextCursor === null) contractError(path, "is incomplete without a next cursor");
|
|
3634
|
+
return {
|
|
3635
|
+
repairs: record.repairs.map(
|
|
3636
|
+
(item, index) => parseListItem(item, `${path}.repairs[${String(index)}]`)
|
|
3637
|
+
),
|
|
3638
|
+
isDone,
|
|
3639
|
+
nextCursor,
|
|
3640
|
+
truncated
|
|
3641
|
+
};
|
|
3642
|
+
}
|
|
3643
|
+
function effectivePageLimit(deps) {
|
|
3644
|
+
if (deps.maxPages === void 0) return MAX_REPAIR_LIST_PAGES;
|
|
3645
|
+
if (!Number.isSafeInteger(deps.maxPages) || deps.maxPages < 1) {
|
|
3646
|
+
throw new RepairListContractError("configured page limit must be a positive safe integer");
|
|
3647
|
+
}
|
|
3648
|
+
return Math.min(deps.maxPages, MAX_REPAIR_LIST_PAGES);
|
|
3649
|
+
}
|
|
3650
|
+
function listPath(cursor) {
|
|
3651
|
+
if (cursor === null) return "/api/cli/decisions/repairs";
|
|
3652
|
+
const search = new URLSearchParams({ cursor });
|
|
3653
|
+
return `/api/cli/decisions/repairs?${search.toString()}`;
|
|
3654
|
+
}
|
|
3655
|
+
async function fetchRepairsWithClient(client, deps) {
|
|
3656
|
+
const repairs = [];
|
|
3657
|
+
const seenCursors = /* @__PURE__ */ new Set();
|
|
3658
|
+
const seenProposalIds = /* @__PURE__ */ new Set();
|
|
3659
|
+
const pageLimit = effectivePageLimit(deps);
|
|
3660
|
+
let cursor = null;
|
|
3661
|
+
for (let pageNumber = 1; pageNumber <= pageLimit; pageNumber += 1) {
|
|
3662
|
+
let rawPage;
|
|
3663
|
+
try {
|
|
3664
|
+
rawPage = await client.get(listPath(cursor), {
|
|
3665
|
+
signal: AbortSignal.timeout(REPAIRS_TIMEOUT_MS)
|
|
3666
|
+
});
|
|
3667
|
+
} catch (error) {
|
|
3668
|
+
if (error instanceof HttpError && error.status === 403) {
|
|
3669
|
+
throw new RepairAuthorizationError();
|
|
3670
|
+
}
|
|
3671
|
+
if (error instanceof HttpError && error.status === 404) {
|
|
3672
|
+
throw new RepairEndpointVersionError();
|
|
3673
|
+
}
|
|
3674
|
+
throw error;
|
|
3675
|
+
}
|
|
3676
|
+
const page = parseListPage(rawPage, pageNumber);
|
|
3677
|
+
for (const item of page.repairs) {
|
|
3678
|
+
if (seenProposalIds.has(item.proposal._id)) {
|
|
3679
|
+
contractError(`page ${String(pageNumber)}`, "repeats a proposal from an earlier page");
|
|
3680
|
+
}
|
|
3681
|
+
seenProposalIds.add(item.proposal._id);
|
|
3682
|
+
repairs.push(item);
|
|
3683
|
+
}
|
|
3684
|
+
if (page.isDone) {
|
|
3685
|
+
return { repairs, isDone: true, nextCursor: null, truncated: false };
|
|
3686
|
+
}
|
|
3687
|
+
const nextCursor = page.nextCursor;
|
|
3688
|
+
if (nextCursor === null)
|
|
3689
|
+
contractError(`page ${String(pageNumber)}`, "has no continuation cursor");
|
|
3690
|
+
if (nextCursor === cursor || seenCursors.has(nextCursor)) {
|
|
3691
|
+
contractError(`page ${String(pageNumber)}`, "has a repeated or non-advancing cursor");
|
|
3692
|
+
}
|
|
3693
|
+
seenCursors.add(nextCursor);
|
|
3694
|
+
cursor = nextCursor;
|
|
3695
|
+
}
|
|
3696
|
+
throw new RepairListContractError(
|
|
3697
|
+
`pagination did not finish within ${String(pageLimit)} page(s); no partial result was emitted`
|
|
3698
|
+
);
|
|
3699
|
+
}
|
|
3700
|
+
async function fetchRepairs(deps = defaultDeps6) {
|
|
3701
|
+
return fetchRepairsWithClient(deps.getClient(), deps);
|
|
3702
|
+
}
|
|
3703
|
+
function normalizedResolutionInput(proposalId, expectedProposedSha) {
|
|
3704
|
+
if (proposalId.length === 0 || proposalId.length > 256) {
|
|
3705
|
+
throw new RepairResolutionInputError("proposal ID must be a non-empty bounded string");
|
|
3706
|
+
}
|
|
3707
|
+
if (!SHA_INPUT_RE.test(expectedProposedSha)) {
|
|
3708
|
+
throw new RepairResolutionInputError("expected proposed SHA must be a 40-character Git SHA");
|
|
3709
|
+
}
|
|
3710
|
+
return { proposalId, expectedProposedSha: expectedProposedSha.toLowerCase() };
|
|
3711
|
+
}
|
|
3712
|
+
function parseResolutionOutcome(value, action) {
|
|
3713
|
+
const record = asRecord(value, "resolution response");
|
|
3714
|
+
const allowed = action === "confirm" ? ["confirmed", "already_applied", "disabled", "no_op", "stale"] : ["rejected", "already_applied", "no_op", "stale"];
|
|
3715
|
+
if (record.status === "backoff") {
|
|
3716
|
+
assertKeys(record, "resolution response", ["status", "retryAt"]);
|
|
3717
|
+
if (action !== "confirm") {
|
|
3718
|
+
contractError("resolution response.status", "backoff is impossible for reject");
|
|
3719
|
+
}
|
|
3720
|
+
return {
|
|
3721
|
+
status: "backoff",
|
|
3722
|
+
retryAt: asTimestamp(record.retryAt, "resolution response.retryAt")
|
|
3723
|
+
};
|
|
3724
|
+
}
|
|
3725
|
+
assertKeys(record, "resolution response", ["status"]);
|
|
3726
|
+
if (typeof record.status !== "string" || !allowed.includes(record.status)) {
|
|
3727
|
+
contractError("resolution response.status", "is not a supported outcome");
|
|
3728
|
+
}
|
|
3729
|
+
return { status: record.status };
|
|
3730
|
+
}
|
|
3731
|
+
function normalizedReviewToken(value) {
|
|
3732
|
+
if (value === void 0 || !REVIEW_TOKEN_INPUT_RE.test(value)) {
|
|
3733
|
+
throw new RepairResolutionInputError(
|
|
3734
|
+
"confirm requires --review-token with the 64-character token from the reviewed list"
|
|
3735
|
+
);
|
|
3736
|
+
}
|
|
3737
|
+
return value.toLowerCase();
|
|
3738
|
+
}
|
|
3739
|
+
function parseConflictOutcome(value, action) {
|
|
3740
|
+
const record = asRecord(value, "resolution conflict response");
|
|
3741
|
+
assertKeys(record, "resolution conflict response", ["status"]);
|
|
3742
|
+
if (action !== "confirm") {
|
|
3743
|
+
contractError(
|
|
3744
|
+
"resolution conflict response.status",
|
|
3745
|
+
"a review conflict is impossible for reject"
|
|
3746
|
+
);
|
|
3747
|
+
}
|
|
3748
|
+
if (record.status !== "review_too_large" && record.status !== "stale_review") {
|
|
3749
|
+
contractError("resolution conflict response.status", "is not a supported 409 outcome");
|
|
3750
|
+
}
|
|
3751
|
+
return { status: record.status };
|
|
3752
|
+
}
|
|
3753
|
+
async function resolveRepair(proposalId, expectedProposedSha, action, expectedReviewToken, deps = defaultDeps6) {
|
|
3754
|
+
const normalized = normalizedResolutionInput(proposalId, expectedProposedSha);
|
|
3755
|
+
const reviewToken = action === "confirm" ? normalizedReviewToken(expectedReviewToken) : void 0;
|
|
3756
|
+
if (action === "reject" && expectedReviewToken !== void 0) {
|
|
3757
|
+
throw new RepairResolutionInputError("--review-token is valid only with confirm");
|
|
3758
|
+
}
|
|
3759
|
+
const client = deps.getClient();
|
|
3760
|
+
const wireAction = action === "confirm" ? REVIEWED_CONFIRM_WIRE_ACTION : "reject";
|
|
3761
|
+
try {
|
|
3762
|
+
const rawOutcome = await client.post(
|
|
3763
|
+
"/api/cli/decisions/repairs/resolve",
|
|
3764
|
+
{
|
|
3765
|
+
id: normalized.proposalId,
|
|
3766
|
+
action: wireAction,
|
|
3767
|
+
expectedProposedSha: normalized.expectedProposedSha,
|
|
3768
|
+
...reviewToken === void 0 ? {} : { expectedReviewToken: reviewToken }
|
|
3769
|
+
},
|
|
3770
|
+
{ signal: AbortSignal.timeout(REPAIRS_TIMEOUT_MS) }
|
|
3771
|
+
);
|
|
3772
|
+
return {
|
|
3773
|
+
proposalId: normalized.proposalId,
|
|
3774
|
+
action,
|
|
3775
|
+
outcome: parseResolutionOutcome(rawOutcome, action)
|
|
3776
|
+
};
|
|
3777
|
+
} catch (error) {
|
|
3778
|
+
if (error instanceof HttpError && error.status === 404 && error.message === "Repair proposal not found") {
|
|
3779
|
+
throw new RepairProposalNotFoundError(normalized.proposalId);
|
|
3780
|
+
}
|
|
3781
|
+
if (error instanceof HttpError && error.status === 404) {
|
|
3782
|
+
throw new RepairEndpointVersionError();
|
|
3783
|
+
}
|
|
3784
|
+
if (error instanceof HttpError && error.status === 403) {
|
|
3785
|
+
throw new RepairAuthorizationError();
|
|
3786
|
+
}
|
|
3787
|
+
if (action === "confirm" && error instanceof HttpError && error.status === 400 && (error.message === LEGACY_REPAIR_BODY_ERROR || error.message === LEGACY_CONFIRM_PROTOCOL_ERROR)) {
|
|
3788
|
+
throw new RepairEndpointVersionError();
|
|
3789
|
+
}
|
|
3790
|
+
if (error instanceof HttpError && error.status === 409) {
|
|
3791
|
+
return {
|
|
3792
|
+
proposalId: normalized.proposalId,
|
|
3793
|
+
action,
|
|
3794
|
+
outcome: parseConflictOutcome(error.body, action)
|
|
3795
|
+
};
|
|
3796
|
+
}
|
|
3797
|
+
throw error;
|
|
3798
|
+
}
|
|
3799
|
+
}
|
|
3800
|
+
function shortSha(sha) {
|
|
3801
|
+
return sha === void 0 ? "(missing)" : sha.slice(0, 12);
|
|
3802
|
+
}
|
|
3803
|
+
function tierLabel(tier) {
|
|
3804
|
+
if (tier === "exact_patch_body") return "exact patch body";
|
|
3805
|
+
if (tier === "exact_file_set") return "exact file set";
|
|
3806
|
+
return "match tier missing";
|
|
3807
|
+
}
|
|
3808
|
+
function oneLine(value) {
|
|
3809
|
+
return stripControlChars(value.replace(/\s+/gu, " ")).trim();
|
|
3810
|
+
}
|
|
3811
|
+
function timestampLabel(value) {
|
|
3812
|
+
const timestamp = new Date(value);
|
|
3813
|
+
return Number.isNaN(timestamp.getTime()) ? String(value) : `${timestamp.toISOString()} (${String(value)})`;
|
|
3814
|
+
}
|
|
3815
|
+
function reviewLabel(item) {
|
|
3816
|
+
if (item.affectedDecisionCount === null) {
|
|
3817
|
+
return `showing ${String(item.decisions.length)}; total unknown; confirmation disabled`;
|
|
3818
|
+
}
|
|
3819
|
+
if (item.decisionsTruncated) {
|
|
3820
|
+
return `showing ${String(item.decisions.length)} of ${String(item.affectedDecisionCount)}; confirmation disabled`;
|
|
3821
|
+
}
|
|
3822
|
+
if (item.affectedDecisionCount > MAX_REPAIR_DECISIONS_FOR_CONFIRM) {
|
|
3823
|
+
return `${String(item.affectedDecisionCount)} affected; exceeds CLI review limit ${String(MAX_REPAIR_DECISIONS_FOR_CONFIRM)}; confirmation disabled`;
|
|
3824
|
+
}
|
|
3825
|
+
if (item.affectedDecisionCount === 0) {
|
|
3826
|
+
return "0 affected; confirmation disabled";
|
|
3827
|
+
}
|
|
3828
|
+
return `${String(item.affectedDecisionCount)} affected; complete`;
|
|
3829
|
+
}
|
|
3830
|
+
function canRenderConfirm(item) {
|
|
3831
|
+
return item.proposal.proposedSha !== void 0 && !item.decisionsTruncated && item.affectedDecisionCount !== null && item.affectedDecisionCount > 0 && item.affectedDecisionCount <= MAX_REPAIR_DECISIONS_FOR_CONFIRM && item.reviewToken !== null;
|
|
3832
|
+
}
|
|
3833
|
+
function shellArg(value) {
|
|
3834
|
+
if (/^[a-zA-Z0-9_./:@%+=,-]+$/u.test(value)) return value;
|
|
3835
|
+
return `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
3836
|
+
}
|
|
3837
|
+
function proposalReviewLabel(item) {
|
|
3838
|
+
if (item.proposal.status === "confirmed") {
|
|
3839
|
+
return canRenderConfirm(item) ? "already confirmed; a guarded application retry may be requested when eligible" : "already confirmed; a guarded retry is currently unavailable";
|
|
3840
|
+
}
|
|
3841
|
+
if (item.proposal.status === "verification_failed") {
|
|
3842
|
+
return canRenderConfirm(item) ? "prior verification failed; eligible for a fresh confirmation" : "prior verification failed; confirmation is currently unavailable";
|
|
3843
|
+
}
|
|
3844
|
+
return canRenderConfirm(item) ? "awaiting operator review" : "awaiting review; confirmation is currently unavailable";
|
|
3845
|
+
}
|
|
3846
|
+
function formatRepairsHuman(result) {
|
|
3847
|
+
if (result.repairs.length === 0) {
|
|
3848
|
+
return "[prim] decision repairs \xB7 0 review-visible proposals \xB7 complete paginated scan";
|
|
3849
|
+
}
|
|
3850
|
+
const lines = [
|
|
3851
|
+
`[prim] decision repairs \xB7 ${String(result.repairs.length)} review-visible proposal(s) \xB7 complete paginated scan`
|
|
3852
|
+
];
|
|
3853
|
+
for (const item of result.repairs) {
|
|
3854
|
+
const { proposal, decisions } = item;
|
|
3855
|
+
const renderedProposalId = oneLine(proposal._id);
|
|
3856
|
+
lines.push(
|
|
3857
|
+
` ${renderedProposalId} [${proposal.status}] \xB7 ${oneLine(proposal.repoFullName)} \xB7 ${shortSha(proposal.deadCommitSha)} -> ${shortSha(proposal.proposedSha)} \xB7 ${tierLabel(proposal.proposedTier)}`,
|
|
3858
|
+
` review state: ${proposalReviewLabel(item)}`
|
|
3859
|
+
);
|
|
3860
|
+
if (proposal.signals.length > 0) {
|
|
3861
|
+
lines.push(` signals: ${proposal.signals.map(oneLine).join("; ")}`);
|
|
3862
|
+
}
|
|
3863
|
+
if (proposal.nextEvaluateAt !== void 0) {
|
|
3864
|
+
lines.push(` next proposal evaluation: ${timestampLabel(proposal.nextEvaluateAt)}`);
|
|
3865
|
+
}
|
|
3866
|
+
lines.push(` decisions (${reviewLabel(item)}):`);
|
|
3867
|
+
for (const decision of decisions) {
|
|
3868
|
+
const id = renderIdentifier({
|
|
3869
|
+
shortId: decision.shortId === void 0 ? void 0 : oneLine(decision.shortId),
|
|
3870
|
+
id: oneLine(decision._id)
|
|
3871
|
+
});
|
|
3872
|
+
const truncation = decision.intentTruncated ? " [intent truncated]" : "";
|
|
3873
|
+
lines.push(` - ${id} [${decision.status}] ${oneLine(decision.intent)}${truncation}`);
|
|
3874
|
+
}
|
|
3875
|
+
if (canRenderConfirm(item) && proposal.status === "proposed") {
|
|
3876
|
+
lines.push(
|
|
3877
|
+
` confirm: prim decisions repairs confirm ${shellArg(renderedProposalId)} ${proposal.proposedSha} --review-token ${item.reviewToken}`
|
|
3878
|
+
);
|
|
3879
|
+
}
|
|
3880
|
+
if (canRenderConfirm(item) && proposal.status === "verification_failed") {
|
|
3881
|
+
lines.push(
|
|
3882
|
+
` retry verification: prim decisions repairs confirm ${shellArg(renderedProposalId)} ${proposal.proposedSha} --review-token ${item.reviewToken}`
|
|
3883
|
+
);
|
|
3884
|
+
}
|
|
3885
|
+
if (canRenderConfirm(item) && proposal.status === "confirmed") {
|
|
3886
|
+
lines.push(
|
|
3887
|
+
` guarded retry confirmation: prim decisions repairs confirm ${shellArg(renderedProposalId)} ${proposal.proposedSha} --review-token ${item.reviewToken}`
|
|
3888
|
+
);
|
|
3889
|
+
}
|
|
3890
|
+
if (proposal.proposedSha !== void 0) {
|
|
3891
|
+
lines.push(
|
|
3892
|
+
` reject: prim decisions repairs reject ${shellArg(renderedProposalId)} ${proposal.proposedSha}`
|
|
3893
|
+
);
|
|
3894
|
+
}
|
|
3895
|
+
}
|
|
3896
|
+
return lines.join("\n");
|
|
3897
|
+
}
|
|
3898
|
+
function formatRepairsJson(result) {
|
|
3899
|
+
return JSON.stringify(result, null, 2);
|
|
3900
|
+
}
|
|
3901
|
+
function formatRepairResolutionHuman(result) {
|
|
3902
|
+
const proposalId = oneLine(result.proposalId);
|
|
3903
|
+
switch (result.outcome.status) {
|
|
3904
|
+
case "confirmed":
|
|
3905
|
+
return `[prim] repair ${proposalId} confirmed; fresh landing verification queued. No Decision was changed by this response.`;
|
|
3906
|
+
case "rejected":
|
|
3907
|
+
return `[prim] repair ${proposalId} rejected.`;
|
|
3908
|
+
case "already_applied":
|
|
3909
|
+
return `[prim] repair ${proposalId} was already applied; nothing to change.`;
|
|
3910
|
+
case "backoff":
|
|
3911
|
+
return `[prim] repair ${proposalId} is in application retry backoff; nothing changed. Retry at ${timestampLabel(result.outcome.retryAt)}.`;
|
|
3912
|
+
case "disabled":
|
|
3913
|
+
return `[prim] repair ${proposalId} was not confirmed because repair application is disabled; nothing changed.`;
|
|
3914
|
+
case "review_too_large":
|
|
3915
|
+
return `[prim] repair ${proposalId} was refused because its complete review exceeds the safe bound; nothing changed.`;
|
|
3916
|
+
case "stale_review":
|
|
3917
|
+
return `[prim] repair ${proposalId} changed after review; re-list and review the current decision set before confirming with its new token. Nothing changed.`;
|
|
3918
|
+
case "stale":
|
|
3919
|
+
return `[prim] repair ${proposalId} has a different proposed SHA; refresh the repair list before acting. Nothing changed.`;
|
|
3920
|
+
case "no_op":
|
|
3921
|
+
return `[prim] repair ${proposalId} cannot be ${result.action === "confirm" ? "confirmed" : "rejected"} in its current state; nothing changed.`;
|
|
3922
|
+
}
|
|
3923
|
+
}
|
|
3924
|
+
function formatRepairResolutionJson(result) {
|
|
3925
|
+
return JSON.stringify(result.outcome, null, 2);
|
|
3926
|
+
}
|
|
3927
|
+
|
|
3397
3928
|
// src/decisions/show.ts
|
|
3398
3929
|
var NOT_FOUND_RE4 = /not found/i;
|
|
3399
3930
|
function colorStatus(status) {
|
|
@@ -3406,14 +3937,14 @@ function colorStatus(status) {
|
|
|
3406
3937
|
return color(status, "gray");
|
|
3407
3938
|
}
|
|
3408
3939
|
var SHOW_TIMEOUT_MS = 1e4;
|
|
3409
|
-
var
|
|
3940
|
+
var defaultDeps7 = { getClient };
|
|
3410
3941
|
var DecisionNotFoundError = class extends Error {
|
|
3411
3942
|
constructor(idOrShortId) {
|
|
3412
3943
|
super(`Decision not found: ${idOrShortId}`);
|
|
3413
3944
|
this.name = "DecisionNotFoundError";
|
|
3414
3945
|
}
|
|
3415
3946
|
};
|
|
3416
|
-
async function fetchShow(idOrShortId, deps =
|
|
3947
|
+
async function fetchShow(idOrShortId, deps = defaultDeps7) {
|
|
3417
3948
|
const params = new URLSearchParams({ id: idOrShortId });
|
|
3418
3949
|
const client = deps.getClient();
|
|
3419
3950
|
try {
|
|
@@ -3547,7 +4078,33 @@ async function askConfirmation(question, output = process.stdout) {
|
|
|
3547
4078
|
|
|
3548
4079
|
// src/commands/decisions.ts
|
|
3549
4080
|
var EXIT_NOT_FOUND = 4;
|
|
4081
|
+
var EXIT_FAILURE = 1;
|
|
3550
4082
|
var EXIT_USAGE = 2;
|
|
4083
|
+
function handleRepairCommandError(error) {
|
|
4084
|
+
if (error instanceof RepairProposalNotFoundError) {
|
|
4085
|
+
console.error(`[prim] ${error.message}`);
|
|
4086
|
+
process.exitCode = EXIT_NOT_FOUND;
|
|
4087
|
+
return true;
|
|
4088
|
+
}
|
|
4089
|
+
if (error instanceof RepairResolutionInputError) {
|
|
4090
|
+
console.error(`[prim] ${error.message}`);
|
|
4091
|
+
process.exitCode = EXIT_USAGE;
|
|
4092
|
+
return true;
|
|
4093
|
+
}
|
|
4094
|
+
if (error instanceof RepairAuthorizationError || error instanceof HttpError && error.status === 403) {
|
|
4095
|
+
console.error(
|
|
4096
|
+
`[prim] ${error instanceof RepairAuthorizationError ? error.message : "Not authorized to review commit repairs; active organization membership and current repository authorization are required"}`
|
|
4097
|
+
);
|
|
4098
|
+
process.exitCode = EXIT_FAILURE;
|
|
4099
|
+
return true;
|
|
4100
|
+
}
|
|
4101
|
+
if (error instanceof RepairEndpointVersionError || error instanceof RepairListContractError) {
|
|
4102
|
+
console.error(`[prim] ${error.message}`);
|
|
4103
|
+
process.exitCode = EXIT_FAILURE;
|
|
4104
|
+
return true;
|
|
4105
|
+
}
|
|
4106
|
+
return false;
|
|
4107
|
+
}
|
|
3551
4108
|
var CREATE_INACTIVE_PROMPT = "[prim] Decision ingestion is disabled here. Create this one Decision without enabling passive ingestion?";
|
|
3552
4109
|
var CREATE_INACTIVE_APPROVED = "[prim] one-time Decision creation approved; passive ingestion remains disabled here";
|
|
3553
4110
|
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 +4183,46 @@ function registerDecisionsCommands(program2) {
|
|
|
3626
4183
|
throw err;
|
|
3627
4184
|
}
|
|
3628
4185
|
});
|
|
4186
|
+
const repairs = decisions.command("repairs").description("Review human-gated commit rewrite repair proposals").action(runRepairsList);
|
|
4187
|
+
async function runRepairsList() {
|
|
4188
|
+
try {
|
|
4189
|
+
const result = await fetchRepairs();
|
|
4190
|
+
console.error(formatRepairsHuman(result));
|
|
4191
|
+
console.log(formatRepairsJson(result));
|
|
4192
|
+
} catch (error) {
|
|
4193
|
+
if (handleRepairCommandError(error)) return;
|
|
4194
|
+
throw error;
|
|
4195
|
+
}
|
|
4196
|
+
}
|
|
4197
|
+
repairs.command("list").description("List review-visible commit rewrite repair proposals").action(runRepairsList);
|
|
4198
|
+
const registerRepairResolution = (action) => {
|
|
4199
|
+
const resolution = repairs.command(`${action} <proposalId> <proposedSha>`).description(
|
|
4200
|
+
action === "confirm" ? "Confirm the exact reviewed decision set and queue fresh landing verification" : "Reject a proposal and remember its proposed SHA"
|
|
4201
|
+
);
|
|
4202
|
+
if (action === "confirm") {
|
|
4203
|
+
resolution.option(
|
|
4204
|
+
"--review-token <token>",
|
|
4205
|
+
"64-character token printed with the complete reviewed decision set (required)"
|
|
4206
|
+
);
|
|
4207
|
+
}
|
|
4208
|
+
resolution.action(
|
|
4209
|
+
async (proposalId, proposedSha, options) => {
|
|
4210
|
+
try {
|
|
4211
|
+
const result = await resolveRepair(proposalId, proposedSha, action, options.reviewToken);
|
|
4212
|
+
console.error(formatRepairResolutionHuman(result));
|
|
4213
|
+
console.log(formatRepairResolutionJson(result));
|
|
4214
|
+
if (result.outcome.status === "review_too_large" || result.outcome.status === "stale_review") {
|
|
4215
|
+
process.exitCode = EXIT_USAGE;
|
|
4216
|
+
}
|
|
4217
|
+
} catch (error) {
|
|
4218
|
+
if (handleRepairCommandError(error)) return;
|
|
4219
|
+
throw error;
|
|
4220
|
+
}
|
|
4221
|
+
}
|
|
4222
|
+
);
|
|
4223
|
+
};
|
|
4224
|
+
registerRepairResolution("confirm");
|
|
4225
|
+
registerRepairResolution("reject");
|
|
3629
4226
|
decisions.command("create").description("Record a decision directly with its explicit user or agent origin").requiredOption("--intent <text>", "What was decided (required)").addOption(
|
|
3630
4227
|
new Option2(
|
|
3631
4228
|
"--attribution <origin>",
|
package/package.json
CHANGED