@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 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, an
186
- exact trusted retry, or a bounded narrowing/revocation rule;
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. Vague continuation text cannot restore older
195
- authorization. Raw revocations override model allows. Compaction and branch
196
- summaries are risk context only; they never count as user authorization.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@erichll/pi-auto-review",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Model-backed boundary approval broker for Pi",
5
5
  "type": "module",
6
6
  "license": "MIT",
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 = applyUserAuthorizationLimits(
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
- candidate.text.includes("I approved one reviewer retry") &&
1555
- containsExactIdentifier(
1556
- candidate.text,
1557
- request.trustedRetryOriginalRequestId,
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
- const effect = constraintEffect(candidate.text);
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
- : exactReference
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 ? ["exact-request-reference" as const] : []),
1576
- ...(activeConstraint && reason !== "active-narrowing-constraint"
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 selectedIds = new Set(selected.map((item) => item.id));
1591
- const constraintOverflow = older.some((candidate) => {
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,