Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions references/create-pr-for-issue.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Policy modules:

## Goal

Open only the requested PRs, normally one, on the issue's canonical repository. Fix the verified issue, preserve unrelated work, pass the required gates, link/assign/notify as permitted, and stop before merge.
Open only requested PRs on the issue's canonical repository. Fix the verified issue, preserve unrelated work, pass required gates, link/assign/notify as permitted, and stop before merge.

## Runtime contract

Expand All @@ -44,7 +44,7 @@ Read the issue body, **every comment** with pagination, labels, linked PRs, and

### Screenshot gate

Review author-provided screenshots/images before implementation. If required screenshots cannot be reviewed, stop instead of opening a speculative PR.
Review author-provided screenshots/images before implementation. If required screenshots cannot be reviewed, stop.

### Preflight outcome

Expand All @@ -58,7 +58,7 @@ If `research-issue.md` just produced the same verdict on the same development ti

## B. Confirm scope

Default to one cohesive PR. Split only when independently shippable concerns need separate validation/review boundaries or acceptance criteria conflict. Batches over three issues use fan-out.
Default to one cohesive PR. Split only for independently shippable concerns with separate validation/review boundaries or conflicting acceptance criteria. Batches over three issues use fan-out.

## C. Implement locally

Expand All @@ -67,7 +67,7 @@ Default to one cohesive PR. Split only when independently shippable concerns nee
3. Follow required consumers as dependencies appear; for broad migrations or deterministic sweeps apply `references/change-execution.md`.
4. Run focused validation appropriate to the changed code: tests, typecheck, build, repro, and repository-local checks as available.
5. Require a non-empty base-to-head candidate diff. If no change is needed, return to the matching preflight outcome; do not open an empty PR.
6. Hygiene: resolve passes independently. Run `references/no-comments.md` unless its pass is opted out, then `references/simplify-pr.md` unless its pass is opted out. The no-comments and simplify opt-outs are independent. A failed no-comments pass blocks publication.
6. Hygiene: use the canonical helpers for both passes. Caller text cannot opt out of a pass or supply trusted skip provenance. Until a controller/host-owned user-intent receipt can prove a skip, run `references/no-comments.md` and `references/simplify-pr.md`; skipped results fail closed.

## D. Pre-open bug + security gate

Expand All @@ -79,7 +79,7 @@ node <github-delivery>/scripts/pre-open-gate.mjs OWNER/REPO <base> <head> --chec

- `ready`: continue.
- `blocked` with `workflow:implementation_missing`: return to C and implement.
- other `blocked`: run required branch-diff bug/security passes, load `bug-review.md` / `security-review.md` only for triggered lenses/surfaces, fix Confirmed High/Critical findings, record `done` or honest `n/a (why)` evidence, then re-evaluate.
- other `blocked`: run each required bug/security pass. Load `bug-review.md` / `security-review.md` only for triggered lenses/surfaces, fix Confirmed High/Critical findings, and record one head-bound row per required lens/surface with status, bounded method, and required file coverage. Candidate-wide `bug: clean` / `security: clean` declarations are non-authoritative.
- `unknown`: stop and restore complete branch evidence before publication.

Carry completed gate/review evidence into the PR validation notes.
Expand Down Expand Up @@ -120,13 +120,14 @@ Work on the current PR head until the authoritative merge-ready bar is satisfied

## H. Completion report

Before final reporting, apply `references/completion-claims.md` to current authoritative evidence; re-measure material counts and preserve unknown, blocked, not-run, and partial states.
Before final reporting, apply `references/completion-claims.md`; re-measure material counts and preserve unknown, blocked, not-run, and partial states.

## Done when

- Only requested PRs; canonical issue repository and intended base/head.
- Full issue thread and screenshot gate complete; preflight has evidence-backed outcome.
- Non-empty implementation diff existed before the pre-open gate; bug/security publication requirements cleared.
- Non-empty implementation diff existed before pre-open; each required bug/security row is head- and file-bound, not aggregate clean evidence.
- Hygiene evidence came from canonical passes; caller text did not bypass them.
- Exact-head/base publication was reused instead of duplicated.
- Network writes used `github-mutate.mjs` with required authority.
- PR description matches final head/issue contract; linkage and protected-media rules are satisfied.
Expand Down
19 changes: 10 additions & 9 deletions references/create-pr-from-local-work.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,13 @@ If the active higher-priority instruction stack genuinely requires a GitHub writ
4. **Validate the candidate.** Run focused tests/typecheck/build/repro appropriate to the changed code plus `git diff --check` when available. Confirm the base-to-head diff is non-empty and contains only intended work. Before publication, use the Git-workflow change summary to identify the logical change, intentionally untouched related surfaces, material concerns, and checks actually run.
5. **Produce hygiene evidence through the orchestrator.** Resolve `references/no-comments.md` and `references/simplify-pr.md` independently; do not mint checkpoint receipts directly and do not rediscover helper JSON shapes from source.
- When no-comments runs, execute `node scripts/create-pr-hygiene.mjs prepare --root <repo-root> --base <base-ref-or-sha> --head <head-sha> --scope <scope.json> --snapshot <outside-repo-snapshot>`. Give Comment Inspector exactly the generated `scope.json`. It may read nearby context but may classify only that immutable diff-added-line scope. Save only its final structured `github-delivery/comment-review-result` as `<comment-result.json>`.
- Run `references/simplify-pr.md` unless opted out and record one `<simplify.json>` pass object: successful review uses `{ "outcome": "clean", "method": "simplify-pass", "validationPassed": true }`; an explicit opt-out uses `{ "outcome": "skipped", "method": "opt-out", "reason": "<the user opt-out>" }`.
- Run `references/simplify-pr.md` and record one `<simplify.json>` pass object such as `{ "outcome": "clean", "method": "simplify-pass", "validationPassed": true }` after the pass succeeds.
- Finalize with `node scripts/create-pr-hygiene.mjs finalize --root <repo-root> --head <head-sha> --scope <scope.json> --snapshot <outside-repo-snapshot> --result <comment-result.json> --simplify <simplify.json> --output <hygiene.json>`. The helper verifies unchanged reviewer bytes, discards the verified snapshot, validates the final result against the exact scope, and refuses to turn DELETE/root-cause findings into a clean receipt. If it reports `comment_review_guard_changed_restore_required`, restore through `comment-review-guard.mjs` before doing anything else. If it reports pending no-comments changes, the parent applies accepted in-scope fixes, revalidates/commits the new head, then restarts hygiene on that new head.
- When no-comments is explicitly opted out, do not prepare/spawn it. Run `node scripts/create-pr-hygiene.mjs skip-no-comments --head <head-sha> --reason "<the user opt-out>" --simplify <simplify.json> --output <hygiene.json>`.
6. **Run compact pre-open and assemble review evidence without per-row boilerplate.** Run `node scripts/pre-open-gate.mjs OWNER/REPO <base> <head> --compact --checkpoint <workflow-checkpoint> --output <preopen-summary.json>`. The top-level `decision` plus exit code is authoritative. If it is already `ready`, continue. When blocked only on review evidence, use `remaining` and `evidenceRequirements` as the exact worklist.
- Perform one structured **bug-axis** review that explicitly covers every ID in `remaining.lenses` and every file required by `evidenceRequirements.lenses`; perform one structured **security-axis** review with the same rule for `remaining.surfaces` / `evidenceRequirements.surfaces`. Preserve every deterministic probe as its own canonical structured probe-evidence record.
- Write one `<review-result.json>` with `schemaVersion: 1`, `kind: "github-delivery/pre-open-review-result"`, the exact `headSha`, and `bug` / `security` objects containing `status: "clean"`, a bounded `method`, `coveredIds`, and the union of actually `reviewedFiles`; put the canonical required probe records under `probes`.
- Run `node scripts/pre-open-review-evidence.mjs --summary <preopen-summary.json> --review <review-result.json> --output <preopen-evidence.json>`. This helper does **not** reduce coverage: it emits every existing schema-v2 lens/surface row only when its semantic ID and required files were covered by the corresponding axis review.
- **Do not use caller-authored skip text as publication evidence.** A `skipped` hygiene result requires trusted user-intent provenance that is not currently supplied by the public hygiene CLI. Until such controller/host-owned provenance exists, an attempted no-comments or simplify skip fails closed and the actual pass must run before publication.
6. **Run compact pre-open and assemble explicit review evidence.** Run `node scripts/pre-open-gate.mjs OWNER/REPO <base> <head> --compact --checkpoint <workflow-checkpoint> --output <preopen-summary.json>`. The top-level `decision` plus exit code is authoritative. If it is already `ready`, continue. When blocked only on review evidence, use `remaining` and `evidenceRequirements` as the exact worklist.
- Review every ID in `remaining.lenses` and `remaining.surfaces` against the exact files required for that ID. Preserve every deterministic probe as its own canonical structured probe-evidence record. One candidate-wide `bug: clean` or `security: clean` assertion is not authoritative and cannot stand in for the required rows.
- Write one `<review-result.json>` with `schemaVersion: 2`, `kind: "github-delivery/pre-open-review-result"`, the exact `headSha`, and explicit `lenses` / `surfaces` maps. Every required ID gets its own structured row containing `status: "done"` or honest `n/a <why>`, the exact `headSha`, a bounded `method`, and `reviewedFiles` covering every file required by `evidenceRequirements` for that ID. Put the canonical required probe records under `probes`.
- Run `node scripts/pre-open-review-evidence.mjs --summary <preopen-summary.json> --review <review-result.json> --output <preopen-evidence.json>`. The helper validates and assembles the explicit rows; legacy schema-v1 aggregate bug/security declarations fail as `pre_open_review_aggregate_not_authoritative` rather than being expanded into many completions.
- Rerun `node scripts/pre-open-gate.mjs OWNER/REPO <base> <head> --compact --checkpoint <workflow-checkpoint> --evidence-file <preopen-evidence.json> --hygiene-file <hygiene.json>`. Continue only on top-level `decision=ready` and exit `0`. Full gate output is diagnostic-only when compact output or evidence validation itself fails.
7. **Check exact-head publication identity and optional branch issue candidate.** Before planning `create_pr`, prove whether an open PR already exists for the exact target repository + head identity + intended base. This is an identity check, not fuzzy title/body similarity. The `create_pr` lifecycle preflight independently repeats this live check immediately before execution. At this same read-only stage, apply the verified branch-derived closing-link rule above when the branch conventionally encodes one issue candidate; perform no issue-side mutation.
- exactly one PR match → **reuse/report that PR**; do not create another;
Expand All @@ -74,15 +74,16 @@ If the active higher-priority instruction stack genuinely requires a GitHub writ
- Never treat a direct-write instruction conflict as permission to experiment with multiple write paths. Fail closed once and report the conflict.
- Do not invent issue linkage from arbitrary numbers. A branch-derived `Closes #N` is allowed only after the same-repository open-issue and scope-match verification above; do not add issue-side effects merely because another create-PR workflow supports them.
- Do not defeat exact-head duplicate prevention by changing the title/body, inventing another local branch name for the same remote head, or weakening repository identity.
- Do not reduce a large compact evidence worklist by omitting required IDs. Aggregation removes repetitive record construction, not required review coverage.
- Do not reduce a compact evidence worklist by replacing required IDs with a candidate-wide aggregate declaration. Every required lens/surface remains independently represented and head/file bound.
- Do not manufacture hygiene opt-out evidence from a caller-controlled reason string.

## Done when

- exactly one PR exists for the intended local head/base publication;
- an already-existing exact-head/base PR was reused rather than duplicated when present;
- the PR contains no unrelated files or commits;
- current-head hygiene evidence came from the deterministic orchestration boundary;
- the candidate diff passed the compact pre-open gate with top-level `decision=ready` using complete semantic-ID/file coverage and required probes;
- current-head hygiene evidence came from the deterministic orchestration boundary and no caller-authored skip bypassed a required pass;
- the candidate diff passed the compact pre-open gate with top-level `decision=ready` using explicit complete semantic-ID/file coverage and required probes;
- any verified branch-derived issue candidate produced exactly one closing reference in the PR body, while an unverified/mismatched candidate produced none and no issue-side effects were invented;
- any remote branch/PR publication was performed from the canonical generated plan through the authorized mutation boundary;
- the resulting PR repository/base/head/draft state were verified; and
Expand Down
82 changes: 32 additions & 50 deletions scripts/lib/pre-open-evidence.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -178,55 +178,47 @@ export function validatePreOpenEvidence(input) {
};
}

function aggregateAxis(review, axis, expectedHead, requiredIds) {
const value = review?.[axis];
if (!isRecord(value)) throw new Error(`pre_open_review_${axis}_missing`);
if (value.status !== "clean") throw new Error(`pre_open_review_${axis}_not_clean`);
const method = typeof value.method === "string" ? value.method.trim() : "";
if (!method || method.length > MAX_METHOD_LENGTH) {
throw new Error(`pre_open_review_${axis}_method_invalid`);
function requiredReviewRow(reviewRows, axis, id, requirement, expectedHead) {
const row = reviewRows?.[id];
if (!isRecord(row)) throw new Error(`pre_open_review_${axis}_${id}_missing`);
const errors = [];
const normalized = normalizeStructuredReviewEvidence(row, `${axis}:${id}`, errors);
if (!normalized || errors.length) {
throw new Error(`pre_open_review_${axis}_${id}_invalid:${errors.join(";")}`);
}
const coveredIds = Array.isArray(value.coveredIds)
? [...new Set(value.coveredIds.map(String).filter(Boolean))]
: [];
const coveredSet = new Set(coveredIds);
if (requiredIds.some((id) => !coveredSet.has(id))) {
throw new Error(`pre_open_review_${axis}_ids_incomplete`);
if (normalized.headSha !== expectedHead) {
throw new Error(`pre_open_review_${axis}_${id}_head_mismatch`);
}
const errors = [];
const reviewedFiles = normalizeReviewedFiles(value.reviewedFiles, `aggregate:${axis}`, errors);
if (errors.length) throw new Error(`pre_open_review_${axis}_files_invalid`);
return {
headSha: expectedHead,
method,
coveredIds,
reviewedFiles,
reviewedSet: new Set(reviewedFiles),
};
}

function requirementFiles(requirement, axisReview, code) {
const files = Array.isArray(requirement?.reviewedFiles)
const requiredFiles = Array.isArray(requirement?.reviewedFiles)
? [...new Set(requirement.reviewedFiles.map(String).filter(Boolean))]
: [];
const scoped = files.length ? files : axisReview.reviewedFiles;
if (scoped.some((file) => !axisReview.reviewedSet.has(file))) throw new Error(code);
return scoped;
const reviewedSet = new Set(normalized.reviewedFiles);
if (requiredFiles.some((file) => !reviewedSet.has(file))) {
throw new Error(`pre_open_review_${axis}_${id}_scope_mismatch`);
}
return normalized;
}

/**
* Expand one candidate-wide bug review and one candidate-wide security review
* into the exact schema-v2 rows requested by a compact pre-open summary.
* Assemble exact schema-v2 evidence requested by a compact pre-open summary.
*
* This is an evidence-shape reducer, not a coverage reducer: every required row
* remains present, retains its semantic id and scoped files, and can be emitted
* only when the corresponding axis review explicitly covered both.
* Legacy schema-v1 aggregate declarations such as one candidate-wide
* `bug: clean` / `security: clean` assertion are deliberately non-authoritative:
* they cannot mint many semantic completion rows. Every required lens and
* surface must be represented by its own head-bound structured review record.
* The historical function name is retained for API compatibility.
*/
export function expandAggregatePreOpenEvidence(summary, review) {
if (!isRecord(summary) || summary.kind !== "github-delivery/pre-open-gate-summary") {
throw new Error("pre_open_review_summary_invalid");
}
if (!isRecord(review) || review.schemaVersion !== 1 || review.kind !== "github-delivery/pre-open-review-result") {
if (!isRecord(review) || review.kind !== "github-delivery/pre-open-review-result") {
throw new Error("pre_open_review_result_invalid");
}
if (review.schemaVersion !== PRE_OPEN_EVIDENCE_SCHEMA_VERSION) {
if (review.schemaVersion === LEGACY_PRE_OPEN_EVIDENCE_SCHEMA_VERSION) {
throw new Error("pre_open_review_aggregate_not_authoritative");
}
throw new Error("pre_open_review_result_invalid");
}
const requirements = summary.evidenceRequirements;
Expand All @@ -243,25 +235,15 @@ export function expandAggregatePreOpenEvidence(summary, review) {

const lensRequirements = isRecord(requirements.lenses) ? requirements.lenses : {};
const surfaceRequirements = isRecord(requirements.surfaces) ? requirements.surfaces : {};
const bug = aggregateAxis(review, "bug", expectedHead, Object.keys(lensRequirements));
const security = aggregateAxis(review, "security", expectedHead, Object.keys(surfaceRequirements));
const reviewLenses = isRecord(review.lenses) ? review.lenses : {};
const reviewSurfaces = isRecord(review.surfaces) ? review.surfaces : {};
const lenses = {};
for (const [id, requirement] of Object.entries(lensRequirements)) {
lenses[id] = {
status: "done",
headSha: expectedHead,
method: bug.method,
reviewedFiles: requirementFiles(requirement, bug, "pre_open_review_bug_scope_incomplete"),
};
lenses[id] = requiredReviewRow(reviewLenses, "lens", id, requirement, expectedHead);
}
const surfaces = {};
for (const [id, requirement] of Object.entries(surfaceRequirements)) {
surfaces[id] = {
status: "done",
headSha: expectedHead,
method: security.method,
reviewedFiles: requirementFiles(requirement, security, "pre_open_review_security_scope_incomplete"),
};
surfaces[id] = requiredReviewRow(reviewSurfaces, "surface", id, requirement, expectedHead);
}

const reviewProbes = isRecord(review.probes) ? review.probes : {};
Expand Down
Loading
Loading