@erichll/pi-auto-review 0.5.0 → 0.6.0
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 +9 -0
- package/README.md +7 -5
- package/package.json +1 -1
- package/src/index.ts +1 -52
- package/src/policy.ts +15 -133
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.6.0 - 2026-08-20
|
|
4
|
+
|
|
5
|
+
- **Remove host user-constraint overlay.** Stop rewriting model allows from
|
|
6
|
+
regex matches on user text (`constraintEffect` / `vagueContinuation` /
|
|
7
|
+
authorization ceilings). Older messages enter evidence only via an exact
|
|
8
|
+
request reference or trusted `/approve` retry. Compaction summaries remain
|
|
9
|
+
excluded from user intent. Hard denies, grant hashing, and the reviewer
|
|
10
|
+
model are unchanged.
|
|
11
|
+
|
|
3
12
|
## 0.5.0 - 2026-08-20
|
|
4
13
|
|
|
5
14
|
- **Safer evidence selection.** Review only request-linked tool calls, paired
|
package/README.md
CHANGED
|
@@ -182,8 +182,8 @@ The reviewer receives one compact canonical request plus bounded, explicitly
|
|
|
182
182
|
untrusted evidence. Selection is deterministic rather than semantic:
|
|
183
183
|
|
|
184
184
|
- the latest raw user message is the authorization anchor;
|
|
185
|
-
- older user messages require an exact request/tool/requester association
|
|
186
|
-
exact trusted retry
|
|
185
|
+
- older user messages require an exact request/tool/requester association or an
|
|
186
|
+
exact trusted retry;
|
|
187
187
|
- tool calls require an exact tool-call ID, exact structured request fields, a
|
|
188
188
|
surface profile, or a security-combination classification; and
|
|
189
189
|
- selected results stay paired with their producer and are limited to exact
|
|
@@ -191,9 +191,11 @@ untrusted evidence. Selection is deterministic rather than semantic:
|
|
|
191
191
|
and Sandbox Runtime process evidence.
|
|
192
192
|
|
|
193
193
|
Unrelated reads, directory listings, builds, tests, assistant prose, and old
|
|
194
|
-
task history are excluded.
|
|
195
|
-
authorization
|
|
196
|
-
|
|
194
|
+
task history are excluded. Compaction and branch summaries are labeled as
|
|
195
|
+
non-authorization context and are never injected as user intent. The host does
|
|
196
|
+
not rewrite a model allow from regex matches on user text, vague continuations,
|
|
197
|
+
or a computed authorization ceiling; those judgments stay with the reviewer
|
|
198
|
+
model. Hard denies still terminate before the model.
|
|
197
199
|
|
|
198
200
|
The current operation appears once as stable, key-sorted JSON. Duplicate exact
|
|
199
201
|
tool-call arguments collapse to an ID/name/reason linkage shell, while fields
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -143,7 +143,6 @@ type ReviewErrorClass =
|
|
|
143
143
|
| "circuit_breaker"
|
|
144
144
|
| "critical_evidence_overflow"
|
|
145
145
|
| "required_profile_overflow"
|
|
146
|
-
| "required_user_constraint_overflow"
|
|
147
146
|
| "reviewer_input_budget_exceeded"
|
|
148
147
|
| "unknown";
|
|
149
148
|
|
|
@@ -771,11 +770,6 @@ function sharedReviewContext(
|
|
|
771
770
|
reviewerContext?: BoundaryReviewerContext,
|
|
772
771
|
): string {
|
|
773
772
|
return canonicalReviewerJson({
|
|
774
|
-
authorizationLimits: {
|
|
775
|
-
compactionState: transcript.compactionState,
|
|
776
|
-
userAuthorizationCeiling: transcript.userAuthorizationCeiling,
|
|
777
|
-
userConstraint: transcript.userConstraint,
|
|
778
|
-
},
|
|
779
773
|
evidence: {
|
|
780
774
|
relevantResults: {
|
|
781
775
|
items: transcript.reviewerEvidence.relevantResults,
|
|
@@ -1405,48 +1399,6 @@ function parseModelRef(modelRef: string): {
|
|
|
1405
1399
|
return { provider, modelId: idParts.join("/") };
|
|
1406
1400
|
}
|
|
1407
1401
|
|
|
1408
|
-
function applyUserAuthorizationLimits(
|
|
1409
|
-
decision: ModelDecision,
|
|
1410
|
-
transcript: TranscriptResult,
|
|
1411
|
-
): ModelDecision {
|
|
1412
|
-
const levels = ["unknown", "low", "medium", "high"] as const;
|
|
1413
|
-
const ceilingIndex = levels.indexOf(transcript.userAuthorizationCeiling);
|
|
1414
|
-
const modelIndex = levels.indexOf(decision.user_authorization);
|
|
1415
|
-
const userAuthorization = levels[Math.min(modelIndex, ceilingIndex)];
|
|
1416
|
-
if (transcript.userConstraint === "revoked" && decision.outcome === "allow") {
|
|
1417
|
-
return {
|
|
1418
|
-
...decision,
|
|
1419
|
-
outcome: "deny",
|
|
1420
|
-
user_authorization: "unknown",
|
|
1421
|
-
rationale: "A current raw user constraint revokes this operation.",
|
|
1422
|
-
};
|
|
1423
|
-
}
|
|
1424
|
-
if (transcript.userConstraint === "narrowed" && decision.outcome === "allow") {
|
|
1425
|
-
return {
|
|
1426
|
-
...decision,
|
|
1427
|
-
outcome: "defer",
|
|
1428
|
-
user_authorization: userAuthorization,
|
|
1429
|
-
rationale:
|
|
1430
|
-
"The requested operation may exceed a current raw user scope constraint.",
|
|
1431
|
-
};
|
|
1432
|
-
}
|
|
1433
|
-
if (
|
|
1434
|
-
decision.outcome === "allow" &&
|
|
1435
|
-
decision.risk_level === "high" &&
|
|
1436
|
-
!["medium", "high"].includes(userAuthorization)
|
|
1437
|
-
) {
|
|
1438
|
-
return {
|
|
1439
|
-
...decision,
|
|
1440
|
-
outcome: "defer",
|
|
1441
|
-
user_authorization: userAuthorization,
|
|
1442
|
-
rationale:
|
|
1443
|
-
transcript.compactionState === "authorization-unavailable"
|
|
1444
|
-
? "Original user authorization is unavailable after compaction."
|
|
1445
|
-
: "Available raw user evidence cannot authorize this high-risk operation.",
|
|
1446
|
-
};
|
|
1447
|
-
}
|
|
1448
|
-
return { ...decision, user_authorization: userAuthorization };
|
|
1449
|
-
}
|
|
1450
1402
|
|
|
1451
1403
|
function reviewerSessionId(
|
|
1452
1404
|
ctx: ExtensionContext,
|
|
@@ -1762,10 +1714,7 @@ async function complete(
|
|
|
1762
1714
|
throw new Error("reviewer returned empty output");
|
|
1763
1715
|
}
|
|
1764
1716
|
try {
|
|
1765
|
-
decision =
|
|
1766
|
-
parseDecision(text),
|
|
1767
|
-
transcript,
|
|
1768
|
-
);
|
|
1717
|
+
decision = parseDecision(text);
|
|
1769
1718
|
} catch (error) {
|
|
1770
1719
|
status = "format_error";
|
|
1771
1720
|
errorClass = parseErrorClass(error);
|
package/src/policy.ts
CHANGED
|
@@ -62,7 +62,6 @@ export type TranscriptResult = {
|
|
|
62
62
|
failureCode?:
|
|
63
63
|
| "critical_evidence_overflow"
|
|
64
64
|
| "required_profile_overflow"
|
|
65
|
-
| "required_user_constraint_overflow"
|
|
66
65
|
| "reviewer_input_budget_exceeded";
|
|
67
66
|
userAuthorizationCeiling: "unknown" | "low" | "medium" | "high";
|
|
68
67
|
userConstraint: "none" | "narrowed" | "revoked";
|
|
@@ -115,7 +114,6 @@ export type EvidenceSensitivity =
|
|
|
115
114
|
export type EvidenceSelectionReason =
|
|
116
115
|
| "latest-user"
|
|
117
116
|
| "exact-request-reference"
|
|
118
|
-
| "active-narrowing-constraint"
|
|
119
117
|
| "trusted-retry-user-message"
|
|
120
118
|
| "exact-tool-call"
|
|
121
119
|
| "structured-request-match"
|
|
@@ -876,7 +874,6 @@ function relevantResultEvidence(
|
|
|
876
874
|
EvidenceSelectionReason,
|
|
877
875
|
| "latest-user"
|
|
878
876
|
| "exact-request-reference"
|
|
879
|
-
| "active-narrowing-constraint"
|
|
880
877
|
| "trusted-retry-user-message"
|
|
881
878
|
| "exact-tool-call"
|
|
882
879
|
| "structured-request-match"
|
|
@@ -1347,7 +1344,6 @@ type UserSelection = {
|
|
|
1347
1344
|
authorizationCeiling: TranscriptResult["userAuthorizationCeiling"];
|
|
1348
1345
|
constraint: TranscriptResult["userConstraint"];
|
|
1349
1346
|
compactionState: TranscriptResult["compactionState"];
|
|
1350
|
-
constraintOverflow: boolean;
|
|
1351
1347
|
};
|
|
1352
1348
|
|
|
1353
1349
|
function utf8Prefix(value: string, maxBytes: number): string {
|
|
@@ -1421,70 +1417,11 @@ function exactUserReference(
|
|
|
1421
1417
|
});
|
|
1422
1418
|
}
|
|
1423
1419
|
|
|
1424
|
-
function constraintEffect(text: string): "narrowed" | "revoked" | undefined {
|
|
1425
|
-
if (
|
|
1426
|
-
/(?:\b(?:do not|don't|never|must not|forbid|forbidden|revoke|cancel)\b|不要|不得|禁止|撤销|取消|不能|别)/i.test(
|
|
1427
|
-
text,
|
|
1428
|
-
)
|
|
1429
|
-
) return "revoked";
|
|
1430
|
-
if (/(?:\b(?:only|except|staging only|not main)\b|只允许|仅允许|仅限|只能)/i.test(text)) {
|
|
1431
|
-
return "narrowed";
|
|
1432
|
-
}
|
|
1433
|
-
return undefined;
|
|
1434
|
-
}
|
|
1435
|
-
|
|
1436
|
-
function vagueContinuation(text: string): boolean {
|
|
1437
|
-
return /^\s*(?:continue|proceed|go ahead|do it|yes|ok(?:ay)?|继续|照做|可以|好的?|行)[.!。!\s]*$/i.test(
|
|
1438
|
-
text,
|
|
1439
|
-
);
|
|
1440
|
-
}
|
|
1441
|
-
|
|
1442
|
-
function constraintMatchesProfile(
|
|
1443
|
-
text: string,
|
|
1444
|
-
profile: EvidenceSurfaceProfile,
|
|
1445
|
-
): boolean {
|
|
1446
|
-
const patterns: Record<EvidenceSurfaceProfile, RegExp> = {
|
|
1447
|
-
network: /(?:\b(?:network|connect|domain|upload|download|http)\b|网络|联网|域名|上传|下载)/i,
|
|
1448
|
-
delete: /(?:\b(?:delete|remove|unlink|rmdir|\brm\b)\b|删除|移除)/i,
|
|
1449
|
-
"git-push": /(?:\b(?:git\s+push|push|branch|main)\b|推送|分支|主分支)/i,
|
|
1450
|
-
forwarded: /(?:\b(?:agent|subagent|forward|worker)\b|代理|子代理|转发)/i,
|
|
1451
|
-
generic: /(?:\b(?:execute|run|write|file|path|permission|deploy)\b|执行|运行|写入|文件|路径|权限|部署)/i,
|
|
1452
|
-
};
|
|
1453
|
-
return patterns[profile].test(text);
|
|
1454
|
-
}
|
|
1455
|
-
|
|
1456
|
-
function constraintConflictsOrIsUncertain(
|
|
1457
|
-
text: string,
|
|
1458
|
-
effect: "narrowed" | "revoked",
|
|
1459
|
-
request: RelevantBoundaryRequest,
|
|
1460
|
-
): boolean {
|
|
1461
|
-
const representation = [
|
|
1462
|
-
request.command,
|
|
1463
|
-
request.path,
|
|
1464
|
-
request.resolvedPath,
|
|
1465
|
-
request.destination,
|
|
1466
|
-
request.agentName,
|
|
1467
|
-
]
|
|
1468
|
-
.filter((value): value is string => Boolean(value))
|
|
1469
|
-
.join(" ")
|
|
1470
|
-
.toLowerCase();
|
|
1471
|
-
if (/\bmain\b|主分支/i.test(text)) {
|
|
1472
|
-
return effect === "revoked"
|
|
1473
|
-
? /\bmain\b/.test(representation)
|
|
1474
|
-
: /\bmain\b/.test(representation);
|
|
1475
|
-
}
|
|
1476
|
-
if (/\bstaging\b|预发布|测试环境/i.test(text)) {
|
|
1477
|
-
const targetsStaging = /\bstaging\b/.test(representation);
|
|
1478
|
-
return effect === "revoked" ? targetsStaging : !targetsStaging;
|
|
1479
|
-
}
|
|
1480
|
-
return true;
|
|
1481
|
-
}
|
|
1482
1420
|
|
|
1483
1421
|
function selectUserEvidence(
|
|
1484
1422
|
entries: readonly unknown[],
|
|
1485
1423
|
evidence: Evidence[],
|
|
1486
1424
|
request: RelevantBoundaryRequest,
|
|
1487
|
-
profile: EvidenceSurfaceProfile,
|
|
1488
1425
|
budgetTokens: number,
|
|
1489
1426
|
): UserSelection {
|
|
1490
1427
|
const users = evidence.filter((item) => item.kind === "user");
|
|
@@ -1508,7 +1445,6 @@ function selectUserEvidence(
|
|
|
1508
1445
|
: "unknown",
|
|
1509
1446
|
constraint: "none",
|
|
1510
1447
|
compactionState,
|
|
1511
|
-
constraintOverflow: false,
|
|
1512
1448
|
};
|
|
1513
1449
|
}
|
|
1514
1450
|
|
|
@@ -1523,15 +1459,6 @@ function selectUserEvidence(
|
|
|
1523
1459
|
),
|
|
1524
1460
|
);
|
|
1525
1461
|
const latestExactReference = exactUserReference(latest.text, request);
|
|
1526
|
-
const latestEffect = constraintEffect(latest.text);
|
|
1527
|
-
const latestActiveConstraint = Boolean(
|
|
1528
|
-
latestEffect && constraintMatchesProfile(latest.text, profile),
|
|
1529
|
-
);
|
|
1530
|
-
const latestConstraintConflict = Boolean(
|
|
1531
|
-
latestEffect &&
|
|
1532
|
-
latestActiveConstraint &&
|
|
1533
|
-
constraintConflictsOrIsUncertain(latest.text, latestEffect, request),
|
|
1534
|
-
);
|
|
1535
1462
|
const selected: Evidence[] = [{
|
|
1536
1463
|
...latest,
|
|
1537
1464
|
text: latestText,
|
|
@@ -1539,32 +1466,26 @@ function selectUserEvidence(
|
|
|
1539
1466
|
secondaryReasons: [
|
|
1540
1467
|
...(latestTrustedRetry ? ["trusted-retry-user-message" as const] : []),
|
|
1541
1468
|
...(latestExactReference ? ["exact-request-reference" as const] : []),
|
|
1542
|
-
...(latestActiveConstraint ? ["active-narrowing-constraint" as const] : []),
|
|
1543
1469
|
],
|
|
1544
1470
|
}];
|
|
1545
1471
|
remaining -= Buffer.byteLength(latestText, "utf8");
|
|
1546
|
-
let constraint: TranscriptResult["userConstraint"] =
|
|
1547
|
-
latestConstraintConflict ? latestEffect! : "none";
|
|
1548
1472
|
|
|
1549
1473
|
const older = eligibleUsers.slice(0, -1).reverse();
|
|
1550
1474
|
for (const candidate of older) {
|
|
1551
1475
|
if (remaining <= 0) break;
|
|
1552
|
-
const trustedRetry =
|
|
1476
|
+
const trustedRetry = Boolean(
|
|
1553
1477
|
request.trustedRetryOriginalRequestId &&
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1478
|
+
candidate.text.includes("I approved one reviewer retry") &&
|
|
1479
|
+
containsExactIdentifier(
|
|
1480
|
+
candidate.text,
|
|
1481
|
+
request.trustedRetryOriginalRequestId,
|
|
1482
|
+
),
|
|
1483
|
+
);
|
|
1559
1484
|
const exactReference = exactUserReference(candidate.text, request);
|
|
1560
|
-
|
|
1561
|
-
const activeConstraint = effect && constraintMatchesProfile(candidate.text, profile);
|
|
1562
|
-
if (!trustedRetry && !exactReference && !activeConstraint) continue;
|
|
1485
|
+
if (!trustedRetry && !exactReference) continue;
|
|
1563
1486
|
const reason: EvidenceSelectionReason = trustedRetry
|
|
1564
1487
|
? "trusted-retry-user-message"
|
|
1565
|
-
:
|
|
1566
|
-
? "exact-request-reference"
|
|
1567
|
-
: "active-narrowing-constraint";
|
|
1488
|
+
: "exact-request-reference";
|
|
1568
1489
|
const text = headTail(candidate.text, remaining);
|
|
1569
1490
|
if (!text) continue;
|
|
1570
1491
|
selected.push({
|
|
@@ -1572,60 +1493,24 @@ function selectUserEvidence(
|
|
|
1572
1493
|
text,
|
|
1573
1494
|
reason,
|
|
1574
1495
|
secondaryReasons: [
|
|
1575
|
-
...(trustedRetry && exactReference
|
|
1576
|
-
|
|
1577
|
-
? ["active-narrowing-constraint" as const]
|
|
1496
|
+
...(trustedRetry && exactReference
|
|
1497
|
+
? ["exact-request-reference" as const]
|
|
1578
1498
|
: []),
|
|
1579
1499
|
],
|
|
1580
1500
|
});
|
|
1581
1501
|
remaining -= Buffer.byteLength(text, "utf8");
|
|
1582
|
-
if (
|
|
1583
|
-
activeConstraint &&
|
|
1584
|
-
constraintConflictsOrIsUncertain(candidate.text, effect, request)
|
|
1585
|
-
) {
|
|
1586
|
-
constraint = effect === "revoked" ? "revoked" : constraint === "none" ? "narrowed" : constraint;
|
|
1587
|
-
}
|
|
1588
1502
|
}
|
|
1589
1503
|
selected.sort((left, right) => left.entryIndex - right.entryIndex);
|
|
1590
|
-
const
|
|
1591
|
-
|
|
1592
|
-
const effect = constraintEffect(candidate.text);
|
|
1593
|
-
if (!effect || !constraintMatchesProfile(candidate.text, profile)) return false;
|
|
1594
|
-
const chosen = selected.find((item) => item.id === candidate.id);
|
|
1595
|
-
return (
|
|
1596
|
-
!selectedIds.has(candidate.id) ||
|
|
1597
|
-
!chosen ||
|
|
1598
|
-
chosen.text.length < candidate.originalCharacters ||
|
|
1599
|
-
candidate.preTruncated
|
|
1600
|
-
);
|
|
1601
|
-
});
|
|
1602
|
-
const latestTruncated = latestText.length < latest.originalCharacters || latest.preTruncated;
|
|
1603
|
-
const hasExactAuthorizationLink = selected.some(
|
|
1604
|
-
(item) =>
|
|
1605
|
-
item.reason === "exact-request-reference" ||
|
|
1606
|
-
item.reason === "trusted-retry-user-message" ||
|
|
1607
|
-
item.secondaryReasons.includes("exact-request-reference") ||
|
|
1608
|
-
item.secondaryReasons.includes("trusted-retry-user-message"),
|
|
1609
|
-
);
|
|
1610
|
-
const authorizationCeiling: TranscriptResult["userAuthorizationCeiling"] =
|
|
1611
|
-
request.trustedRetryOriginalRequestId && constraint !== "revoked"
|
|
1612
|
-
? "high"
|
|
1613
|
-
: compactionState === "authorization-unavailable" || constraint === "revoked"
|
|
1614
|
-
? "unknown"
|
|
1615
|
-
: latestTruncated
|
|
1616
|
-
? "medium"
|
|
1617
|
-
: vagueContinuation(latest.text) && !hasExactAuthorizationLink
|
|
1618
|
-
? "unknown"
|
|
1619
|
-
: "high";
|
|
1504
|
+
const latestTruncated =
|
|
1505
|
+
latestText.length < latest.originalCharacters || latest.preTruncated;
|
|
1620
1506
|
return {
|
|
1621
1507
|
selected,
|
|
1622
1508
|
truncated:
|
|
1623
1509
|
latestTruncated ||
|
|
1624
1510
|
selected.some((item) => item.text.length < item.originalCharacters),
|
|
1625
|
-
authorizationCeiling,
|
|
1626
|
-
constraint,
|
|
1511
|
+
authorizationCeiling: "high",
|
|
1512
|
+
constraint: "none",
|
|
1627
1513
|
compactionState,
|
|
1628
|
-
constraintOverflow,
|
|
1629
1514
|
};
|
|
1630
1515
|
}
|
|
1631
1516
|
|
|
@@ -1690,7 +1575,6 @@ export function buildClassifierTranscript(
|
|
|
1690
1575
|
entries,
|
|
1691
1576
|
evidence,
|
|
1692
1577
|
request,
|
|
1693
|
-
profile,
|
|
1694
1578
|
config.maxUserTranscriptTokens,
|
|
1695
1579
|
);
|
|
1696
1580
|
const requestAwareTools = selectRequestAwareTools(
|
|
@@ -1898,8 +1782,6 @@ export function buildClassifierTranscript(
|
|
|
1898
1782
|
? { failureCode: "critical_evidence_overflow" as const }
|
|
1899
1783
|
: requiredProfileOverflow
|
|
1900
1784
|
? { failureCode: "required_profile_overflow" as const }
|
|
1901
|
-
: users.constraintOverflow
|
|
1902
|
-
? { failureCode: "required_user_constraint_overflow" as const }
|
|
1903
1785
|
: {}),
|
|
1904
1786
|
userAuthorizationCeiling: users.authorizationCeiling,
|
|
1905
1787
|
userConstraint: users.constraint,
|