/** * Implemented change-set derivation for the streamlined verification flow. * * Given a task, find the work that implements it, first match wins: * * 1. a merged PR that cites the task — files read from its merge commit; * 2. the unmerged task branch, diffed against its merge-base with origin's * default branch; * 3. for research, spike and documentation tasks, the attached docs and the * decisions linked to the task. * * The result is an atom string for the EXISTING ADR-051 validators * (`parseEvidence` → `validateAtom` → `checkGateEvidenceMinimum` → * `checkTaskEvidenceContext`). Nothing here validates, records or writes: * every git and gh call is a read, and the PR lookup runs with * `readOnly: true` so not even the PR cache is written. * * Every git and gh call runs in one resolved execution root: a declared * `evidence.gitRoot` first, then the caller's own worktree of this project, * then — when invoked from the main checkout — the task's registered * worktree, then the store root (or its single child checkout). * * Paths are repo-relative, which is the portable form the `files:` atom * stores; deleted paths are reported separately and never enter `files:`, * because a deleted file has no bytes to hash. * * Spec: `verify-streamlined-design` §3.2 step 2 and §6. * * @task T12624 */ import type { ChangeSetRootSource, Task, TaskChangeSet } from '@cleocode/contracts'; import { resolveOriginDefault } from '../git/base-ref.js'; import type { PrAtomResolution } from '../release/pr-evidence.js'; import { type ComponentLandingDeps } from './component-pr.js'; import { type EvidenceRoots } from './evidence.js'; /** Task fields change-set derivation reads. */ export type ChangeSetTask = Pick & { verification?: Task['verification']; }; /** A merged PR as returned by discovery, before verification. */ export interface MergedPrSummary { /** PR number. */ number: number; /** PR title. */ title: string; /** PR body. */ body: string; /** Head branch. */ headRefName: string; /** Merge time, when discovery reports it (orders a revert after its original). */ mergedAt?: string | null; } /** A merged PR's head branch and merge commit (T14001). */ export interface MergedPrHead { /** PR number. */ number: number; /** Head branch. */ headRefName: string; /** Merge commit, or null when GitHub reported none. */ mergeCommitSha: string | null; } /** One PR as `gh pr view` reports it: where it merged, and its commits. */ export interface PrDetails { /** PR number. */ number: number; /** PR title. */ title: string; /** Head branch. */ headRefName: string; /** Branch the PR merged (or will merge) into. */ baseRefName: string; /** `MERGED`, `OPEN` or `CLOSED`. */ state: string; /** Merge time, or null when unmerged. */ mergedAt: string | null; /** Head branch tip the PR carried. */ headRefOid: string | null; /** Merge commit, or null when unmerged. */ mergeCommitSha: string | null; /** The PR's commits, oldest first, when known (T12710). */ commits?: string[]; } /** A document attached to a task, as the docs read model reports it. */ export interface TaskDocRef { /** Attachment or blob identifier. */ id: string; /** Stable slug, when present. */ slug: string | null; /** Content sha256 — locates the stored bytes. */ sha256: string; } /** * Injectable I/O for {@link deriveTaskChangeSet}. Every member defaults to the * real implementation; tests replace the `gh` and store readers. */ export interface ChangeSetDeps { /** Merged PRs whose title, body or head branch may cite the task. */ listMergedPrs?: (taskId: string, executionRoot: string) => Promise<{ ok: true; prs: MergedPrSummary[]; } | { ok: false; reason: string; }>; /** One PR's base branch, state and commits (`gh pr view`); null when unknown. */ viewPr?: (prNumber: number, executionRoot: string) => Promise; /** * The most recent merged PRs with their head branch and merge commit * (T14001): what {@link taskOwnWorkPaths} matches branch names against, * which a `--search` of titles and bodies cannot. Null when `gh` failed. */ listMergedPrHeads?: (executionRoot: string) => Promise; /** * PRs GitHub associates with a commit, whatever they cite (T12959): empty * when GitHub has none (or does not know the commit), null when the lookup * failed. */ listPrsForCommit?: (sha: string, executionRoot: string) => Promise; /** * The repository's default branch from `gh`, asked only when origin has no * default ref (T12959 review); null when unknown. */ defaultBranch?: (executionRoot: string) => string | null; /** The PR whose head is `branch` — merged first, else newest; null when none. */ findPrByHead?: (branch: string, executionRoot: string) => Promise; /** Verify one PR through the existing `pr:` provenance code. */ resolvePr?: (prNumber: number, roots: EvidenceRoots) => Promise; /** Documents attached to the task. */ listTaskDocs?: (storeRoot: string, taskId: string) => Promise; /** Accepted or proposed decisions linked to the task. */ listTaskDecisions?: (storeRoot: string, taskId: string) => Promise; /** `gh` lookups that follow a component PR to its integration PR (T12710). */ landing?: ComponentLandingDeps; /** Environment for declared git-root resolution. */ env?: NodeJS.ProcessEnv; } /** Input to {@link deriveTaskChangeSet}. */ export interface DeriveChangeSetInput { /** The task whose work is being located. */ task: ChangeSetTask; /** CLEO store root. */ storeRoot: string; /** Directory the command was invoked from. Defaults to `process.cwd()`. */ cwd?: string; /** Explicit PR (`--pr `); replaces candidate selection (discovery still runs, to find reverts). */ prNumber?: number; /** * IDs of the task's children that are done (T13785). A merged PR that cites * the task AND one of them is part of the task's own work split across its * children, so several such PRs are not ambiguous. */ doneChildIds?: readonly string[] /** * Explicit commit (`--commit `, T13799): work that landed on the default * branch with no PR or branch citing the task. Verified, never trusted. */; commit?: string; } /** The files a merged change is compared on (a merge commit, its files and root). */ type CarryTarget = Pick; /** * The part of a carrier's change that is this task's, for the scoped-run * carry check (T13655): the files the task's own commits changed (read from * git, first-parent for a merge commit) plus its declared files, within the * carrier's files. Its own commits are the recorded implemented commits and * every carrier commit that belongs to it (T13662: the whole branch when the * branch is the task's, else the commits citing it), so recording one * test-only commit never narrows a task below its own migration. With no own * commit resolvable here, the whole carrier counts. A single-task PR therefore * stays whole, a shared or parent PR counts only what this task changed, and * `cleo done --plan` and `cleo complete` both judge through this one rule. * * @param target - The carrier's merge commit, files and root, or null. * @param task - The task (its id and declared files). * @param commits - The task's recorded implemented commit SHAs. * @returns The target with the task's files, or the target unchanged. * @task T13655 * @task T13662 */ export declare function taskCarryScope(target: T | null, task: Pick, commits: readonly string[]): T | null; /** * Resolve the repository a task's change set is read from: the shared * evidence tree ({@link resolveEvidenceTree}, T14058), which typed gates run * in too. * * Order: declared git root — the invocation's linked worktree of it, a * declared linked worktree, the task's worktree of it (T13926), the declared * tree — else the invocation's own worktree of this project → the task's * registered worktree → the store root or its single child checkout, as * `resolveEvidenceExecutionRoot` already decides. * * @param storeRoot - CLEO store root. * @param taskId - Task whose registered worktree to look for. * @param cwd - Invocation directory. * @param env - Environment for the declared-root tier. * @returns The execution root and why it was chosen. * @task T12624 * @task T14058 */ export declare function resolveChangeSetRoot(storeRoot: string, taskId: string, cwd?: string, // CWD-OK: the invocation dir is the subject (gh#1220) env?: NodeJS.ProcessEnv): { root: string; source: ChangeSetRootSource; }; export { resolveOriginDefault }; /** * Whether `sha` has landed on origin's default branch in `root`. * * @param root - Repository to ask. * @param sha - Commit to test. * @returns True when it is an ancestor of `origin/`; false when it is * not, or when no default branch or the commit is unknown here. * @task T12960 */ export declare function isLandedOnOriginDefault(root: string, sha: string): boolean; /** * Whether `ancestor` is contained in `descendant` in `root`. * * @param root - Repository to ask. * @param ancestor - Commit that should be contained. * @param descendant - Commit or ref that should contain it. * @returns True when it is; false when it is not or either is unknown here. * @task T12960 */ export declare function isAncestorCommit(root: string, ancestor: string, descendant: string): boolean; /** Why {@link hasPatchEquivalent} could not decide: commits git does not have here. */ export interface PatchEquivalenceUnknown { /** Commits missing from the local object store (`git fetch` brings them). */ missing: string[]; } /** * Whether `sha` has a patch-equivalent (`git patch-id --stable`) among * `candidates` — a commit rebased or cherry-picked before it merged keeps its * patch but not its SHA (T12959 review). The candidates present locally are * compared first, so one missing candidate does not hide a match among the * others. * * @param root - Repository to ask. * @param sha - The recorded commit. * @param candidates - Commits that may carry its patch (a PR's own commits). * @returns True on a match; false when every candidate was compared and none * matched; `{ missing }` when no present candidate matched and `sha` or * some candidate is not in the local object store; null when git failed. * @task T12959 */ export declare function hasPatchEquivalent(root: string, sha: string, candidates: readonly string[]): boolean | null | PatchEquivalenceUnknown; /** * Deadline for one read-only `gh` query (T12656 review): discovery sits on the * `cleo complete` path, so a hung `gh` must fail — as an unknown merge state — * rather than hang the completion. One value for every evidence `gh` call. */ export { GH_QUERY_TIMEOUT_MS } from '../release/github-pr.js'; /** * Default merged-PR discovery: `gh pr list` by search text and by task branch. * * @param taskId - Task whose PRs to find. * @param executionRoot - Repository `gh` runs in. * @param opts - `timeoutMs` per query (default `ghQueryTimeoutMs()`). * @returns The merged PRs, or why discovery failed (a timeout included). * @task T12624 */ export declare function defaultListMergedPrs(taskId: string, executionRoot: string, opts?: { timeoutMs?: number; }): Promise<{ ok: true; prs: MergedPrSummary[]; } | { ok: false; reason: string; }>; /** * Default {@link ChangeSetDeps.listMergedPrHeads}: the newest * {@link MERGED_PR_HEADS_LIMIT} merged PRs (`gh pr list --json headRefName`). * * @param executionRoot - Repository `gh` runs in. * @returns The PRs, or null when `gh` failed. * @task T14001 */ export declare function defaultListMergedPrHeads(executionRoot: string): Promise; /** * Default {@link ChangeSetDeps.viewPr}: `gh pr view `. * * @param prNumber - PR to view. * @param executionRoot - Repository `gh` runs in. * @returns The PR's base, state, merge commit and commits, or null when `gh` failed. * @task T12624 */ export declare function defaultViewPr(prNumber: number, executionRoot: string): Promise; /** A PR GitHub associates with a commit. */ export interface CommitPr { /** PR number. */ number: number; /** Merge time, or null when the PR has not merged. */ mergedAt: string | null; /** Branch the PR merged (or will merge) into. */ baseRefName: string; /** The PR's merge commit, when GitHub reports one. */ mergeCommitSha?: string | null; } /** * The PRs GitHub associates with a commit * (`GET /repos/{owner}/{repo}/commits//pulls`): the merged PR that * introduced it to the default branch, or — for a commit a squash or rebase * merge rewrote — the PRs it was pushed in, whatever they cite (T12959 * review). Candidates only: a caller checks that a PR carries the commit. * Read-only. * * @param sha - The commit. * @param executionRoot - Repository `gh` runs in. * @returns The PRs; empty when GitHub has none, or does not know the commit * (never pushed: HTTP 422); null when the lookup failed (`gh` missing, * unauthenticated, unreachable or timed out), which is not "no PRs". * @task T12959 */ export declare function defaultListPrsForCommit(sha: string, executionRoot: string): Promise; /** What {@link taskOwnWorkPaths} found, or why it could not know. */ export type TaskOwnWork = { ok: true; paths: string[]; } | { ok: false; reason: string; }; /** * Every path a task's own work changed, whatever its evidence cites (T14001, * reviews of #2413), found the way the planner finds a change set: * * 1. the checked-out branch citing the task (step 2 of * {@link deriveTaskChangeSet}), with its uncommitted tracked edits; * 2. every other local or `origin/` branch whose name cites the task * (`task/`, `feat/`, `fix/`, …), diffed from its merge-base with origin's * default branch; * 3. first-parent commits on origin's default branch whose subject cites the * task, case-insensitively: merge commits of its branches, squash merges * (`feat(T…): … (#n)`) and direct commits, each read first-parent; * 4. for a GitHub origin, recent merged PRs whose head branch cites the task * ({@link defaultListMergedPrHeads}), read at their merge commits: a squash * whose title does not cite it. * * It fails closed: when nothing was found but the search could not finish * (no origin default branch, or the GitHub lookup failed), or a commit git * cannot read, the answer is not ok, with the reason. A store outside git has * no history to search and answers no paths. An answer is reused for a * minute in the same process while HEAD stays put (T14006). * * @param storeRoot - CLEO store root. * @param taskId - Task whose work to list. * @param cwd - Invocation directory, which locates the task's worktree. * @param deps - Merged-PR discovery (tests inject; defaults to `gh`). * @returns The paths, or why they are unknown. * @task T14001 */ export declare function taskOwnWorkPaths(storeRoot: string, taskId: string, cwd?: string, deps?: Pick): Promise; /** * Forget every cached {@link taskOwnWorkPaths} answer (tests). * * @task T14006 */ export declare function resetTaskOwnWorkCache(): void; /** * Derive the implemented change set for a task. Read-only. * * @param input - Task, store root, invocation directory and optional `--pr`. * @param deps - Injectable I/O; defaults to git, gh and the CLEO stores. * @returns The change set, its provenance, the planned `implemented` atoms and * any blockers. `source: 'none'` carries one `no-change-set` blocker. * @example * ```ts * const cs = await deriveTaskChangeSet({ task, storeRoot: getProjectRoot() }); * if (cs.implementedEvidence) console.log(cs.implementedEvidence); * ``` * @task T12624 */ export declare function deriveTaskChangeSet(input: DeriveChangeSetInput, deps?: ChangeSetDeps): Promise; //# sourceMappingURL=change-set.d.ts.map