@jphutchins/code-review 0.1.0-alpha.41 → 0.1.0-alpha.43
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 +28 -44
- package/dist/index.js +27 -41
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/schema/VERSIONING.md +17 -15
- package/schema/findings.schema.json +1 -1
package/README.md
CHANGED
|
@@ -100,61 +100,45 @@ links to:
|
|
|
100
100
|
downstream tool) SHOULD base64-decode and parse that marker rather than parse the comment's prose.
|
|
101
101
|
Embedding in the comment (rather than only linking the artifact) keeps the pointer from expiring
|
|
102
102
|
with artifact retention; when the encoded findings are too large to embed, the sticky falls back to
|
|
103
|
-
a `<!-- code-review:findings-json <url> -->` link marker instead
|
|
104
|
-
agent's draft and lacks the surfaced fields below) — the shared serializer is
|
|
103
|
+
a `<!-- code-review:findings-json <url> -->` link marker instead. The shared serializer is
|
|
105
104
|
[`src/surface.ts`](src/surface.ts).
|
|
106
105
|
|
|
107
|
-
The embedded document is the **
|
|
108
|
-
|
|
109
|
-
[`schema/findings.schema.json`](schema/findings.schema.json) validates
|
|
110
|
-
|
|
111
|
-
|
|
106
|
+
The embedded document is the agent's **complete** findings document — the same `schema_version`
|
|
107
|
+
0.6.0 contract the review agent is held to and
|
|
108
|
+
[`schema/findings.schema.json`](schema/findings.schema.json) validates, and the same object the
|
|
109
|
+
comment is rendered from. It is embedded verbatim: no field is added or dropped, so the machine
|
|
110
|
+
channel can never carry less than, or drift from, the rendered prose.
|
|
112
111
|
|
|
113
112
|
```json
|
|
114
113
|
{
|
|
115
|
-
"schema_version": "0.
|
|
114
|
+
"schema_version": "0.6.0",
|
|
116
115
|
"verdict": "comment",
|
|
117
116
|
"summary": "...",
|
|
118
|
-
"convergence": { "score": 1, "threshold": 1, "converged": true },
|
|
119
|
-
"round": 4,
|
|
120
|
-
"scope_metastasis": {
|
|
121
|
-
"decision_prompt": "Findings keep recurring in the same mechanism across consecutive rounds — each fix keeps enabling the next finding in that machinery. This is a decision, not a directive: state in your summary whether you are committing to the expanding scope (plan the remaining facets of the recurring mechanism(s) above as one unit) or narrowing the scope so the recurrence stops.",
|
|
122
|
-
"recurring": [
|
|
123
|
-
{ "code": "mixin-field-guard-gap", "consecutive_rounds": 4, "start_round": 1 }
|
|
124
|
-
]
|
|
125
|
-
},
|
|
126
117
|
"findings": []
|
|
127
118
|
}
|
|
128
119
|
```
|
|
129
120
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
complete (empty diff, corrupt output, did-not-complete) — embeds **no** signal in its own blob:
|
|
152
|
-
its document already says no review was produced this run, and a carried `converged` beside that
|
|
153
|
-
would read as a stop signal for a run that produced none; the last completed round's signal still
|
|
154
|
-
survives on the sticky in the compact `<!-- code-review:signal;base64 <base64> -->` marker, so
|
|
155
|
-
the next post reads it back. The same compact marker is emitted when the embedded payload is too
|
|
156
|
-
large and the whole-document marker falls back to the artifact link — an oversized review's stop
|
|
157
|
-
signal stays readable and can be carried forward.
|
|
121
|
+
The deterministic **stop signal** for an iterating author-agent rides its own compact marker beside
|
|
122
|
+
the findings blob, `<!-- code-review:signal;base64 <base64> -->`, whose decoded payload is
|
|
123
|
+
`{ "schema_version": "0.8.0", "round": <n>, "convergence": { "score": <s>, "threshold": <t>, "converged": <bool> } }`.
|
|
124
|
+
`round` is the count of completed full-review rounds; `convergence.score` = critical·4 + major·2 +
|
|
125
|
+
minor·1 + nit·0, `threshold` defaults to 1, and `converged` = score ≤ threshold as a literal
|
|
126
|
+
boolean — `converged: true` means the last completed round is at or below the tolerance, so another
|
|
127
|
+
iteration round is not warranted. The commenter computes the signal from the review's own severities
|
|
128
|
+
(the agent never writes it); it appears once at least one full-review round has completed and is
|
|
129
|
+
carried verbatim afterward — a mechanic (CI-fix) pass or an envelope-loss notice re-emits the last
|
|
130
|
+
completed round's signal rather than re-deriving it, so an operator changing `convergence_threshold`
|
|
131
|
+
mid-PR cannot flip a stored `converged`, and a notice never fabricates a stop signal for a run that
|
|
132
|
+
produced no review. Both markers survive the "review in progress" banner, which replaces only the
|
|
133
|
+
sticky's prose and carries them forward verbatim.
|
|
134
|
+
|
|
135
|
+
Cross-round recurrence (the sticky's "Scope metastasis" warning) is carried structurally in the
|
|
136
|
+
`<!-- code-review:rounds;base64 <base64> -->` marker — per-round severity counts and mechanism
|
|
137
|
+
frequencies — from which the re-review seed re-derives the advisory `scope_metastasis` entry it
|
|
138
|
+
hands the next-round agent. It is deliberately NOT embedded in the findings blob: the blob is the
|
|
139
|
+
agent's own document, and a recurrence claim is round state the commenter owns. Each inline review
|
|
140
|
+
comment embeds only its own finding (a `schema_version` + one-finding fragment), so the sticky and
|
|
141
|
+
review-body markers remain the whole-document source of truth.
|
|
158
142
|
- **`code-review-transcript`** — the full Claude Code session transcripts for the triage and review
|
|
159
143
|
phases. This is advisory/auditability only: it is never read by the comment job and never affects
|
|
160
144
|
what gets posted.
|
package/dist/index.js
CHANGED
|
@@ -598,28 +598,15 @@ var convergenceSignal = (counts, threshold = DEFAULT_CONVERGENCE_THRESHOLD) => {
|
|
|
598
598
|
return { score, threshold, converged: score <= threshold };
|
|
599
599
|
};
|
|
600
600
|
var signalForRound = (round, counts, threshold = DEFAULT_CONVERGENCE_THRESHOLD) => ({ round, convergence: convergenceSignal(counts, threshold) });
|
|
601
|
-
var surfaceFindings = (findings, signal, scopeMetastasis = null) => {
|
|
602
|
-
const agentDoc = Object.fromEntries(
|
|
603
|
-
Object.entries(findings).filter(
|
|
604
|
-
([key2]) => key2 !== "round" && key2 !== "convergence" && key2 !== "scope_metastasis"
|
|
605
|
-
)
|
|
606
|
-
);
|
|
607
|
-
return {
|
|
608
|
-
...agentDoc,
|
|
609
|
-
schema_version: SURFACE_SCHEMA_VERSION,
|
|
610
|
-
...signal === null ? {} : signal,
|
|
611
|
-
...scopeMetastasis === null ? {} : { scope_metastasis: scopeMetastasis }
|
|
612
|
-
};
|
|
613
|
-
};
|
|
614
601
|
var signalMarker = (signal) => `<!-- code-review:signal;base64 ${Buffer.from(
|
|
615
602
|
JSON.stringify({ schema_version: SURFACE_SCHEMA_VERSION, ...signal }),
|
|
616
603
|
"utf-8"
|
|
617
604
|
).toString("base64")} -->`;
|
|
618
|
-
var
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
605
|
+
var joinSignalMarker = (base, marker) => base === "" ? marker : `${base}
|
|
606
|
+
${marker}`;
|
|
607
|
+
var surfacedFindingsPointer = (findings, signal, jsonUrl) => {
|
|
608
|
+
const marker = findingsPointer(findings, jsonUrl);
|
|
609
|
+
return signal === null ? marker : joinSignalMarker(marker, signalMarker(signal));
|
|
623
610
|
};
|
|
624
611
|
var SIGNAL_RE = /<!-- code-review:signal;base64 ([A-Za-z0-9+/=]+) -->/;
|
|
625
612
|
var parseSignalMarker = (body) => {
|
|
@@ -630,8 +617,7 @@ var parseSignalMarker = (body) => {
|
|
|
630
617
|
var parseSurfaceSignal = (doc) => {
|
|
631
618
|
if (typeof doc !== "object" || doc === null || Array.isArray(doc)) return null;
|
|
632
619
|
const o = doc;
|
|
633
|
-
|
|
634
|
-
if (typeof declared !== "string" || !SURFACE_SCHEMA_VERSIONS.includes(declared)) return null;
|
|
620
|
+
if (!isSurfaceVersion(o["schema_version"])) return null;
|
|
635
621
|
const round = o["round"];
|
|
636
622
|
const convergence = o["convergence"];
|
|
637
623
|
if (typeof round !== "number" || !Number.isSafeInteger(round) || round < 1) return null;
|
|
@@ -646,11 +632,11 @@ var parseSurfaceSignal = (doc) => {
|
|
|
646
632
|
};
|
|
647
633
|
};
|
|
648
634
|
var SURFACE_SCHEMA_VERSIONS = ["0.7.0", SURFACE_SCHEMA_VERSION];
|
|
635
|
+
var isSurfaceVersion = (version) => typeof version === "string" && SURFACE_SCHEMA_VERSIONS.includes(version);
|
|
649
636
|
var stripSurfaceFields = (doc) => {
|
|
650
637
|
if (typeof doc !== "object" || doc === null || Array.isArray(doc)) return doc;
|
|
651
638
|
const o = doc;
|
|
652
|
-
|
|
653
|
-
if (typeof declared !== "string" || !SURFACE_SCHEMA_VERSIONS.includes(declared)) return doc;
|
|
639
|
+
if (!isSurfaceVersion(o["schema_version"])) return doc;
|
|
654
640
|
const rest = Object.fromEntries(
|
|
655
641
|
Object.entries(o).filter(([key2]) => key2 !== "convergence" && key2 !== "round")
|
|
656
642
|
);
|
|
@@ -1059,7 +1045,6 @@ var render = (input) => {
|
|
|
1059
1045
|
const isFullReviewRound = (input.convergenceRound ?? (isConvergenceRound(route, incomplete) && rounds.length > 0)) && isReviewVerdict(input.findings.verdict);
|
|
1060
1046
|
const convergenceCounts = rounds[rounds.length - 1] ?? computeRoundCounts(input.findings);
|
|
1061
1047
|
const advisoryAllowed = isFullReviewRound;
|
|
1062
|
-
const scopeMetastasis = input.scopeMetastasis !== void 0 ? input.scopeMetastasis : advisoryAllowed ? computeScopeMetastasis(rounds) : null;
|
|
1063
1048
|
return eta.renderString(input.template, {
|
|
1064
1049
|
findings: input.findings,
|
|
1065
1050
|
envelope: input.envelope,
|
|
@@ -1090,8 +1075,7 @@ var render = (input) => {
|
|
|
1090
1075
|
// label does; post always supplies the marker, so this path cannot disagree with it in
|
|
1091
1076
|
// production (issue #141 review r4).
|
|
1092
1077
|
isFullReviewRound && rounds.length > 0 ? signalForRound(rounds.length, convergenceCounts, input.convergenceThreshold) : null,
|
|
1093
|
-
input.jsonUrl
|
|
1094
|
-
scopeMetastasis
|
|
1078
|
+
input.jsonUrl
|
|
1095
1079
|
),
|
|
1096
1080
|
roundsMarker: roundsMarker(rounds),
|
|
1097
1081
|
roundsSummary: roundsSummary(rounds, input.roundCount),
|
|
@@ -2273,10 +2257,9 @@ var post = async (input, ghApi = runGhApi) => {
|
|
|
2273
2257
|
const emptyMechanicWouldBury = (route, incomplete) => route === "mechanic" && !incomplete && findings.findings.length === 0 && existingSticky !== null && priorIsFullReview(existingSticky.body);
|
|
2274
2258
|
const priorRounds = existingSticky !== null ? parseRounds(existingSticky.body) : [];
|
|
2275
2259
|
const priorSignal = existingSticky === null ? null : parseSignalMarker(existingSticky.body) ?? parseSurfaceSignal(parseFindingsMarker(existingSticky.body));
|
|
2276
|
-
const findingsMarkerFor = (findings2, signal2
|
|
2277
|
-
const pointer = surfacedFindingsPointer(findings2, signal2, input.jsonUrl
|
|
2278
|
-
return signal2 !== null || priorSignal === null ? pointer :
|
|
2279
|
-
${signalMarker(priorSignal)}`;
|
|
2260
|
+
const findingsMarkerFor = (findings2, signal2) => {
|
|
2261
|
+
const pointer = surfacedFindingsPointer(findings2, signal2, input.jsonUrl);
|
|
2262
|
+
return signal2 !== null || priorSignal === null ? pointer : joinSignalMarker(pointer, signalMarker(priorSignal));
|
|
2280
2263
|
};
|
|
2281
2264
|
const leaveInPlace = (message) => {
|
|
2282
2265
|
process.stderr.write(
|
|
@@ -2430,8 +2413,8 @@ ${dropNote}` : ""}`,
|
|
|
2430
2413
|
inlineDisposition: { kind: "no-envelope" },
|
|
2431
2414
|
runUrl: input.runUrl,
|
|
2432
2415
|
jsonUrl: input.jsonUrl,
|
|
2433
|
-
// Same signal rule as the main path:
|
|
2434
|
-
//
|
|
2416
|
+
// Same signal rule as the main path: a completed-review doc and an error-verdict doc alike
|
|
2417
|
+
// ride the prior signal on the compact marker, never inside the blob.
|
|
2435
2418
|
findingsPointer: findingsMarkerFor(
|
|
2436
2419
|
findings,
|
|
2437
2420
|
isReviewVerdict(findings.verdict) ? priorSignal : null
|
|
@@ -2490,19 +2473,18 @@ ${dropNote}` : ""}`,
|
|
|
2490
2473
|
)
|
|
2491
2474
|
] : priorRounds;
|
|
2492
2475
|
const signal = isRound ? signalForRound(roundNumber, computeRoundCounts(findings), input.convergenceThreshold) : thisIncomplete || !isReviewVerdict(findings.verdict) ? null : priorSignal;
|
|
2493
|
-
const
|
|
2494
|
-
const
|
|
2495
|
-
const
|
|
2496
|
-
surfaceFindings(findings, signal, scopeMetastasis),
|
|
2497
|
-
input.jsonUrl
|
|
2498
|
-
);
|
|
2476
|
+
const findingsMarker = findingsMarkerFor(findings, signal);
|
|
2477
|
+
const markerForm = findingsMarkerForm(findings, input.jsonUrl);
|
|
2478
|
+
const signalNote = signal !== null || priorSignal !== null ? " (the stop signal still rides the compact marker)" : "";
|
|
2499
2479
|
if (markerForm === "link") {
|
|
2500
2480
|
process.stderr.write(
|
|
2501
|
-
|
|
2481
|
+
`Warning: the findings-json blob exceeds the embed limit \u2014 degraded to the jsonUrl-link form; a decoding agent must fetch the artifact for the findings${signalNote}
|
|
2482
|
+
`
|
|
2502
2483
|
);
|
|
2503
2484
|
} else if (markerForm === "omitted") {
|
|
2504
2485
|
process.stderr.write(
|
|
2505
|
-
|
|
2486
|
+
`Warning: the findings-json blob exceeds the embed limit and no --json-url was given \u2014 the embedded findings seed is dropped from the posted surfaces${signalNote}
|
|
2487
|
+
`
|
|
2506
2488
|
);
|
|
2507
2489
|
}
|
|
2508
2490
|
const commonRenderInput = {
|
|
@@ -2521,7 +2503,6 @@ ${dropNote}` : ""}`,
|
|
|
2521
2503
|
sameRootNotes,
|
|
2522
2504
|
answeredNotes: reRaisedNotes,
|
|
2523
2505
|
answeredReRaiseNote: answeredDropNote,
|
|
2524
|
-
scopeMetastasis,
|
|
2525
2506
|
roundCount: signal?.round ?? priorSignal?.round ?? priorRounds.length,
|
|
2526
2507
|
convergenceThreshold: input.convergenceThreshold,
|
|
2527
2508
|
convergenceRound: isRound,
|
|
@@ -4166,6 +4147,8 @@ ${printableSchema(schemaPath)}
|
|
|
4166
4147
|
}
|
|
4167
4148
|
}
|
|
4168
4149
|
});
|
|
4150
|
+
var isSurfaceStampedDoc = (doc) => typeof doc === "object" && doc !== null && !Array.isArray(doc) && doc["schema_version"] === SURFACE_SCHEMA_VERSION;
|
|
4151
|
+
var withoutScopeMetastasis = (doc) => typeof doc === "object" && doc !== null && !Array.isArray(doc) ? Object.fromEntries(Object.entries(doc).filter(([key2]) => key2 !== "scope_metastasis")) : doc;
|
|
4169
4152
|
var seedDraftCmd = defineCommand({
|
|
4170
4153
|
meta: {
|
|
4171
4154
|
name: "seed-draft",
|
|
@@ -4239,7 +4222,10 @@ var seedDraftCmd = defineCommand({
|
|
|
4239
4222
|
})();
|
|
4240
4223
|
return typeof raw === "object" && raw !== null && "body" in raw && typeof raw.body === "string" ? raw.body : null;
|
|
4241
4224
|
})();
|
|
4242
|
-
const
|
|
4225
|
+
const parsedPrior = priorBody === null ? null : parseFindingsMarker(priorBody);
|
|
4226
|
+
const strippedPrior = parsedPrior === null ? null : stripSurfaceFields(
|
|
4227
|
+
isSurfaceStampedDoc(parsedPrior) ? parsedPrior : withoutScopeMetastasis(parsedPrior)
|
|
4228
|
+
);
|
|
4243
4229
|
const priorFindings = (() => {
|
|
4244
4230
|
if (strippedPrior === null || typeof strippedPrior !== "object" || Array.isArray(strippedPrior))
|
|
4245
4231
|
return strippedPrior;
|