@osdlc/repo-cleaner 0.17.2 → 0.19.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 +31 -0
- package/README.md +691 -305
- package/dist/branches.d.ts +37 -36
- package/dist/branches.js +199 -196
- package/dist/clean.d.ts +179 -0
- package/dist/clean.js +824 -0
- package/dist/cli.d.ts +12 -16
- package/dist/cli.js +367 -478
- package/dist/hooks.d.ts +8 -12
- package/dist/hooks.js +2 -2
- package/dist/index.d.ts +18 -25
- package/dist/index.js +16 -23
- package/dist/landing.d.ts +3 -3
- package/dist/landing.js +7 -7
- package/dist/plan.d.ts +76 -70
- package/dist/plan.js +82 -77
- package/dist/providers/github.d.ts +2 -1
- package/dist/providers/github.js +2 -1
- package/dist/providers/gitlab.d.ts +3 -2
- package/dist/providers/gitlab.js +14 -8
- package/dist/providers/index.d.ts +5 -4
- package/dist/providers/index.js +8 -5
- package/dist/providers/types.d.ts +8 -2
- package/dist/prs.d.ts +35 -7
- package/dist/prs.js +45 -12
- package/dist/receipt.d.ts +46 -57
- package/dist/receipt.js +52 -28
- package/dist/reference.d.ts +12 -0
- package/dist/reference.js +152 -0
- package/dist/remote-mirror.d.ts +9 -1
- package/dist/remote-mirror.js +19 -1
- package/dist/render.d.ts +66 -61
- package/dist/render.js +206 -255
- package/dist/resolve.d.ts +3 -3
- package/dist/resolve.js +4 -12
- package/dist/status-line.d.ts +1 -1
- package/dist/status-line.js +5 -5
- package/dist/step-board.d.ts +1 -1
- package/dist/step-board.js +2 -5
- package/dist/submodules.d.ts +120 -0
- package/dist/submodules.js +292 -0
- package/dist/sweep.d.ts +107 -64
- package/dist/sweep.js +333 -119
- package/dist/types.d.ts +294 -105
- package/dist/types.js +106 -54
- package/dist/util.d.ts +13 -0
- package/dist/util.js +36 -0
- package/dist/version.generated.d.ts +1 -1
- package/dist/version.generated.js +1 -1
- package/dist/worktrees.d.ts +21 -37
- package/dist/worktrees.js +198 -165
- package/package.json +5 -5
- package/dist/apply.d.ts +0 -131
- package/dist/apply.js +0 -541
package/dist/hooks.d.ts
CHANGED
|
@@ -1,18 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* What a host may supply that this package cannot know for itself.
|
|
3
3
|
*
|
|
4
|
-
* A standalone run passes none; a host passes `annotate` so its verdicts
|
|
5
|
-
*
|
|
4
|
+
* A standalone run passes none; a host passes `annotate` so its verdicts are
|
|
5
|
+
* in the sweep, the plan, and the re-check `check` and `run` make.
|
|
6
6
|
*/
|
|
7
|
-
import type { RepoInfo,
|
|
7
|
+
import type { RepoInfo, Row } from './types.ts';
|
|
8
8
|
/**
|
|
9
|
-
* Re-rank a repository's swept rows with what the host knows
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* too.
|
|
9
|
+
* Re-rank a repository's swept rows with what the host knows: change a
|
|
10
|
+
* verdict, add a note, drop a row. Runs after classification, on its output;
|
|
11
|
+
* it never reaches into it. A worktree the host holds back keeps its branch
|
|
12
|
+
* too: the sweep applies that rule after the hook.
|
|
14
13
|
*/
|
|
15
|
-
export type Annotate = (repo: RepoInfo,
|
|
16
|
-
export interface HostHooks {
|
|
17
|
-
annotate?: Annotate | undefined;
|
|
18
|
-
}
|
|
14
|
+
export type Annotate = (repo: RepoInfo, rows: Row[]) => Row[];
|
package/dist/hooks.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* What a host may supply that this package cannot know for itself.
|
|
3
3
|
*
|
|
4
|
-
* A standalone run passes none; a host passes `annotate` so its verdicts
|
|
5
|
-
*
|
|
4
|
+
* A standalone run passes none; a host passes `annotate` so its verdicts are
|
|
5
|
+
* in the sweep, the plan, and the re-check `check` and `run` make.
|
|
6
6
|
*/
|
|
7
7
|
export {};
|
package/dist/index.d.ts
CHANGED
|
@@ -2,30 +2,23 @@
|
|
|
2
2
|
* `@osdlc/repo-cleaner` — what has been merged into the default branch, and
|
|
3
3
|
* what can go away.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
5
|
+
* `sweep` inventories worktrees, local branches and remote branches across
|
|
6
|
+
* one repository or many, and judges each row: `delete`, `review`, `keep` or
|
|
7
|
+
* `blocked`, with the risks its deletion carries and the evidence behind it.
|
|
8
|
+
* The sweep deletes nothing. `plan` saves the rows and their evidence;
|
|
9
|
+
* `check` resolves a person's approval against a plan and shows the steps a
|
|
10
|
+
* run would take; `run` takes them, compare-and-swap, verifying each.
|
|
10
11
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* rather than reaching into classification, which is why every row carries
|
|
15
|
-
* its facts and why `investigationParts` is a list rather than a sentence.
|
|
12
|
+
* A host that knows more than git does (an issue tracker, say) passes
|
|
13
|
+
* `annotate` to re-rank the rows, rather than reaching into classification,
|
|
14
|
+
* which is why every row carries its evidence and why `notes` is a list.
|
|
16
15
|
*/
|
|
17
|
-
export {
|
|
18
|
-
export {
|
|
19
|
-
export {
|
|
20
|
-
export {
|
|
21
|
-
export {
|
|
22
|
-
export
|
|
23
|
-
export {
|
|
24
|
-
export
|
|
25
|
-
export {
|
|
26
|
-
export { fetchPrs } from './prs.ts';
|
|
27
|
-
export { PROVIDER_CHOICES, PROVIDER_NAMES, chooseProvider, forgeTarget, githubProvider, gitlabProvider, probeProvider, providers, type ForgeTarget, type PrProvider, type ProviderChoice, type ProviderName, type ProviderStatus, } from './providers/index.ts';
|
|
28
|
-
export { expandTilde, isCheckoutRoot, resolveEntries, type Resolution, type ResolveOptions, } from './resolve.ts';
|
|
29
|
-
export { holdFreedBranches, sweepRepo, sweepRepos, type SweepOptions, type SweepReposOptions, } from './sweep.ts';
|
|
30
|
-
export { proposedCommands, renderJson, renderApplySummary, renderReceipt, summarizeReceipt, type ApplySummary, renderRepo, renderScan, renderSummary, renderText, requiresNote, } from './render.ts';
|
|
31
|
-
export { ALL_SCOPES, AUTHORIZATIONS, authorizationsFor, emptyBucket, emptyScan, findDuplicateCheckouts, isUnfetched, makeItem, summarize, type Authorization, type Category, type Classification, type DuplicateCheckout, type PRInfo, type RepoInfo, type RepoSweep, type ScanStats, type Scope, type SummaryBucket, type SweepItem, type SweepProgress, type SweepResult, type UnresolvedEntry, } from './types.ts';
|
|
16
|
+
export { sweep, inspectWorktree, type Runners, type SweepOptions } from './sweep.ts';
|
|
17
|
+
export { PLAN_VERSION, plan, readPlan, writePlan, type Plan, type PlanRepo, type PlanRow, } from './plan.ts';
|
|
18
|
+
export { check, run, type Allow, type Approval, type CheckOptions, type Preview, type ResolvedApproval, type Resolved, type RunOptions, } from './clean.ts';
|
|
19
|
+
export { RECEIPT_VERSION, ReceiptSchema, type DropReason, type Dropped, type Receipt, type Step, } from './receipt.ts';
|
|
20
|
+
export { render, type RunSummary, type Style } from './render.ts';
|
|
21
|
+
export { defaultProcessProbe, type ProcessHolder, type ProcessProbe } from './processes.ts';
|
|
22
|
+
export { providers, type PrProvider, type ProviderChoice } from './providers/index.ts';
|
|
23
|
+
export type { Annotate } from './hooks.ts';
|
|
24
|
+
export { KINDS, RISKS, RepoCleanerError, type Action, type Blocker, type BranchEvidence, type BranchRow, type Bucket, type Counts, type ErrorCode, type Kind, type LandingEvidence, type PrEvidence, type RemoteEvidence, type RemoteRow, type RepoInfo, type RepoNote, type RepoSweep, type Risk, type Row, type RowId, type SubmoduleEvidence, type Sweep, type SweepProgress, type SweptWith, type Verdict, type WorktreeEvidence, type WorktreeRow, } from './types.ts';
|
package/dist/index.js
CHANGED
|
@@ -2,29 +2,22 @@
|
|
|
2
2
|
* `@osdlc/repo-cleaner` — what has been merged into the default branch, and
|
|
3
3
|
* what can go away.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
5
|
+
* `sweep` inventories worktrees, local branches and remote branches across
|
|
6
|
+
* one repository or many, and judges each row: `delete`, `review`, `keep` or
|
|
7
|
+
* `blocked`, with the risks its deletion carries and the evidence behind it.
|
|
8
|
+
* The sweep deletes nothing. `plan` saves the rows and their evidence;
|
|
9
|
+
* `check` resolves a person's approval against a plan and shows the steps a
|
|
10
|
+
* run would take; `run` takes them, compare-and-swap, verifying each.
|
|
10
11
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* rather than reaching into classification, which is why every row carries
|
|
15
|
-
* its facts and why `investigationParts` is a list rather than a sentence.
|
|
12
|
+
* A host that knows more than git does (an issue tracker, say) passes
|
|
13
|
+
* `annotate` to re-rank the rows, rather than reaching into classification,
|
|
14
|
+
* which is why every row carries its evidence and why `notes` is a list.
|
|
16
15
|
*/
|
|
17
|
-
export {
|
|
18
|
-
export {
|
|
19
|
-
export {
|
|
20
|
-
export { assessLanding, patchEquivalent } from "./landing.js";
|
|
21
|
-
export { ApplyArgumentError, ApplyRefused, applyPlan, parseGrant, parseRowRef, } from "./apply.js";
|
|
22
|
-
export { PLAN_VERSION, PlanReadError, planRepo, parsePlan, readPlan, writePlan, } from "./plan.js";
|
|
16
|
+
export { sweep, inspectWorktree } from "./sweep.js";
|
|
17
|
+
export { PLAN_VERSION, plan, readPlan, writePlan, } from "./plan.js";
|
|
18
|
+
export { check, run, } from "./clean.js";
|
|
23
19
|
export { RECEIPT_VERSION, ReceiptSchema, } from "./receipt.js";
|
|
24
|
-
export {
|
|
25
|
-
export {
|
|
26
|
-
export {
|
|
27
|
-
export {
|
|
28
|
-
export { holdFreedBranches, sweepRepo, sweepRepos, } from "./sweep.js";
|
|
29
|
-
export { proposedCommands, renderJson, renderApplySummary, renderReceipt, summarizeReceipt, renderRepo, renderScan, renderSummary, renderText, requiresNote, } from "./render.js";
|
|
30
|
-
export { ALL_SCOPES, AUTHORIZATIONS, authorizationsFor, emptyBucket, emptyScan, findDuplicateCheckouts, isUnfetched, makeItem, summarize, } from "./types.js";
|
|
20
|
+
export { render } from "./render.js";
|
|
21
|
+
export { defaultProcessProbe } from "./processes.js";
|
|
22
|
+
export { providers } from "./providers/index.js";
|
|
23
|
+
export { KINDS, RISKS, RepoCleanerError, } from "./types.js";
|
package/dist/landing.d.ts
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
* and a patch that is empty all end the same way: no evidence.
|
|
25
25
|
*/
|
|
26
26
|
import type { Git } from '@osdlc/typed-git';
|
|
27
|
-
import { type PRInfo, type PrEvidenceGap, type RepoInfo } from './types.ts';
|
|
27
|
+
import { type LandingEvidence, type PRInfo, type PrEvidenceGap, type RepoInfo } from './types.ts';
|
|
28
28
|
/** Which check showed that the work is on the landed ref. */
|
|
29
29
|
export type LandingCheck =
|
|
30
30
|
/** Every commit on the branch has a patch-equivalent commit on the landed ref (rebase merge, cherry-pick). */
|
|
@@ -77,8 +77,8 @@ export declare function judgeLanding(repo: RepoInfo, tip: string, pr: PRInfo | u
|
|
|
77
77
|
export declare function landingConfirmed(landing: Landing | null): boolean;
|
|
78
78
|
/** {@link assessLanding}, reduced to the check that passed, or null. */
|
|
79
79
|
export declare function patchEquivalent(repo: RepoInfo, tip: string, git: Git, prHead?: string | null): LandingCheck | null;
|
|
80
|
-
/** Write the landing evidence into a row's
|
|
81
|
-
export declare function recordLanding(
|
|
80
|
+
/** Write the landing evidence into a row's evidence. */
|
|
81
|
+
export declare function recordLanding(evidence: LandingEvidence, landing: Landing): void;
|
|
82
82
|
/**
|
|
83
83
|
* The part of a row's reason that says whether the content check agreed, for a
|
|
84
84
|
* patch-equivalent verdict.
|
package/dist/landing.js
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
* shallow clone, a missing merge base, a commit that is not on the landed ref
|
|
24
24
|
* and a patch that is empty all end the same way: no evidence.
|
|
25
25
|
*/
|
|
26
|
-
import { landedRef } from "./types.js";
|
|
26
|
+
import { landedRef, } from "./types.js";
|
|
27
27
|
/** Cap on {@link Landing} `unmatched`. */
|
|
28
28
|
const UNMATCHED_SHOWN = 10;
|
|
29
29
|
/**
|
|
@@ -237,15 +237,15 @@ export function landingConfirmed(landing) {
|
|
|
237
237
|
export function patchEquivalent(repo, tip, git, prHead = null) {
|
|
238
238
|
return assessLanding(repo, tip, git, prHead).check;
|
|
239
239
|
}
|
|
240
|
-
/** Write the landing evidence into a row's
|
|
241
|
-
export function recordLanding(
|
|
240
|
+
/** Write the landing evidence into a row's evidence. */
|
|
241
|
+
export function recordLanding(evidence, landing) {
|
|
242
242
|
if (landing.check !== null) {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
243
|
+
evidence.landing = 'patch-equivalent';
|
|
244
|
+
evidence.landingCheck = landing.check;
|
|
245
|
+
evidence.landingContentConfirmed = landing.confirmed;
|
|
246
246
|
}
|
|
247
247
|
else if (landing.unmatched.length > 0) {
|
|
248
|
-
|
|
248
|
+
evidence.unmatchedCommits = landing.unmatched;
|
|
249
249
|
}
|
|
250
250
|
}
|
|
251
251
|
/**
|
package/dist/plan.d.ts
CHANGED
|
@@ -1,23 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The
|
|
3
|
-
*
|
|
4
|
-
* whose fingerprint
|
|
2
|
+
* The plan: a sweep's deletable rows, each with a fingerprint of the evidence
|
|
3
|
+
* its verdict rests on, and the options the sweep ran with. `check` sweeps
|
|
4
|
+
* again with the same options and drops any row whose fingerprint moved.
|
|
5
5
|
*/
|
|
6
6
|
import { z } from 'zod';
|
|
7
|
-
import { type
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
export declare const PLAN_CATEGORIES: readonly ["worktree", "local_branch", "remote_branch"];
|
|
12
|
-
export type PlanCategory = (typeof PLAN_CATEGORIES)[number];
|
|
7
|
+
import { type Row, type Sweep } from './types.ts';
|
|
8
|
+
export declare const PLAN_VERSION = 2;
|
|
9
|
+
/** A row id, `<kind>:<name>` with a name; shared by the plan and the receipt. */
|
|
10
|
+
export declare const RowIdSchema: z.ZodCustom<`worktree:${string}` | `branch:${string}` | `remote:${string}`, `worktree:${string}` | `branch:${string}` | `remote:${string}`>;
|
|
13
11
|
export declare const PlanRowSchema: z.ZodObject<{
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
id: z.ZodCustom<`worktree:${string}` | `branch:${string}` | `remote:${string}`, `worktree:${string}` | `branch:${string}` | `remote:${string}`>;
|
|
13
|
+
verdict: z.ZodEnum<{
|
|
14
|
+
delete: "delete";
|
|
15
|
+
review: "review";
|
|
16
|
+
keep: "keep";
|
|
17
|
+
blocked: "blocked";
|
|
18
18
|
}>;
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
action: z.ZodNullable<z.ZodEnum<{
|
|
20
|
+
delete: "delete";
|
|
21
|
+
remove: "remove";
|
|
22
|
+
prune: "prune";
|
|
23
|
+
}>>;
|
|
24
|
+
risks: z.ZodArray<z.ZodEnum<{
|
|
21
25
|
"abandon-unmerged": "abandon-unmerged";
|
|
22
26
|
"discard-changes": "discard-changes";
|
|
23
27
|
"confirm-open-pr": "confirm-open-pr";
|
|
@@ -27,24 +31,21 @@ export declare const PlanRowSchema: z.ZodObject<{
|
|
|
27
31
|
export type PlanRow = z.infer<typeof PlanRowSchema>;
|
|
28
32
|
export declare const PlanRepoSchema: z.ZodObject<{
|
|
29
33
|
root: z.ZodString;
|
|
30
|
-
|
|
31
|
-
no_gh: z.ZodBoolean;
|
|
32
|
-
provider: z.ZodOptional<z.ZodEnum<{
|
|
33
|
-
github: "github";
|
|
34
|
-
gitlab: "gitlab";
|
|
35
|
-
auto: "auto";
|
|
36
|
-
none: "none";
|
|
37
|
-
}>>;
|
|
38
|
-
branch: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
39
|
-
fetch: z.ZodDefault<z.ZodBoolean>;
|
|
34
|
+
fetched: z.ZodBoolean;
|
|
40
35
|
rows: z.ZodArray<z.ZodObject<{
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
36
|
+
id: z.ZodCustom<`worktree:${string}` | `branch:${string}` | `remote:${string}`, `worktree:${string}` | `branch:${string}` | `remote:${string}`>;
|
|
37
|
+
verdict: z.ZodEnum<{
|
|
38
|
+
delete: "delete";
|
|
39
|
+
review: "review";
|
|
40
|
+
keep: "keep";
|
|
41
|
+
blocked: "blocked";
|
|
45
42
|
}>;
|
|
46
|
-
|
|
47
|
-
|
|
43
|
+
action: z.ZodNullable<z.ZodEnum<{
|
|
44
|
+
delete: "delete";
|
|
45
|
+
remove: "remove";
|
|
46
|
+
prune: "prune";
|
|
47
|
+
}>>;
|
|
48
|
+
risks: z.ZodArray<z.ZodEnum<{
|
|
48
49
|
"abandon-unmerged": "abandon-unmerged";
|
|
49
50
|
"discard-changes": "discard-changes";
|
|
50
51
|
"confirm-open-pr": "confirm-open-pr";
|
|
@@ -54,28 +55,42 @@ export declare const PlanRepoSchema: z.ZodObject<{
|
|
|
54
55
|
}, z.core.$strip>;
|
|
55
56
|
export type PlanRepo = z.infer<typeof PlanRepoSchema>;
|
|
56
57
|
export declare const PlanSchema: z.ZodObject<{
|
|
57
|
-
version: z.ZodLiteral<
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
58
|
+
version: z.ZodLiteral<2>;
|
|
59
|
+
createdAt: z.ZodString;
|
|
60
|
+
options: z.ZodObject<{
|
|
61
|
+
only: z.ZodArray<z.ZodEnum<{
|
|
62
|
+
worktree: "worktree";
|
|
63
|
+
branch: "branch";
|
|
64
|
+
remote: "remote";
|
|
65
|
+
}>>;
|
|
66
|
+
remote: z.ZodBoolean;
|
|
67
|
+
offline: z.ZodBoolean;
|
|
68
|
+
against: z.ZodNullable<z.ZodString>;
|
|
69
|
+
provider: z.ZodEnum<{
|
|
64
70
|
github: "github";
|
|
65
71
|
gitlab: "gitlab";
|
|
66
72
|
auto: "auto";
|
|
67
73
|
none: "none";
|
|
68
|
-
}
|
|
69
|
-
branch: z.
|
|
70
|
-
|
|
74
|
+
}>;
|
|
75
|
+
branch: z.ZodNullable<z.ZodString>;
|
|
76
|
+
}, z.core.$strip>;
|
|
77
|
+
repos: z.ZodArray<z.ZodObject<{
|
|
78
|
+
root: z.ZodString;
|
|
79
|
+
fetched: z.ZodBoolean;
|
|
71
80
|
rows: z.ZodArray<z.ZodObject<{
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
81
|
+
id: z.ZodCustom<`worktree:${string}` | `branch:${string}` | `remote:${string}`, `worktree:${string}` | `branch:${string}` | `remote:${string}`>;
|
|
82
|
+
verdict: z.ZodEnum<{
|
|
83
|
+
delete: "delete";
|
|
84
|
+
review: "review";
|
|
85
|
+
keep: "keep";
|
|
86
|
+
blocked: "blocked";
|
|
76
87
|
}>;
|
|
77
|
-
|
|
78
|
-
|
|
88
|
+
action: z.ZodNullable<z.ZodEnum<{
|
|
89
|
+
delete: "delete";
|
|
90
|
+
remove: "remove";
|
|
91
|
+
prune: "prune";
|
|
92
|
+
}>>;
|
|
93
|
+
risks: z.ZodArray<z.ZodEnum<{
|
|
79
94
|
"abandon-unmerged": "abandon-unmerged";
|
|
80
95
|
"discard-changes": "discard-changes";
|
|
81
96
|
"confirm-open-pr": "confirm-open-pr";
|
|
@@ -85,38 +100,29 @@ export declare const PlanSchema: z.ZodObject<{
|
|
|
85
100
|
}, z.core.$strip>>;
|
|
86
101
|
}, z.core.$strip>;
|
|
87
102
|
export type Plan = z.infer<typeof PlanSchema>;
|
|
88
|
-
/** What planning reads off a row. A host's own row type plans the same way. */
|
|
89
|
-
export type PlannableRow = Pick<SweepItem, 'id' | 'facts' | 'requires' | 'deletable'> & {
|
|
90
|
-
category: string;
|
|
91
|
-
};
|
|
92
|
-
export declare function isPlanCategory(category: string): category is PlanCategory;
|
|
93
103
|
/**
|
|
94
|
-
* The
|
|
95
|
-
* and state. A worktree: HEAD, branch, how much unlanded work it holds
|
|
96
|
-
*
|
|
97
|
-
* revived after planning,
|
|
104
|
+
* The evidence a row's verdict rests on. A branch: its sha and its PR's number
|
|
105
|
+
* and state. A worktree: HEAD, branch, how much unlanded work it holds, whether
|
|
106
|
+
* its lock was stale, whether it is on disk, and each submodule's state, so a
|
|
107
|
+
* lock taken or revived after planning, a directory that came back, or a
|
|
108
|
+
* submodule that changed, drops the row.
|
|
98
109
|
*/
|
|
99
|
-
export declare function fingerprint(
|
|
110
|
+
export declare function fingerprint(row: Row): Record<string, unknown>;
|
|
100
111
|
/** Equal fingerprints, treating a missing key as null. */
|
|
101
112
|
export declare function sameFingerprint(a: Record<string, unknown>, b: Record<string, unknown>): boolean;
|
|
102
|
-
/**
|
|
103
|
-
export declare function
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
branch?: string | null;
|
|
107
|
-
fetch?: boolean;
|
|
108
|
-
provider?: ProviderChoice | undefined;
|
|
109
|
-
}, items: readonly PlannableRow[]): PlanRepo;
|
|
113
|
+
/** True when `row` could run without being picked by name: it has an action and is not blocked. */
|
|
114
|
+
export declare function runnable(row: Pick<PlanRow, 'action' | 'verdict'>): boolean;
|
|
115
|
+
/** A plan of `sweep`: every row, in every repository it read. */
|
|
116
|
+
export declare function plan(sweep: Sweep, now?: Date): Plan;
|
|
110
117
|
/** A UTC timestamp safe in a file name, e.g. `2026-10-04T12-30-05-123Z`. */
|
|
111
118
|
export declare function fileStamp(d: Date): string;
|
|
112
119
|
export declare function writePlan(path: string, plan: Plan): void;
|
|
113
|
-
/** A plan file that cannot be read or does not match {@link PlanSchema}. */
|
|
114
|
-
export declare class PlanReadError extends Error {
|
|
115
|
-
}
|
|
116
120
|
/**
|
|
117
121
|
* Parse a plan's JSON text. `source` names where it came from, for the error.
|
|
118
|
-
*
|
|
122
|
+
* A plan of another version is refused, not translated: sweep again.
|
|
123
|
+
*
|
|
124
|
+
* @throws RepoCleanerError (`bad-plan`)
|
|
119
125
|
*/
|
|
120
126
|
export declare function parsePlan(text: string, source: string): Plan;
|
|
121
|
-
/** @throws
|
|
127
|
+
/** @throws RepoCleanerError (`bad-plan`) */
|
|
122
128
|
export declare function readPlan(path: string): Plan;
|
package/dist/plan.js
CHANGED
|
@@ -1,74 +1,69 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The
|
|
3
|
-
*
|
|
4
|
-
* whose fingerprint
|
|
2
|
+
* The plan: a sweep's deletable rows, each with a fingerprint of the evidence
|
|
3
|
+
* its verdict rests on, and the options the sweep ran with. `check` sweeps
|
|
4
|
+
* again with the same options and drops any row whose fingerprint moved.
|
|
5
5
|
*/
|
|
6
6
|
import { readFileSync } from 'node:fs';
|
|
7
7
|
import { z } from 'zod';
|
|
8
8
|
import { PROVIDER_CHOICES } from "./providers/types.js";
|
|
9
|
-
import {
|
|
9
|
+
import { ACTIONS, KINDS, RISKS, RepoCleanerError, VERDICTS, parseRowId, } from "./types.js";
|
|
10
10
|
import { writeJsonAtomic } from "./write-json.js";
|
|
11
|
-
export const PLAN_VERSION =
|
|
12
|
-
/**
|
|
13
|
-
export const
|
|
11
|
+
export const PLAN_VERSION = 2;
|
|
12
|
+
/** A row id, `<kind>:<name>` with a name; shared by the plan and the receipt. */
|
|
13
|
+
export const RowIdSchema = z.custom((v) => typeof v === 'string' && (parseRowId(v)?.name ?? '') !== '', { message: 'expected <kind>:<name>' });
|
|
14
14
|
export const PlanRowSchema = z.object({
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
id: RowIdSchema,
|
|
16
|
+
/** The sweep's verdict when the plan was made. Without a pick, only `delete` rows run. */
|
|
17
|
+
verdict: z.enum(VERDICTS),
|
|
18
|
+
/** What deleting it does; null for a row that can never be deleted, kept so a pick of it is skipped with its reason. */
|
|
19
|
+
action: z.enum(ACTIONS).nullable(),
|
|
20
|
+
risks: z.array(z.enum(RISKS)),
|
|
18
21
|
fingerprint: z.record(z.string(), z.unknown()),
|
|
19
22
|
});
|
|
20
23
|
export const PlanRepoSchema = z.object({
|
|
21
24
|
root: z.string(),
|
|
22
|
-
/**
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* True when the sweep asked no provider for pull requests. The name is the
|
|
26
|
-
* plan format's, from when GitHub was the only provider; renaming it would
|
|
27
|
-
* break every plan already written.
|
|
28
|
-
*/
|
|
29
|
-
no_gh: z.boolean(),
|
|
30
|
-
/** The provider choice the sweep ran with; absent is `auto`. `no_gh` wins over it. */
|
|
31
|
-
provider: z.enum(PROVIDER_CHOICES).optional(),
|
|
32
|
-
/** The `branch` the sweep was narrowed to; the re-sweep uses the same. */
|
|
33
|
-
branch: z.string().nullable().default(null),
|
|
34
|
-
/**
|
|
35
|
-
* Whether the sweep fetched before classifying. The re-sweep fetches too, so
|
|
36
|
-
* it judges against the remote's current default branch, as the sweep did.
|
|
37
|
-
* A plan written before this field reads as not fetched.
|
|
38
|
-
*/
|
|
39
|
-
fetch: z.boolean().default(false),
|
|
25
|
+
/** Whether this repository's sweep fetched; the re-check fetches too, so it judges against the same default branch. */
|
|
26
|
+
fetched: z.boolean(),
|
|
40
27
|
rows: z.array(PlanRowSchema),
|
|
41
28
|
});
|
|
42
29
|
export const PlanSchema = z.object({
|
|
43
30
|
version: z.literal(PLAN_VERSION),
|
|
44
|
-
|
|
31
|
+
createdAt: z.string(),
|
|
32
|
+
/** What was swept; the re-check sweeps the same. `SweptWith` is this object's type. */
|
|
33
|
+
options: z.object({
|
|
34
|
+
only: z.array(z.enum(KINDS)),
|
|
35
|
+
remote: z.boolean(),
|
|
36
|
+
offline: z.boolean(),
|
|
37
|
+
against: z.string().nullable(),
|
|
38
|
+
provider: z.enum(PROVIDER_CHOICES),
|
|
39
|
+
branch: z.string().nullable(),
|
|
40
|
+
}),
|
|
45
41
|
repos: z.array(PlanRepoSchema),
|
|
46
42
|
});
|
|
47
|
-
export function isPlanCategory(category) {
|
|
48
|
-
return PLAN_CATEGORIES.includes(category);
|
|
49
|
-
}
|
|
50
43
|
/**
|
|
51
|
-
* The
|
|
52
|
-
* and state. A worktree: HEAD, branch, how much unlanded work it holds
|
|
53
|
-
*
|
|
54
|
-
* revived after planning,
|
|
44
|
+
* The evidence a row's verdict rests on. A branch: its sha and its PR's number
|
|
45
|
+
* and state. A worktree: HEAD, branch, how much unlanded work it holds, whether
|
|
46
|
+
* its lock was stale, whether it is on disk, and each submodule's state, so a
|
|
47
|
+
* lock taken or revived after planning, a directory that came back, or a
|
|
48
|
+
* submodule that changed, drops the row.
|
|
55
49
|
*/
|
|
56
|
-
export function fingerprint(
|
|
57
|
-
|
|
58
|
-
|
|
50
|
+
export function fingerprint(row) {
|
|
51
|
+
if (row.kind === 'worktree') {
|
|
52
|
+
const e = row.evidence;
|
|
59
53
|
return {
|
|
60
|
-
head:
|
|
61
|
-
branch:
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
// Prune or remove: a directory that came back between plan and
|
|
54
|
+
head: e.head,
|
|
55
|
+
branch: e.branch,
|
|
56
|
+
dirtyFiles: e.dirtyFiles ?? null,
|
|
57
|
+
unpushedCommits: e.unpushedCommits ?? null,
|
|
58
|
+
staleLock: e.staleLock ?? null,
|
|
59
|
+
// Prune or remove: a directory that came back between plan and run
|
|
66
60
|
// must not turn a prune into a remove.
|
|
67
|
-
|
|
61
|
+
onDisk: e.onDisk ?? null,
|
|
62
|
+
submodules: e.submodules ?? null,
|
|
68
63
|
};
|
|
69
64
|
}
|
|
70
|
-
const pr =
|
|
71
|
-
return { sha:
|
|
65
|
+
const pr = row.evidence.pr;
|
|
66
|
+
return { sha: row.evidence.sha, prNumber: pr?.number ?? null, prState: pr?.state ?? null };
|
|
72
67
|
}
|
|
73
68
|
/** Equal fingerprints, treating a missing key as null. */
|
|
74
69
|
export function sameFingerprint(a, b) {
|
|
@@ -79,27 +74,33 @@ export function sameFingerprint(a, b) {
|
|
|
79
74
|
}
|
|
80
75
|
return true;
|
|
81
76
|
}
|
|
82
|
-
/**
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
77
|
+
/**
|
|
78
|
+
* The rows of `rows` as a plan holds them: every one. Without a pick only those
|
|
79
|
+
* with an action run; the rest are there so a pick of one is skipped with its
|
|
80
|
+
* reason rather than refused as unknown.
|
|
81
|
+
*/
|
|
82
|
+
function planRows(rows) {
|
|
83
|
+
return rows.map((row) => ({
|
|
84
|
+
id: row.id,
|
|
85
|
+
verdict: row.verdict,
|
|
86
|
+
action: row.action,
|
|
87
|
+
risks: row.risks,
|
|
88
|
+
fingerprint: fingerprint(row),
|
|
89
|
+
}));
|
|
90
|
+
}
|
|
91
|
+
/** True when `row` could run without being picked by name: it has an action and is not blocked. */
|
|
92
|
+
export function runnable(row) {
|
|
93
|
+
return row.action !== null && row.verdict !== 'blocked';
|
|
94
|
+
}
|
|
95
|
+
/** A plan of `sweep`: every row, in every repository it read. */
|
|
96
|
+
export function plan(sweep, now = new Date()) {
|
|
95
97
|
return {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
rows,
|
|
98
|
+
version: PLAN_VERSION,
|
|
99
|
+
createdAt: now.toISOString(),
|
|
100
|
+
options: { ...sweep.options },
|
|
101
|
+
repos: sweep.repos.flatMap((s) => s.repo === null
|
|
102
|
+
? []
|
|
103
|
+
: [{ root: s.repo.root, fetched: s.repo.fetched, rows: planRows(s.rows) }]),
|
|
103
104
|
};
|
|
104
105
|
}
|
|
105
106
|
/** A UTC timestamp safe in a file name, e.g. `2026-10-04T12-30-05-123Z`. */
|
|
@@ -109,12 +110,11 @@ export function fileStamp(d) {
|
|
|
109
110
|
export function writePlan(path, plan) {
|
|
110
111
|
writeJsonAtomic(path, plan);
|
|
111
112
|
}
|
|
112
|
-
/** A plan file that cannot be read or does not match {@link PlanSchema}. */
|
|
113
|
-
export class PlanReadError extends Error {
|
|
114
|
-
}
|
|
115
113
|
/**
|
|
116
114
|
* Parse a plan's JSON text. `source` names where it came from, for the error.
|
|
117
|
-
*
|
|
115
|
+
* A plan of another version is refused, not translated: sweep again.
|
|
116
|
+
*
|
|
117
|
+
* @throws RepoCleanerError (`bad-plan`)
|
|
118
118
|
*/
|
|
119
119
|
export function parsePlan(text, source) {
|
|
120
120
|
let raw;
|
|
@@ -122,21 +122,26 @@ export function parsePlan(text, source) {
|
|
|
122
122
|
raw = JSON.parse(text);
|
|
123
123
|
}
|
|
124
124
|
catch (err) {
|
|
125
|
-
throw new
|
|
125
|
+
throw new RepoCleanerError('bad-plan', `cannot read plan ${source}: ${err instanceof Error ? err.message : String(err)}`);
|
|
126
|
+
}
|
|
127
|
+
const version = raw?.version;
|
|
128
|
+
if (version !== PLAN_VERSION) {
|
|
129
|
+
throw new RepoCleanerError('bad-plan', `plan ${source} is version ${JSON.stringify(version ?? null)}; this repo-cleaner reads version ${PLAN_VERSION}. Sweep again to make a new plan`);
|
|
126
130
|
}
|
|
127
131
|
const parsed = PlanSchema.safeParse(raw);
|
|
128
|
-
if (!parsed.success)
|
|
129
|
-
throw new
|
|
132
|
+
if (!parsed.success) {
|
|
133
|
+
throw new RepoCleanerError('bad-plan', `bad plan ${source}: ${parsed.error.message}`);
|
|
134
|
+
}
|
|
130
135
|
return parsed.data;
|
|
131
136
|
}
|
|
132
|
-
/** @throws
|
|
137
|
+
/** @throws RepoCleanerError (`bad-plan`) */
|
|
133
138
|
export function readPlan(path) {
|
|
134
139
|
let text;
|
|
135
140
|
try {
|
|
136
141
|
text = readFileSync(path, 'utf-8');
|
|
137
142
|
}
|
|
138
143
|
catch (err) {
|
|
139
|
-
throw new
|
|
144
|
+
throw new RepoCleanerError('bad-plan', `cannot read plan ${path}: ${err instanceof Error ? err.message : String(err)}`);
|
|
140
145
|
}
|
|
141
146
|
return parsePlan(text, path);
|
|
142
147
|
}
|
|
@@ -5,6 +5,7 @@ import type { PrProvider } from './types.ts';
|
|
|
5
5
|
export declare const PR_LIMIT = 300;
|
|
6
6
|
/**
|
|
7
7
|
* A provider over `gh pr list`. `gh` picks the repository from the checkout's
|
|
8
|
-
* remotes, so `target` is not consulted.
|
|
8
|
+
* remotes, so `target` is not consulted. A listing `gh` fails, such as one for
|
|
9
|
+
* a repository the signed-in account cannot see, throws with `gh`'s reason.
|
|
9
10
|
*/
|
|
10
11
|
export declare function githubProvider(gh: Gh): PrProvider;
|
package/dist/providers/github.js
CHANGED
|
@@ -15,7 +15,8 @@ const FIELDS = [
|
|
|
15
15
|
export const PR_LIMIT = 300;
|
|
16
16
|
/**
|
|
17
17
|
* A provider over `gh pr list`. `gh` picks the repository from the checkout's
|
|
18
|
-
* remotes, so `target` is not consulted.
|
|
18
|
+
* remotes, so `target` is not consulted. A listing `gh` fails, such as one for
|
|
19
|
+
* a repository the signed-in account cannot see, throws with `gh`'s reason.
|
|
19
20
|
*/
|
|
20
21
|
export function githubProvider(gh) {
|
|
21
22
|
return {
|