@tokenfactory/acc-runner 0.44.0 → 0.44.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -0
- package/dist/cost-pricing.d.ts +45 -0
- package/dist/cost-pricing.d.ts.map +1 -1
- package/dist/cost-pricing.js +57 -0
- package/dist/cost-pricing.js.map +1 -1
- package/dist/doctor.d.ts +4 -0
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +19 -0
- package/dist/doctor.js.map +1 -1
- package/dist/instance.d.ts +30 -0
- package/dist/instance.d.ts.map +1 -0
- package/dist/instance.js +51 -0
- package/dist/instance.js.map +1 -0
- package/dist/keychain.d.ts +4 -8
- package/dist/keychain.d.ts.map +1 -1
- package/dist/keychain.js +10 -4
- package/dist/keychain.js.map +1 -1
- package/dist/login.d.ts +4 -0
- package/dist/login.d.ts.map +1 -1
- package/dist/login.js +6 -1
- package/dist/login.js.map +1 -1
- package/dist/memory-context.d.ts +106 -0
- package/dist/memory-context.d.ts.map +1 -0
- package/dist/memory-context.js +220 -0
- package/dist/memory-context.js.map +1 -0
- package/dist/memory-curation.d.ts +172 -0
- package/dist/memory-curation.d.ts.map +1 -0
- package/dist/memory-curation.js +277 -0
- package/dist/memory-curation.js.map +1 -0
- package/dist/program.d.ts.map +1 -1
- package/dist/program.js +13 -0
- package/dist/program.js.map +1 -1
- package/dist/provider-auth.d.ts +19 -0
- package/dist/provider-auth.d.ts.map +1 -1
- package/dist/provider-auth.js +23 -0
- package/dist/provider-auth.js.map +1 -1
- package/dist/runtime/cache-dir.d.ts.map +1 -1
- package/dist/runtime/cache-dir.js +8 -3
- package/dist/runtime/cache-dir.js.map +1 -1
- package/dist/runtime/decision-provenance.d.ts +63 -0
- package/dist/runtime/decision-provenance.d.ts.map +1 -0
- package/dist/runtime/decision-provenance.js +107 -0
- package/dist/runtime/decision-provenance.js.map +1 -0
- package/dist/runtime/prompt-substitute.d.ts +100 -0
- package/dist/runtime/prompt-substitute.d.ts.map +1 -0
- package/dist/runtime/prompt-substitute.js +124 -0
- package/dist/runtime/prompt-substitute.js.map +1 -0
- package/dist/runtime/review-cost-cap.d.ts +167 -0
- package/dist/runtime/review-cost-cap.d.ts.map +1 -0
- package/dist/runtime/review-cost-cap.js +182 -0
- package/dist/runtime/review-cost-cap.js.map +1 -0
- package/dist/runtime/reviewer-abstain.d.ts +101 -0
- package/dist/runtime/reviewer-abstain.d.ts.map +1 -0
- package/dist/runtime/reviewer-abstain.js +138 -0
- package/dist/runtime/reviewer-abstain.js.map +1 -0
- package/dist/runtime/reviewer.d.ts +75 -0
- package/dist/runtime/reviewer.d.ts.map +1 -1
- package/dist/runtime/reviewer.js +205 -65
- package/dist/runtime/reviewer.js.map +1 -1
- package/dist/runtime/singleton.d.ts.map +1 -1
- package/dist/runtime/singleton.js +9 -1
- package/dist/runtime/singleton.js.map +1 -1
- package/dist/serving-auth/index.d.ts +79 -0
- package/dist/serving-auth/index.d.ts.map +1 -0
- package/dist/serving-auth/index.js +169 -0
- package/dist/serving-auth/index.js.map +1 -0
- package/dist/task-runner.d.ts +8 -0
- package/dist/task-runner.d.ts.map +1 -1
- package/dist/task-runner.js +37 -3
- package/dist/task-runner.js.map +1 -1
- package/dist/watch.d.ts.map +1 -1
- package/dist/watch.js +47 -19
- package/dist/watch.js.map +1 -1
- package/package.json +3 -2
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* REVIEW-DECISION-PROVENANCE (runner half; mirrors
|
|
3
|
+
* api/_lib/reviewer/decision-provenance.ts, which carries the full rationale).
|
|
4
|
+
*
|
|
5
|
+
* MEASURED FAULT (PR #1913): `decision = "reject"` was persisted while every
|
|
6
|
+
* entry in `reasons[]` concluded "Met." — a full-pass analysis — with nothing on
|
|
7
|
+
* the row explaining the disagreement. That verdict was not asserted by the
|
|
8
|
+
* reviewer; the prose fallback in `extractReviewerDecision` SYNTHESIZED it from
|
|
9
|
+
* the first approve/reject keyword when the model answered in markdown, and left
|
|
10
|
+
* no trace. The contrast case (PR #1911) was a legitimate forced reject that
|
|
11
|
+
* named its rationale in `reasons[]`.
|
|
12
|
+
*
|
|
13
|
+
* INVARIANT: a forced or synthesized decision carries its justification in
|
|
14
|
+
* `reasons[]` plus a machine-readable marker naming the override source. The
|
|
15
|
+
* runner is the hot-path writer (acc.submit_review), so the guard runs here,
|
|
16
|
+
* immediately before the row is persisted.
|
|
17
|
+
*
|
|
18
|
+
* JUDGEMENT IS NEVER ALTERED — nothing here flips approve↔reject. `reasons[]` is
|
|
19
|
+
* the only channel (acc.review_queue stores `reasons text[]` and submit_review
|
|
20
|
+
* takes no extra argument), so the marker rides there in a parseable form, the
|
|
21
|
+
* same way the read-coverage telemetry line does.
|
|
22
|
+
*
|
|
23
|
+
* PURE. No I/O, no clock, no client.
|
|
24
|
+
*/
|
|
25
|
+
/** Stable prefix for cheap row detection. */
|
|
26
|
+
export const DECISION_PROVENANCE_PREFIX = "decision_provenance:";
|
|
27
|
+
const PROVENANCE_RE = /^decision_provenance:\s*decision_source=(\S+)\s+override_reason=([\s\S]*)$/i;
|
|
28
|
+
/** Rationale stamped when the prose fallback synthesizes the verdict. */
|
|
29
|
+
export const PROSE_KEYWORD_OVERRIDE_REASON = "the reviewer answered in prose instead of the structured verdict object, so " +
|
|
30
|
+
"this decision was synthesized by the parser from the first approve/reject " +
|
|
31
|
+
"keyword in the text rather than asserted as a verdict";
|
|
32
|
+
/** Rationale stamped when a reject is contradicted by its own reasons. */
|
|
33
|
+
export const CONTRADICTION_OVERRIDE_REASON = "the analysis recorded every acceptance criterion as met, yet the verdict is " +
|
|
34
|
+
"reject and the row states no override rationale — the verdict is kept as " +
|
|
35
|
+
"submitted and flagged here for the operator";
|
|
36
|
+
/** Render a provenance record as the reasons[] line that persists it. */
|
|
37
|
+
export function decisionProvenanceReason(p) {
|
|
38
|
+
const rationale = p.override_reason.replace(/\s+/g, " ").trim() || "(unstated)";
|
|
39
|
+
return `${DECISION_PROVENANCE_PREFIX} decision_source=${p.decision_source} override_reason=${rationale}`;
|
|
40
|
+
}
|
|
41
|
+
/** True when a single reason IS the provenance marker (never a finding). */
|
|
42
|
+
export function isDecisionProvenanceReason(reason) {
|
|
43
|
+
return typeof reason === "string" && PROVENANCE_RE.test(reason.trim());
|
|
44
|
+
}
|
|
45
|
+
/** The first provenance marker carried by a verdict's reasons, or null. */
|
|
46
|
+
export function parseDecisionProvenance(reasons) {
|
|
47
|
+
for (const raw of reasons ?? []) {
|
|
48
|
+
if (typeof raw !== "string")
|
|
49
|
+
continue;
|
|
50
|
+
const m = PROVENANCE_RE.exec(raw.trim());
|
|
51
|
+
if (m) {
|
|
52
|
+
return { decision_source: m[1], override_reason: m[2].trim() };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
/** Severity tag the server-side calibrator stamps on stored findings. */
|
|
58
|
+
const SEVERITY_TAG_RE = /^\s*\[(?:blocking|advisory)\]\s*/i;
|
|
59
|
+
/** A finding that concludes the criterion it names was satisfied. */
|
|
60
|
+
const MET_RE = /\bmet\b\s*[.)\]]?\s*$/i;
|
|
61
|
+
/**
|
|
62
|
+
* Defect / non-satisfaction language. Present anywhere ⇒ the analysis is not a
|
|
63
|
+
* clean full pass and the guard stands down — deliberately biased toward NOT
|
|
64
|
+
* stamping, since a missed stamp is an audit gap while a wrong one would
|
|
65
|
+
* editorialize a real reject.
|
|
66
|
+
*/
|
|
67
|
+
const DEFECT_RE = /\b(?:not\s+met|unmet|partially\s+met|fail(?:s|ed|ing|ure)?|missing|absent|violat\w*|blocking|defect|regression|broken|incorrect|unsafe|does\s+not|is\s+not|are\s+not|was\s+not|cannot|can['’]?t)\b/i;
|
|
68
|
+
/**
|
|
69
|
+
* True when a verdict's reasons read as a FULL-PASS analysis: at least one
|
|
70
|
+
* criterion recorded as met and no defect language anywhere. Lines that are
|
|
71
|
+
* neither (telemetry such as the coverage line) are tolerated.
|
|
72
|
+
*/
|
|
73
|
+
export function allCriteriaMet(reasons) {
|
|
74
|
+
const findings = (reasons ?? [])
|
|
75
|
+
.filter((r) => typeof r === "string")
|
|
76
|
+
.filter((r) => !isDecisionProvenanceReason(r))
|
|
77
|
+
.map((r) => r.replace(SEVERITY_TAG_RE, "").trim())
|
|
78
|
+
.filter((r) => r.length > 0);
|
|
79
|
+
if (findings.length === 0)
|
|
80
|
+
return false;
|
|
81
|
+
if (findings.some((r) => DEFECT_RE.test(r)))
|
|
82
|
+
return false;
|
|
83
|
+
return findings.some((r) => MET_RE.test(r));
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* The submit-seam guard. A reject whose reasons record a clean full pass and
|
|
87
|
+
* which carries no provenance marker gets one appended, so the #1913 shape can
|
|
88
|
+
* never be persisted unexplained. Every other verdict is returned untouched and
|
|
89
|
+
* the DECISION IS NEVER CHANGED. Idempotent.
|
|
90
|
+
*/
|
|
91
|
+
export function reconcileReviewDecision(verdict) {
|
|
92
|
+
const reasons = (verdict.reasons ?? []).filter((r) => typeof r === "string");
|
|
93
|
+
const existing = parseDecisionProvenance(reasons);
|
|
94
|
+
if (verdict.decision !== "reject" || existing !== null || !allCriteriaMet(reasons)) {
|
|
95
|
+
return { decision: verdict.decision, reasons, provenance: existing };
|
|
96
|
+
}
|
|
97
|
+
const provenance = {
|
|
98
|
+
decision_source: "reconciled_contradiction",
|
|
99
|
+
override_reason: CONTRADICTION_OVERRIDE_REASON,
|
|
100
|
+
};
|
|
101
|
+
return {
|
|
102
|
+
decision: verdict.decision,
|
|
103
|
+
reasons: [...reasons, decisionProvenanceReason(provenance)],
|
|
104
|
+
provenance,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=decision-provenance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decision-provenance.js","sourceRoot":"","sources":["../../src/runtime/decision-provenance.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S15-FU/REVIEW-1 (AC1) — literal-safe reviewer-prompt placeholder substitution.
|
|
3
|
+
*
|
|
4
|
+
* THE FAULT THIS CLOSES (the LEDGER-1 #1933 episode)
|
|
5
|
+
* ----------------------------------------------------------------------------
|
|
6
|
+
* `renderReviewerPrompt` interpolated the PR diff into the reviewer prompt with
|
|
7
|
+
*
|
|
8
|
+
* template.replace("{{pr_diff}}", diff)
|
|
9
|
+
*
|
|
10
|
+
* `String.prototype.replace` treats a STRING replacement as a pattern: `$$`,
|
|
11
|
+
* `$&`, `` $` ``, `$'` and `$1`…`$9` are expanded, not copied. So a diff whose
|
|
12
|
+
* text contains `$'` — which every one of these clean SQL literals does —
|
|
13
|
+
*
|
|
14
|
+
* select seq, slug, sha from acc.applied_migrations where seq ~ '^[0-9]+$' order by 1
|
|
15
|
+
* ^^ $'
|
|
16
|
+
*
|
|
17
|
+
* made `replace` splice in "the portion of the template AFTER the match": the
|
|
18
|
+
* reviewer's OWN instruction text, starting at
|
|
19
|
+
* "Now answer in this exact JSON shape, with no surrounding prose:". The
|
|
20
|
+
* reviewer then read that prose INSIDE the SQL literal and — correctly, given
|
|
21
|
+
* what it was shown — reported "prose spliced into the literal" as a corruption
|
|
22
|
+
* defect, at 0.75–0.85 confidence, across several independent sessions. The
|
|
23
|
+
* diff on disk was clean the whole time; the harness corrupted it on the way in.
|
|
24
|
+
*
|
|
25
|
+
* (Same root cause as the 3× `gatherAppliedLedger` corruption in
|
|
26
|
+
* `scripts/ci/migration-lint.mjs`: `$'` expansion in a string replacement, not
|
|
27
|
+
* context bleed.)
|
|
28
|
+
*
|
|
29
|
+
* THE RULE: NEVER pass untrusted text as `replace`'s string replacement. A
|
|
30
|
+
* FUNCTION replacement is copied verbatim — `$` has no meaning in its return
|
|
31
|
+
* value — so `substitutePlaceholder` below is the only sanctioned way to put a
|
|
32
|
+
* diff, PR body, title, description, or acceptance criterion into a prompt.
|
|
33
|
+
*
|
|
34
|
+
* Mirrored, byte-for-byte in behaviour, from
|
|
35
|
+
* `api/_lib/reviewer/prompt-substitute.ts` (the server path). The runner ships as
|
|
36
|
+
* a published npm package and cannot import from `api/_lib`, so the module is
|
|
37
|
+
* duplicated and pinned by a cross-implementation parity test.
|
|
38
|
+
* // keep in sync with api/_lib/reviewer/prompt-substitute.ts
|
|
39
|
+
*/
|
|
40
|
+
/**
|
|
41
|
+
* The `$`-sequences `String.prototype.replace` expands inside a STRING
|
|
42
|
+
* replacement. Documented here because the whole module exists to defeat them.
|
|
43
|
+
*
|
|
44
|
+
* `$$` → a literal `$`
|
|
45
|
+
* `$&` → the matched substring
|
|
46
|
+
* ``$` `` → everything BEFORE the match
|
|
47
|
+
* `$'` → everything AFTER the match ← the #1933 corruption
|
|
48
|
+
* `$n` → capture group n
|
|
49
|
+
*/
|
|
50
|
+
export declare const REPLACEMENT_EXPANSION_SEQUENCES: readonly ["$$", "$&", "$`", "$'"];
|
|
51
|
+
/**
|
|
52
|
+
* True when `value` contains a sequence that `String.prototype.replace` would
|
|
53
|
+
* expand if it were used as a string replacement. Diagnostic only — the
|
|
54
|
+
* substitution below is safe either way — but it lets the render seam log that a
|
|
55
|
+
* value was in the dangerous class without ever emitting the value itself.
|
|
56
|
+
*/
|
|
57
|
+
export declare function hasReplacementExpansion(value: string | null | undefined): boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Substitute the FIRST occurrence of `placeholder` with `value`, VERBATIM.
|
|
60
|
+
*
|
|
61
|
+
* The replacement is a function, so no `$`-sequence in `value` is expanded and
|
|
62
|
+
* no template text can bleed into it. An absent placeholder is a no-op (the
|
|
63
|
+
* template is returned unchanged) — same as the string form it replaces.
|
|
64
|
+
*/
|
|
65
|
+
export declare function substitutePlaceholder(template: string, placeholder: string, value: string | null | undefined): string;
|
|
66
|
+
/**
|
|
67
|
+
* Apply an ORDERED list of `[placeholder, value]` pairs. Order matters only in
|
|
68
|
+
* that each pass sees the output of the previous one; because values are
|
|
69
|
+
* inserted verbatim, a value that happens to contain a later placeholder's
|
|
70
|
+
* literal text (`"{{pr_diff}}"` inside a PR body, say) is NOT re-substituted
|
|
71
|
+
* away — the later pass still matches the TEMPLATE's own placeholder first,
|
|
72
|
+
* since it appears earlier in the string only if the template put it there.
|
|
73
|
+
*/
|
|
74
|
+
export declare function substitutePlaceholders(template: string, pairs: ReadonlyArray<readonly [string, string | null | undefined]>): string;
|
|
75
|
+
/** One value that did not survive substitution verbatim. */
|
|
76
|
+
export interface PromptRenderBleed {
|
|
77
|
+
/** The placeholder whose value was corrupted (e.g. `{{pr_diff}}`). */
|
|
78
|
+
placeholder: string;
|
|
79
|
+
/** Length of the value we expected to find verbatim (never the value itself). */
|
|
80
|
+
expected_chars: number;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* INTEGRITY CHECK: every value substituted for a placeholder THE TEMPLATE
|
|
84
|
+
* ACTUALLY CONTAINS must appear VERBATIM in the rendered output. Anything else
|
|
85
|
+
* means the render mangled file content — the #1933 class — and the review must
|
|
86
|
+
* NOT proceed on that prompt (the reviewer would be judging text the repo does
|
|
87
|
+
* not contain).
|
|
88
|
+
*
|
|
89
|
+
* `template` is required precisely so a placeholder the template OMITS is not
|
|
90
|
+
* reported: a prompt that never asks for the PR body was not corrupted by the
|
|
91
|
+
* body's absence, it simply does not carry it. Only a value the template asked
|
|
92
|
+
* for and did not receive verbatim is a bleed.
|
|
93
|
+
*
|
|
94
|
+
* Pure and allocation-cheap (`includes`, no regex). Values are never echoed into
|
|
95
|
+
* the result, so a bleed report is safe to log.
|
|
96
|
+
*/
|
|
97
|
+
export declare function findPromptRenderBleed(template: string, rendered: string, pairs: ReadonlyArray<readonly [string, string | null | undefined]>): PromptRenderBleed[];
|
|
98
|
+
/** Operator-facing one-liner naming the corrupted placeholders (never the values). */
|
|
99
|
+
export declare function describePromptRenderBleed(bleeds: PromptRenderBleed[]): string;
|
|
100
|
+
//# sourceMappingURL=prompt-substitute.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt-substitute.d.ts","sourceRoot":"","sources":["../../src/runtime/prompt-substitute.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH;;;;;;;;;GASG;AACH,eAAO,MAAM,+BAA+B,mCAAoC,CAAC;AAEjF;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAGjF;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC/B,MAAM,CAGR;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,aAAa,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC,GACjE,MAAM,CAMR;AAED,4DAA4D;AAC5D,MAAM,WAAW,iBAAiB;IAChC,sEAAsE;IACtE,WAAW,EAAE,MAAM,CAAC;IACpB,iFAAiF;IACjF,cAAc,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,aAAa,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC,GACjE,iBAAiB,EAAE,CAarB;AAED,sFAAsF;AACtF,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,iBAAiB,EAAE,GAAG,MAAM,CAI7E"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S15-FU/REVIEW-1 (AC1) — literal-safe reviewer-prompt placeholder substitution.
|
|
3
|
+
*
|
|
4
|
+
* THE FAULT THIS CLOSES (the LEDGER-1 #1933 episode)
|
|
5
|
+
* ----------------------------------------------------------------------------
|
|
6
|
+
* `renderReviewerPrompt` interpolated the PR diff into the reviewer prompt with
|
|
7
|
+
*
|
|
8
|
+
* template.replace("{{pr_diff}}", diff)
|
|
9
|
+
*
|
|
10
|
+
* `String.prototype.replace` treats a STRING replacement as a pattern: `$$`,
|
|
11
|
+
* `$&`, `` $` ``, `$'` and `$1`…`$9` are expanded, not copied. So a diff whose
|
|
12
|
+
* text contains `$'` — which every one of these clean SQL literals does —
|
|
13
|
+
*
|
|
14
|
+
* select seq, slug, sha from acc.applied_migrations where seq ~ '^[0-9]+$' order by 1
|
|
15
|
+
* ^^ $'
|
|
16
|
+
*
|
|
17
|
+
* made `replace` splice in "the portion of the template AFTER the match": the
|
|
18
|
+
* reviewer's OWN instruction text, starting at
|
|
19
|
+
* "Now answer in this exact JSON shape, with no surrounding prose:". The
|
|
20
|
+
* reviewer then read that prose INSIDE the SQL literal and — correctly, given
|
|
21
|
+
* what it was shown — reported "prose spliced into the literal" as a corruption
|
|
22
|
+
* defect, at 0.75–0.85 confidence, across several independent sessions. The
|
|
23
|
+
* diff on disk was clean the whole time; the harness corrupted it on the way in.
|
|
24
|
+
*
|
|
25
|
+
* (Same root cause as the 3× `gatherAppliedLedger` corruption in
|
|
26
|
+
* `scripts/ci/migration-lint.mjs`: `$'` expansion in a string replacement, not
|
|
27
|
+
* context bleed.)
|
|
28
|
+
*
|
|
29
|
+
* THE RULE: NEVER pass untrusted text as `replace`'s string replacement. A
|
|
30
|
+
* FUNCTION replacement is copied verbatim — `$` has no meaning in its return
|
|
31
|
+
* value — so `substitutePlaceholder` below is the only sanctioned way to put a
|
|
32
|
+
* diff, PR body, title, description, or acceptance criterion into a prompt.
|
|
33
|
+
*
|
|
34
|
+
* Mirrored, byte-for-byte in behaviour, from
|
|
35
|
+
* `api/_lib/reviewer/prompt-substitute.ts` (the server path). The runner ships as
|
|
36
|
+
* a published npm package and cannot import from `api/_lib`, so the module is
|
|
37
|
+
* duplicated and pinned by a cross-implementation parity test.
|
|
38
|
+
* // keep in sync with api/_lib/reviewer/prompt-substitute.ts
|
|
39
|
+
*/
|
|
40
|
+
/**
|
|
41
|
+
* The `$`-sequences `String.prototype.replace` expands inside a STRING
|
|
42
|
+
* replacement. Documented here because the whole module exists to defeat them.
|
|
43
|
+
*
|
|
44
|
+
* `$$` → a literal `$`
|
|
45
|
+
* `$&` → the matched substring
|
|
46
|
+
* ``$` `` → everything BEFORE the match
|
|
47
|
+
* `$'` → everything AFTER the match ← the #1933 corruption
|
|
48
|
+
* `$n` → capture group n
|
|
49
|
+
*/
|
|
50
|
+
export const REPLACEMENT_EXPANSION_SEQUENCES = ["$$", "$&", "$`", "$'"];
|
|
51
|
+
/**
|
|
52
|
+
* True when `value` contains a sequence that `String.prototype.replace` would
|
|
53
|
+
* expand if it were used as a string replacement. Diagnostic only — the
|
|
54
|
+
* substitution below is safe either way — but it lets the render seam log that a
|
|
55
|
+
* value was in the dangerous class without ever emitting the value itself.
|
|
56
|
+
*/
|
|
57
|
+
export function hasReplacementExpansion(value) {
|
|
58
|
+
const text = value ?? "";
|
|
59
|
+
return /\$[$&`'\d]/.test(text);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Substitute the FIRST occurrence of `placeholder` with `value`, VERBATIM.
|
|
63
|
+
*
|
|
64
|
+
* The replacement is a function, so no `$`-sequence in `value` is expanded and
|
|
65
|
+
* no template text can bleed into it. An absent placeholder is a no-op (the
|
|
66
|
+
* template is returned unchanged) — same as the string form it replaces.
|
|
67
|
+
*/
|
|
68
|
+
export function substitutePlaceholder(template, placeholder, value) {
|
|
69
|
+
const text = value ?? "";
|
|
70
|
+
return (template ?? "").replace(placeholder, () => text);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Apply an ORDERED list of `[placeholder, value]` pairs. Order matters only in
|
|
74
|
+
* that each pass sees the output of the previous one; because values are
|
|
75
|
+
* inserted verbatim, a value that happens to contain a later placeholder's
|
|
76
|
+
* literal text (`"{{pr_diff}}"` inside a PR body, say) is NOT re-substituted
|
|
77
|
+
* away — the later pass still matches the TEMPLATE's own placeholder first,
|
|
78
|
+
* since it appears earlier in the string only if the template put it there.
|
|
79
|
+
*/
|
|
80
|
+
export function substitutePlaceholders(template, pairs) {
|
|
81
|
+
let out = template ?? "";
|
|
82
|
+
for (const [placeholder, value] of pairs) {
|
|
83
|
+
out = substitutePlaceholder(out, placeholder, value);
|
|
84
|
+
}
|
|
85
|
+
return out;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* INTEGRITY CHECK: every value substituted for a placeholder THE TEMPLATE
|
|
89
|
+
* ACTUALLY CONTAINS must appear VERBATIM in the rendered output. Anything else
|
|
90
|
+
* means the render mangled file content — the #1933 class — and the review must
|
|
91
|
+
* NOT proceed on that prompt (the reviewer would be judging text the repo does
|
|
92
|
+
* not contain).
|
|
93
|
+
*
|
|
94
|
+
* `template` is required precisely so a placeholder the template OMITS is not
|
|
95
|
+
* reported: a prompt that never asks for the PR body was not corrupted by the
|
|
96
|
+
* body's absence, it simply does not carry it. Only a value the template asked
|
|
97
|
+
* for and did not receive verbatim is a bleed.
|
|
98
|
+
*
|
|
99
|
+
* Pure and allocation-cheap (`includes`, no regex). Values are never echoed into
|
|
100
|
+
* the result, so a bleed report is safe to log.
|
|
101
|
+
*/
|
|
102
|
+
export function findPromptRenderBleed(template, rendered, pairs) {
|
|
103
|
+
const out = [];
|
|
104
|
+
const tpl = template ?? "";
|
|
105
|
+
const text = rendered ?? "";
|
|
106
|
+
for (const [placeholder, value] of pairs) {
|
|
107
|
+
const expected = value ?? "";
|
|
108
|
+
if (expected.length === 0)
|
|
109
|
+
continue;
|
|
110
|
+
if (!tpl.includes(placeholder))
|
|
111
|
+
continue;
|
|
112
|
+
if (!text.includes(expected)) {
|
|
113
|
+
out.push({ placeholder, expected_chars: expected.length });
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return out;
|
|
117
|
+
}
|
|
118
|
+
/** Operator-facing one-liner naming the corrupted placeholders (never the values). */
|
|
119
|
+
export function describePromptRenderBleed(bleeds) {
|
|
120
|
+
return bleeds
|
|
121
|
+
.map((b) => `${b.placeholder} (${b.expected_chars} chars did not survive verbatim)`)
|
|
122
|
+
.join("; ");
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=prompt-substitute.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt-substitute.js","sourceRoot":"","sources":["../../src/runtime/prompt-substitute.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAU,CAAC;AAEjF;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CAAC,KAAgC;IACtE,MAAM,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;IACzB,OAAO,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACjC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,qBAAqB,CACnC,QAAgB,EAChB,WAAmB,EACnB,KAAgC;IAEhC,MAAM,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;IACzB,OAAO,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;AAC3D,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,sBAAsB,CACpC,QAAgB,EAChB,KAAkE;IAElE,IAAI,GAAG,GAAG,QAAQ,IAAI,EAAE,CAAC;IACzB,KAAK,MAAM,CAAC,WAAW,EAAE,KAAK,CAAC,IAAI,KAAK,EAAE,CAAC;QACzC,GAAG,GAAG,qBAAqB,CAAC,GAAG,EAAE,WAAW,EAAE,KAAK,CAAC,CAAC;IACvD,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAUD;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,qBAAqB,CACnC,QAAgB,EAChB,QAAgB,EAChB,KAAkE;IAElE,MAAM,GAAG,GAAwB,EAAE,CAAC;IACpC,MAAM,GAAG,GAAG,QAAQ,IAAI,EAAE,CAAC;IAC3B,MAAM,IAAI,GAAG,QAAQ,IAAI,EAAE,CAAC;IAC5B,KAAK,MAAM,CAAC,WAAW,EAAE,KAAK,CAAC,IAAI,KAAK,EAAE,CAAC;QACzC,MAAM,QAAQ,GAAG,KAAK,IAAI,EAAE,CAAC;QAC7B,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;YAAE,SAAS;QACpC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,WAAW,CAAC;YAAE,SAAS;QACzC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC7B,GAAG,CAAC,IAAI,CAAC,EAAE,WAAW,EAAE,cAAc,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;QAC7D,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,sFAAsF;AACtF,MAAM,UAAU,yBAAyB,CAAC,MAA2B;IACnE,OAAO,MAAM;SACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,WAAW,KAAK,CAAC,CAAC,cAAc,kCAAkC,CAAC;SACnF,IAAI,CAAC,IAAI,CAAC,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* REV-COST — runner-side per-review cost cap (size-scaled).
|
|
3
|
+
*
|
|
4
|
+
* THE EPISODE (PR #1937). The runner reviewer applied the org's FLAT
|
|
5
|
+
* `max_cost_usd_per_review` and, the instant the spend crossed it, discarded the
|
|
6
|
+
* review with `cost_cap_exceeded`. #1937 is a ~1800-line PR: it cost ~$18 against
|
|
7
|
+
* an $8 flat cap and produced NO verdict at all — the money was spent, the
|
|
8
|
+
* analysis was done, and the harness threw both away, then re-charged the same
|
|
9
|
+
* review on the next tick. That is the measured 239-decisions-over-9-tasks loop
|
|
10
|
+
* AU-5 fixed on the SERVER path (api/_lib/cost-cap.ts) and left unfixed here,
|
|
11
|
+
* which is why a large PR could still be stranded whenever a runner picked the
|
|
12
|
+
* broadcast up (the normal case — the server path is the emergency fallback).
|
|
13
|
+
*
|
|
14
|
+
* This module is the runner's copy of AU-5's PURE cost-cap core:
|
|
15
|
+
*
|
|
16
|
+
* • `effectiveReviewCostCap` — the cap for THIS review, scaled by assembled
|
|
17
|
+
* diff size, FLOORED at the org's flat cap (so a median PR is judged against
|
|
18
|
+
* exactly today's number — never inflated) and CLAMPED at
|
|
19
|
+
* REVIEW_CAP_MAX_MULTIPLE.
|
|
20
|
+
* • `foldReviewCostLedger` — the chargeable-vs-infra split. Only PRODUCTIVE
|
|
21
|
+
* attempts (a spawn that returned a readable reviewer envelope) are charged
|
|
22
|
+
* to the cap; capacity/auth/tooling faults are reported but never charged.
|
|
23
|
+
* • `classifyReviewCost` — within / overrun / runaway. `overrun` keeps the
|
|
24
|
+
* verdict (the money is already spent; discarding it only strands the PR);
|
|
25
|
+
* `runaway` is the hard backstop that still refuses.
|
|
26
|
+
*
|
|
27
|
+
* The runner is a standalone published package and cannot import from api/_lib
|
|
28
|
+
* (the same convention as the duplicated PRICING / MODEL_ALIASES maps and
|
|
29
|
+
* review-diff-guard.ts). KEEP IN SYNC with the AU-5 block at the bottom of
|
|
30
|
+
* api/_lib/cost-cap.ts — the constants and all three functions are pinned
|
|
31
|
+
* byte-for-behaviour by tests/unit/revcost1-runner-cap-parity.test.ts, which
|
|
32
|
+
* fails the build the moment the two drift.
|
|
33
|
+
*/
|
|
34
|
+
/**
|
|
35
|
+
* Assembled-diff size the flat cap is treated as calibrated for: the measured
|
|
36
|
+
* p50 production churn (750 changed lines ≈ 30k chars of unified diff). At or
|
|
37
|
+
* below this the effective cap is EXACTLY `max_cost_usd_per_review`.
|
|
38
|
+
* Mirrors api/_lib/cost-cap.ts. // keep in sync
|
|
39
|
+
*/
|
|
40
|
+
export declare const REVIEW_CAP_REFERENCE_DIFF_CHARS = 30000;
|
|
41
|
+
/** Fraction of the base cap added per reference-diff-worth of EXCESS size. */
|
|
42
|
+
export declare const REVIEW_CAP_SIZE_SLOPE = 0.5;
|
|
43
|
+
/**
|
|
44
|
+
* Ceiling on the scaled cap as a multiple of the base cap. Deliberately tight
|
|
45
|
+
* (1.5× → $3.00 at a $2 base) because the measured size↔cost correlation is
|
|
46
|
+
* weak (Pearson r = 0.100): a bigger ramp would be unjustified by the data.
|
|
47
|
+
*/
|
|
48
|
+
export declare const REVIEW_CAP_MAX_MULTIPLE = 1.5;
|
|
49
|
+
/**
|
|
50
|
+
* Multiple of the effective cap above which an already-PAID review is still
|
|
51
|
+
* discarded as a runaway — the hard backstop that survives this change. Between
|
|
52
|
+
* 1× and this, the verdict is KEPT and flagged (`cost_overrun`) instead of
|
|
53
|
+
* stranding the PR.
|
|
54
|
+
*/
|
|
55
|
+
export declare const REVIEW_COST_OVERRUN_TOLERANCE = 3;
|
|
56
|
+
export interface ReviewCostCapDecision {
|
|
57
|
+
/** The cap to enforce for THIS review. */
|
|
58
|
+
cap_usd: number;
|
|
59
|
+
/** The org's flat `max_cost_usd_per_review` — the floor. */
|
|
60
|
+
base_cap_usd: number;
|
|
61
|
+
/** false when the base cap is 0/negative/non-finite (cap disabled). */
|
|
62
|
+
enabled: boolean;
|
|
63
|
+
/** Assembled-diff chars the scale was computed from. */
|
|
64
|
+
diff_chars: number;
|
|
65
|
+
/** cap_usd / base_cap_usd — 1 when the flat floor applied. */
|
|
66
|
+
scale: number;
|
|
67
|
+
/** True when the size ramp hit REVIEW_CAP_MAX_MULTIPLE. */
|
|
68
|
+
ceiling_applied: boolean;
|
|
69
|
+
/** Spend above which an already-paid verdict is discarded as a runaway. */
|
|
70
|
+
runaway_usd: number;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The size-scaled per-review cost cap. Monotone non-decreasing in `diffChars`,
|
|
74
|
+
* floored at `baseCapUsd` (so it can only ever LOOSEN relative to the flat cap —
|
|
75
|
+
* a small PR's effective cap is unchanged) and clamped at
|
|
76
|
+
* `REVIEW_CAP_MAX_MULTIPLE × baseCapUsd`. Pure.
|
|
77
|
+
*
|
|
78
|
+
* A base cap of 0 (or a non-finite/negative one) means "cap disabled" — the
|
|
79
|
+
* existing `max_cost_usd_per_review > 0` semantics — and is returned verbatim.
|
|
80
|
+
*
|
|
81
|
+
* Mirrors api/_lib/cost-cap.ts `effectiveReviewCostCap`. // keep in sync
|
|
82
|
+
*/
|
|
83
|
+
export declare function effectiveReviewCostCap(baseCapUsd: number, diffChars: number): ReviewCostCapDecision;
|
|
84
|
+
/**
|
|
85
|
+
* How one reviewer attempt is classified for BUDGET purposes. Only `productive`
|
|
86
|
+
* attempts (a spawn that returned a reviewer envelope we can read a verdict out
|
|
87
|
+
* of) are charged to the per-review cap. Every other class is an INFRA fault: it
|
|
88
|
+
* is still reported in the review's total spend so the accounting stays honest,
|
|
89
|
+
* but it must never consume the budget set aside for reviewing this PR.
|
|
90
|
+
*
|
|
91
|
+
* Mirrors api/_lib/cost-cap.ts `ReviewAttemptClass`. // keep in sync
|
|
92
|
+
*/
|
|
93
|
+
export type ReviewAttemptClass = "productive"
|
|
94
|
+
/** Parse-repair re-prompt — a harness cost, not review work. */
|
|
95
|
+
| "parse_repair"
|
|
96
|
+
/** 429 / overloaded / rate-limit brownout (budget-exempt). */
|
|
97
|
+
| "infra_capacity"
|
|
98
|
+
/** Hard billing/quota wall. */
|
|
99
|
+
| "infra_billing"
|
|
100
|
+
/** Account-level spend cap. */
|
|
101
|
+
| "infra_billing_capped"
|
|
102
|
+
/** Missing/invalid credential — env broken. */
|
|
103
|
+
| "infra_auth"
|
|
104
|
+
/** Verification-tooling failure (unread file / truncated diff). */
|
|
105
|
+
| "infra_tooling";
|
|
106
|
+
export interface ReviewCostAttempt {
|
|
107
|
+
/** 1-based attempt ordinal within this review. */
|
|
108
|
+
attempt: number;
|
|
109
|
+
model_id: string;
|
|
110
|
+
attempt_class: ReviewAttemptClass;
|
|
111
|
+
/** Spend this attempt actually incurred (0 for a throw before any tokens). */
|
|
112
|
+
cost_usd: number;
|
|
113
|
+
}
|
|
114
|
+
export interface ReviewCostLedger {
|
|
115
|
+
attempts: ReviewCostAttempt[];
|
|
116
|
+
/** Sum of PRODUCTIVE attempt cost — the only figure the cap is checked against. */
|
|
117
|
+
chargeable_usd: number;
|
|
118
|
+
/** Sum of every non-productive attempt cost (reported, never charged). */
|
|
119
|
+
infra_usd: number;
|
|
120
|
+
/** chargeable + infra — what the review truly cost (stored on the queue row). */
|
|
121
|
+
total_usd: number;
|
|
122
|
+
productive_attempts: number;
|
|
123
|
+
infra_attempts: number;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Fold an attempt list into the chargeable / infra split. Pure.
|
|
127
|
+
* Mirrors api/_lib/cost-cap.ts `foldReviewCostLedger`. // keep in sync
|
|
128
|
+
*/
|
|
129
|
+
export declare function foldReviewCostLedger(attempts: ReviewCostAttempt[]): ReviewCostLedger;
|
|
130
|
+
/**
|
|
131
|
+
* Verdict on this review's chargeable spend:
|
|
132
|
+
* • `within` — at or under the effective cap. Nothing changes.
|
|
133
|
+
* • `overrun` — over the cap but under the runaway threshold. The money is
|
|
134
|
+
* ALREADY SPENT, so discarding the verdict buys nothing and strands the PR
|
|
135
|
+
* (#1937). Keep the verdict and flag it.
|
|
136
|
+
* • `runaway` — past the tolerance band. Refuse the verdict; something is
|
|
137
|
+
* pathological and the operator must see it.
|
|
138
|
+
*
|
|
139
|
+
* Mirrors api/_lib/cost-cap.ts `classifyReviewCost`. // keep in sync
|
|
140
|
+
*/
|
|
141
|
+
export type ReviewCostVerdict = "within" | "overrun" | "runaway";
|
|
142
|
+
export declare function classifyReviewCost(chargeableUsd: number, cap: ReviewCostCapDecision): ReviewCostVerdict;
|
|
143
|
+
/** Mutable cost state for ONE review, shared by every pass it runs. */
|
|
144
|
+
export interface ReviewCostContext {
|
|
145
|
+
cap: ReviewCostCapDecision;
|
|
146
|
+
attempts: ReviewCostAttempt[];
|
|
147
|
+
}
|
|
148
|
+
/** Open a cost context for a review over `diffChars` of assembled diff. */
|
|
149
|
+
export declare function openReviewCostContext(baseCapUsd: number, diffChars: number): ReviewCostContext;
|
|
150
|
+
/** Record one attempt and return the folded ledger. */
|
|
151
|
+
export declare function chargeReviewAttempt(ctx: ReviewCostContext, modelId: string, attemptClass: ReviewAttemptClass, costUsd: number): ReviewCostLedger;
|
|
152
|
+
/** The current folded ledger without recording anything. */
|
|
153
|
+
export declare function reviewCostLedger(ctx: ReviewCostContext): ReviewCostLedger;
|
|
154
|
+
/**
|
|
155
|
+
* Advisory reason for an ACCEPTED overrun. Deliberately phrased to match none of
|
|
156
|
+
* the absence-claim patterns in review-chunking.ts (asserted by this wave's
|
|
157
|
+
* test), so an audit line can never be re-read as a read-coverage claim.
|
|
158
|
+
*/
|
|
159
|
+
export declare function reviewCostOverrunReason(cap: ReviewCostCapDecision, ledger: ReviewCostLedger): string;
|
|
160
|
+
/**
|
|
161
|
+
* Refusal reason for a RUNAWAY — the backstop above the scaled cap. Carries the
|
|
162
|
+
* PRODUCTIVE attempt count, which is what distinguishes a single-pass runaway
|
|
163
|
+
* from a chunked review that accumulated past the tolerance over several part
|
|
164
|
+
* passes (the old chunked-only reason string), without needing two call sites.
|
|
165
|
+
*/
|
|
166
|
+
export declare function reviewCostRunawayReason(cap: ReviewCostCapDecision, ledger: ReviewCostLedger): string;
|
|
167
|
+
//# sourceMappingURL=review-cost-cap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-cost-cap.d.ts","sourceRoot":"","sources":["../../src/runtime/review-cost-cap.ts"],"names":[],"mappings":"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"}
|