dw-sdlc 0.18.0 → 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/README.md +107 -83
- package/config/agentic.config.schema.json +2 -2
- package/dist/commands/doctor.js +5 -1
- package/dist/commands/setup.d.ts +2 -2
- package/dist/commands/setup.js +2 -2
- package/dist/commands/sweep.js +5 -8
- package/dist/sweep-trees.d.ts +10 -0
- package/dist/sweep-trees.js +22 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
# dw-sdlc
|
|
2
2
|
|
|
3
|
-
The
|
|
4
|
-
works for an agent: its package manager, protected
|
|
5
|
-
Gate and who merges a pull request. The package ships
|
|
6
|
-
resolver of its layers.
|
|
3
|
+
The bin and the config behind the [dw-sdlc](https://github.com/dominikwozniak/dw-sdlc) Claude Code plugins.
|
|
4
|
+
`.ai/agentic.config.json` declares how a repository works for an agent: its package manager, protected
|
|
5
|
+
branches, env-file policy, commit convention, the Gate and who merges a pull request. The package ships
|
|
6
|
+
the `dw-sdlc` bin that runs on that file, the file's JSON Schema and the resolver of its layers. The Gate
|
|
7
|
+
and the Ship script work in any repository, with or without the plugins.
|
|
7
8
|
|
|
8
9
|
## Install
|
|
9
10
|
|
|
@@ -13,17 +14,17 @@ Node 24 or later.
|
|
|
13
14
|
pnpm add -D dw-sdlc
|
|
14
15
|
```
|
|
15
16
|
|
|
17
|
+
Every release goes to npm from GitHub Actions by trusted publishing, and every version after 0.17.0
|
|
18
|
+
carries provenance too. Trust evidence never drops from one version to the next, so a repository on
|
|
19
|
+
pnpm's `trustPolicy: no-downgrade` takes each release. Dependabot offers one after the seven days of
|
|
20
|
+
`minimumReleaseAge`; a repository that cannot wait names `dw-sdlc` in `minimumReleaseAgeExclude` under
|
|
21
|
+
its own rule for exclusions.
|
|
22
|
+
|
|
16
23
|
## The Gate
|
|
17
24
|
|
|
18
|
-
`dw-sdlc gate` runs the Steps under `gate.steps
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
deletes a file git tracks or would track fails the Gate as Drift, unless `gate.drift` is `"allow"`.
|
|
22
|
-
It exits 0 when everything passes, 1 when a Step or a Check fails or a Step drifts, and 2 when the
|
|
23
|
-
Gate cannot run. `--json` prints the result as one document. `--cache` skips the run when the
|
|
24
|
-
working tree and the resolved Gate match the last green run in that checkout, so a pre-push hook
|
|
25
|
-
costs nothing on an unchanged tree. `dw-sdlc check <name>` runs one Check alone, with the same
|
|
26
|
-
lines, exit codes and document.
|
|
25
|
+
`dw-sdlc gate` proves a change. It runs the Steps under `gate.steps`, the repository's own commands, in
|
|
26
|
+
order from the repository root, stopping at the first that fails. Then it runs every Check under
|
|
27
|
+
`gate.checks`, a named rule the package ships, none stopping the next.
|
|
27
28
|
|
|
28
29
|
```json
|
|
29
30
|
{
|
|
@@ -35,90 +36,113 @@ lines, exit codes and document.
|
|
|
35
36
|
}
|
|
36
37
|
```
|
|
37
38
|
|
|
38
|
-
A Step is a command string, or `{ "command": "…", "hint": "…" }`; a `gate.checks.run` entry takes a
|
|
39
|
-
`hint` beside `name` and `command`. When the Step or script fails, the Gate prints `hint: <text>` under
|
|
40
|
-
its failure line, and `--json` carries `hint` on every Step and Check, `null` where there is none.
|
|
41
|
-
|
|
42
39
|
```json
|
|
43
40
|
{ "scripts": { "check": "dw-sdlc gate" } }
|
|
44
41
|
```
|
|
45
42
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
`
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
`
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
43
|
+
It prints one line per Step and per Check, and the last 50 lines of a command that fails. A Step that
|
|
44
|
+
creates, changes or deletes a file git tracks or would track fails as Drift, unless `gate.drift` is
|
|
45
|
+
`"allow"`. A Step written as `{ "command": "…", "hint": "…" }` prints `hint: <text>` under its failure
|
|
46
|
+
line; a `gate.checks.run` entry takes a `hint` beside `name` and `command`.
|
|
47
|
+
|
|
48
|
+
| flag | does |
|
|
49
|
+
| ------------------- | --------------------------------------------------------------------------------- |
|
|
50
|
+
| `--json` | prints the result as one document; each Step and Check carries `tail` and `hint` |
|
|
51
|
+
| `--verbose` | streams every command's output instead of the tail |
|
|
52
|
+
| `--cache` | skips the run when the tree and the Gate match the last green run in the checkout |
|
|
53
|
+
| `--update-baseline` | after a green run, tightens the baseline of each Ratchet |
|
|
54
|
+
|
|
55
|
+
| exit | means |
|
|
56
|
+
| ---- | ---------------------------------------------------------------------------------- |
|
|
57
|
+
| 0 | everything passes |
|
|
58
|
+
| 1 | a Step or a Check fails, or a Step drifts |
|
|
59
|
+
| 2 | the Gate cannot run: an invalid config layer, a command not found, a missing input |
|
|
60
|
+
|
|
61
|
+
`dw-sdlc check <name>` runs one Check alone, with the same lines, exit codes and document.
|
|
62
|
+
|
|
63
|
+
### Checks
|
|
64
|
+
|
|
65
|
+
Under `gate.checks`, a Check is `true` for its defaults, an object of its options, or `false`. The
|
|
66
|
+
schema's description of each one names its options and defaults.
|
|
67
|
+
|
|
68
|
+
| Check | fails | reads first |
|
|
69
|
+
| -------------------- | --------------------------------------------------------------------------- | ------------------------- |
|
|
70
|
+
| `markdown-refs` | a relative link or image whose path or heading anchor does not resolve | nothing |
|
|
71
|
+
| `temporal-language` | a changelog phrase in a durable document, outside code | nothing |
|
|
72
|
+
| `comment-budget` | a comment block over 3 lines of prose in JS, TS or shell | nothing |
|
|
73
|
+
| `disable-directives` | a linter or type-checker disable comment with no reason | nothing |
|
|
74
|
+
| `agents-map` | an `AGENTS.md` over budget, missing the Gate command or pointing at nothing | `AGENTS.md` |
|
|
75
|
+
| `primitives` | a catalogue entry out of step with the tree, or a file no entry owns | `.ai/primitives.json` |
|
|
76
|
+
| `routed-docs` | a document under `docs/` no Task Router row or Primitive leads to | the map, the catalogue |
|
|
77
|
+
| `markdown-budget` | a markdown file over the byte budget of its glob | the files its globs match |
|
|
78
|
+
| `decision-records` | a decision record misnamed, misnumbered or without a valid status | a records directory |
|
|
79
|
+
| `file-size-ratchet` | a code file over its line limit that the baseline does not allow | its baseline |
|
|
80
|
+
| `skill-sections` | a README section a skill copies that differs from its source | the catalogue |
|
|
81
|
+
|
|
82
|
+
Each Check's options and defaults:
|
|
83
|
+
[its description in the schema](https://github.com/dominikwozniak/dw-sdlc/blob/main/packages/dw-sdlc/config/agentic.config.schema.json).
|
|
66
84
|
|
|
67
85
|
## The Ship script
|
|
68
86
|
|
|
69
|
-
`dw-sdlc pr ship [<n>] [-R owner/name]` reads one pull request through an authenticated `gh` and
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
87
|
+
`dw-sdlc pr ship [<n>] [-R owner/name]` reads one pull request through an authenticated `gh` and answers
|
|
88
|
+
the one Action it needs next, as a line or, with `--json`, one document. It performs its own Actions:
|
|
89
|
+
it updates a branch GitHub reports `BEHIND`, marks the head reviewed at Review depth `off`, marks a draft
|
|
90
|
+
ready once nothing else holds it back, and squash merges when the merge is its own. It never deletes a
|
|
91
|
+
branch. `--dry-run` performs nothing.
|
|
92
|
+
|
|
93
|
+
| exit | whose turn |
|
|
94
|
+
| ---- | ------------------ |
|
|
95
|
+
| 0 | done |
|
|
96
|
+
| 10 | the agent's: act |
|
|
97
|
+
| 20 | nobody's yet: wait |
|
|
98
|
+
| 30 | the dev's: park |
|
|
99
|
+
| 2 | it cannot run |
|
|
100
|
+
|
|
101
|
+
| key | sets |
|
|
102
|
+
| ------------------ | ---------------------------------------------------------------------------------------------- |
|
|
103
|
+
| `pr.merge` | who merges: the script under `auto`, the dev under `ask`, the default |
|
|
104
|
+
| `pr.review` | the Review depth of an unreviewed head: `full`, `light`, `off`, or `auto` from the change size |
|
|
105
|
+
| `pr.reviewCommand` | the slash command a `full` Review runs, `/code-review` by default |
|
|
86
106
|
|
|
87
107
|
```json
|
|
88
108
|
{ "pr": { "merge": "auto", "review": "full" } }
|
|
89
109
|
```
|
|
90
110
|
|
|
91
|
-
`pr
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
so the Review after a fix reads the fix alone. `pr reviewed` posts nothing and exits 2 once a push
|
|
96
|
-
moved the head, so a new head needs a fresh review. Under `off` the script posts that status
|
|
97
|
-
itself, its description saying why, and goes on.
|
|
111
|
+
The local layer may tighten `pr.merge` and `pr.review`, never loosen them. `--merge` acts as `auto` and
|
|
112
|
+
`--review full|light|off` sets the depth for one call. A pull request the `gh` account did not open is
|
|
113
|
+
always the dev's to merge. `light` spawns the `sdlc-reviewer` subagent of the
|
|
114
|
+
[`sdlc` plugin](https://github.com/dominikwozniak/dw-sdlc/blob/main/plugins/sdlc/README.md#the-light-review).
|
|
98
115
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
buys one more Review past it.
|
|
116
|
+
`dw-sdlc pr reviewed <n> --head <sha>` pins a Review to the head it read, as the commit status
|
|
117
|
+
`dw-sdlc/review`: clean, Findings open with `--findings`, or accepted by the dev with `--accept`. The
|
|
118
|
+
routing rules, the read mark and the limit of three unclean Reviews are in
|
|
119
|
+
[How the Ship script merges a pull request](https://github.com/dominikwozniak/dw-sdlc/blob/main/docs/overview.md#how-the-ship-script-merges-a-pull-request).
|
|
104
120
|
|
|
105
121
|
## Commands
|
|
106
122
|
|
|
107
|
-
| command | what it does
|
|
108
|
-
| ------------------ |
|
|
109
|
-
| `gate` | the Steps of `gate.steps` in order, then every Check under `gate.checks`
|
|
110
|
-
| `check <name>` | one Check of the Gate by its name, without the Steps and the other Checks
|
|
111
|
-
| `primitives` | the Primitives that own each path, or that a change removes, adds or extends
|
|
112
|
-
| `setup` | this machine's local config layer,
|
|
113
|
-
| `doctor` | one PASS / WARN / FAIL line per machine check the
|
|
114
|
-
| `config` | the resolved agentic config, or one file validated against the schema
|
|
115
|
-
| `references sync` | every declared Reference brought to its ref under `references/`
|
|
116
|
-
| `references setup` | the Router, its table and the ignore rules a repository needs to declare References
|
|
117
|
-
| `pr ship [<n>]` | the Action one pull request needs next, performing the script's own
|
|
118
|
-
| `pr reviewed <n>` | a Review pinned to the head it read, as a commit status
|
|
119
|
-
| `recap` | the facts a Recap rests on
|
|
120
|
-
|
|
121
|
-
`
|
|
123
|
+
| command | what it does |
|
|
124
|
+
| ------------------ | ------------------------------------------------------------------------------------------------- |
|
|
125
|
+
| `gate` | the Gate: the Steps of `gate.steps` in order, then every Check under `gate.checks` |
|
|
126
|
+
| `check <name>` | one Check of the Gate by its name, without the Steps and the other Checks |
|
|
127
|
+
| `primitives` | the Primitives that own each path, or that a change removes, adds or extends |
|
|
128
|
+
| `setup` | the Gate adopted where asked, this machine's local config layer, git excludes and permissions |
|
|
129
|
+
| `doctor` | one PASS / WARN / FAIL line per machine check the `sdlc` plugin relies on |
|
|
130
|
+
| `config` | the resolved agentic config, or one file validated against the schema |
|
|
131
|
+
| `references sync` | every declared Reference, a repository cloned for reading, brought to its ref under `references/` |
|
|
132
|
+
| `references setup` | the Router, its table and the ignore rules a repository needs to declare References |
|
|
133
|
+
| `pr ship [<n>]` | the Action one pull request needs next, performing the script's own |
|
|
134
|
+
| `pr reviewed <n>` | a Review pinned to the head it read, as a commit status |
|
|
135
|
+
| `recap` | the facts a Recap rests on, whether the session can be closed and what could come next |
|
|
136
|
+
| `sweep` | Landed branches and clean worktrees removed, and what it keeps named |
|
|
137
|
+
| `worker start` | a Worker started on a Brief in a herdr terminal |
|
|
138
|
+
| `worker ls` | the Workers this Lead started and their states |
|
|
139
|
+
| `worker report` | a Recap delivered to the Lead, retried while it is blocked |
|
|
140
|
+
| `lead start` | the next session started as a Lead in a herdr terminal of its own |
|
|
141
|
+
|
|
142
|
+
`dw-sdlc --help` lists them, and `dw-sdlc <command> --help` gives each one's options and exit codes. A
|
|
143
|
+
Primitive is one unit a repository exposes to an agent, a module, a hook, a script, a config key or a
|
|
144
|
+
skill, listed in `.ai/primitives.json`. A Recap, a Brief, a Worker and a Lead belong to the
|
|
145
|
+
[`sdlc-sessions` plugin](https://github.com/dominikwozniak/dw-sdlc/blob/main/plugins/sdlc-sessions/README.md).
|
|
122
146
|
|
|
123
147
|
## The resolver
|
|
124
148
|
|
|
@@ -129,8 +153,8 @@ const { config, problems } = resolveConfig(process.cwd())
|
|
|
129
153
|
```
|
|
130
154
|
|
|
131
155
|
`resolveConfig` merges the committed `.ai/agentic.config.json`, the machine's
|
|
132
|
-
`.ai/agentic.config.local.json` and what it infers from the repository. `problems` names each layer
|
|
133
|
-
|
|
156
|
+
`.ai/agentic.config.local.json` and what it infers from the repository. `problems` names each layer that
|
|
157
|
+
fails the schema; an invalid layer is dropped whole, and the guards then run the strictest set.
|
|
134
158
|
|
|
135
159
|
## License
|
|
136
160
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"$id": "https://raw.githubusercontent.com/dominikwozniak/dw-sdlc/main/
|
|
3
|
+
"$id": "https://raw.githubusercontent.com/dominikwozniak/dw-sdlc/main/packages/dw-sdlc/config/agentic.config.schema.json",
|
|
4
4
|
"title": "agentic.config",
|
|
5
|
-
"description": "How a repository works for an agent. `.ai/agentic.config.json` is committed; `.ai/agentic.config.local.json` overrides it on one machine and may tighten `guardrails` and `pr.
|
|
5
|
+
"description": "How a repository works for an agent. `.ai/agentic.config.json` is committed; `.ai/agentic.config.local.json` overrides it on one machine and may tighten `guardrails`, `pr.merge` and `pr.review` but never loosen them. A key neither file sets is inferred: the Node package manager from the lockfile, the `conventional` commit preset, and `gate.command` from the manifest's `check` script. Unknown top-level keys are tolerated so other tool families can share the file; the keys named here refuse unknown children.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"properties": {
|
|
8
8
|
"$schema": {
|
package/dist/commands/doctor.js
CHANGED
|
@@ -47,7 +47,11 @@ function checkJq(probe) {
|
|
|
47
47
|
if (status === 0) {
|
|
48
48
|
return { name: "jq", level: "PASS", detail: stdout.trim() };
|
|
49
49
|
}
|
|
50
|
-
return {
|
|
50
|
+
return {
|
|
51
|
+
name: "jq",
|
|
52
|
+
level: "FAIL",
|
|
53
|
+
detail: "missing: every guard is silent without it. https://jqlang.org/download/"
|
|
54
|
+
};
|
|
51
55
|
}
|
|
52
56
|
/** Every hook command string in a settings file, whatever the event and matcher. */
|
|
53
57
|
function hookCommands(settings) {
|
package/dist/commands/setup.d.ts
CHANGED
|
@@ -27,8 +27,8 @@ export type SetupOptions = {
|
|
|
27
27
|
export declare const summary = "adopt the Gate where asked, and write this machine's local config layer, the git excludes and the permission rules";
|
|
28
28
|
/**
|
|
29
29
|
* What the machine's layer points `$schema` at: the marketplace clone's schema as a `file://` URL, as an
|
|
30
|
-
* editor
|
|
31
|
-
*
|
|
30
|
+
* editor reads it offline at the installed version and the clone, unlike the install cache, keeps its
|
|
31
|
+
* path across plugin updates. Without a registered clone holding the schema, the `$id`.
|
|
32
32
|
*/
|
|
33
33
|
export declare function schemaReference(claudeDir: string): string;
|
|
34
34
|
export declare function setup(options: SetupOptions): Step[];
|
package/dist/commands/setup.js
CHANGED
|
@@ -69,8 +69,8 @@ function committedConfig(root) {
|
|
|
69
69
|
}
|
|
70
70
|
/**
|
|
71
71
|
* What the machine's layer points `$schema` at: the marketplace clone's schema as a `file://` URL, as an
|
|
72
|
-
* editor
|
|
73
|
-
*
|
|
72
|
+
* editor reads it offline at the installed version and the clone, unlike the install cache, keeps its
|
|
73
|
+
* path across plugin updates. Without a registered clone holding the schema, the `$id`.
|
|
74
74
|
*/
|
|
75
75
|
export function schemaReference(claudeDir) {
|
|
76
76
|
const registry = readJson(join(claudeDir, "plugins", "known_marketplaces.json"));
|
package/dist/commands/sweep.js
CHANGED
|
@@ -9,7 +9,7 @@ import { isCommitLanded, isLanded } from "../landed.js";
|
|
|
9
9
|
import { shellLine } from "../pr/decide.js";
|
|
10
10
|
import { CannotRun } from "../pr/read.js";
|
|
11
11
|
import { byHand, holdersOf, keptIn, lockAfterStop, lockHolder, outsideWorktrees, readTerminals, removeTerminal, stopsOf, sweepingTerminal, whyHeld } from "../sweep-terminals.js";
|
|
12
|
-
import {
|
|
12
|
+
import { branchesOf, dirtyFiles, pausedOperation, switchOff, worktreesOf } from "../sweep-trees.js";
|
|
13
13
|
import { asked, git } from "../working-tree.js";
|
|
14
14
|
import { lockVerdict } from "../worktree-lock.js";
|
|
15
15
|
export const summary = "removes Landed branches and clean worktrees whose work has landed, names what it keeps";
|
|
@@ -26,7 +26,8 @@ config, else the branch origin/HEAD names) and removes, never forcing:
|
|
|
26
26
|
It keeps, and names with the reason and the command to run yourself:
|
|
27
27
|
a worktree that fails the set: the files that hold it with the forced removal, the paused operation, the
|
|
28
28
|
lock's reason with git worktree unlock, a live Claude Code lock named by its pid, a HEAD that is not Landed;
|
|
29
|
-
the default branch, the base branch, a protected branch and a branch a kept worktree has checked out;
|
|
29
|
+
the default branch, the base branch, a protected branch and a branch a kept worktree has checked out; a
|
|
30
|
+
Landed branch the main checkout has checked out, clean and with nothing paused, with git switch to the base;
|
|
30
31
|
a branch whose work is on no commit of the base, with the forced delete.
|
|
31
32
|
It never changes anything on a remote: the fetch only writes remote-tracking refs. A removal git refuses is
|
|
32
33
|
kept with git's words, and the sweep goes on.
|
|
@@ -91,11 +92,6 @@ function baseOf(root, head) {
|
|
|
91
92
|
}
|
|
92
93
|
return { name, ref };
|
|
93
94
|
}
|
|
94
|
-
function branchesOf(root) {
|
|
95
|
-
return must(root, ["for-each-ref", "--format=%(refname:lstrip=2)", "refs/heads"])
|
|
96
|
-
.split("\n")
|
|
97
|
-
.filter((name) => name !== "");
|
|
98
|
-
}
|
|
99
95
|
/** Why the worktree stays, with the dev's command when there is one, or null when its whole set passes. */
|
|
100
96
|
function whyTreeStays(root, baseRef, tree, lock, holders) {
|
|
101
97
|
const { path } = tree;
|
|
@@ -194,8 +190,9 @@ function judge(root, terminals) {
|
|
|
194
190
|
}
|
|
195
191
|
judged.add(name);
|
|
196
192
|
const why = stays(name, footing);
|
|
193
|
+
const off = why?.dev === true && held.get(name) === checkout.path ? switchOff(root, base, name) : null;
|
|
197
194
|
if (why !== null) {
|
|
198
|
-
kept.push({ kind: "branch", name, ...why });
|
|
195
|
+
kept.push({ kind: "branch", name, ...why, ...off });
|
|
199
196
|
}
|
|
200
197
|
else if (isLanded(root, base.ref, name)) {
|
|
201
198
|
removed.push({ kind: "branch", name, reason: `Landed on ${baseName}` });
|
package/dist/sweep-trees.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/** `git <args>` at `root`, or a stop naming its problem. */
|
|
2
2
|
export declare function must(root: string, args: string[]): string;
|
|
3
|
+
/** The local branches of the repository at `root`. */
|
|
4
|
+
export declare function branchesOf(root: string): string[];
|
|
3
5
|
export type Tree = {
|
|
4
6
|
path: string;
|
|
5
7
|
head: string;
|
|
@@ -15,3 +17,11 @@ export declare function dirtyFiles(path: string): string[] | {
|
|
|
15
17
|
};
|
|
16
18
|
/** The operation paused in the worktree at `path`: its git dir holds the state git resumes it from. */
|
|
17
19
|
export declare function pausedOperation(path: string): string | undefined;
|
|
20
|
+
/** The dev's way off `branch`, checked out in the main checkout at `root`: null until it is Landed and nothing there could be lost. */
|
|
21
|
+
export declare function switchOff(root: string, base: {
|
|
22
|
+
name: string;
|
|
23
|
+
ref: string;
|
|
24
|
+
}, branch: string): {
|
|
25
|
+
reason: string;
|
|
26
|
+
command: string;
|
|
27
|
+
} | null;
|
package/dist/sweep-trees.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
// What a Sweep reads of a repository's
|
|
2
|
-
// the files that hold it and the operation paused in it.
|
|
1
|
+
// What a Sweep reads of a repository's branches and worktrees: where each worktree lies, what it has checked
|
|
2
|
+
// out, its lock, the files that hold it and the operation paused in it, and the way off the main checkout's.
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { exec } from "./exec.js";
|
|
6
6
|
import { repositoryFreeEnv } from "./git-env.js";
|
|
7
|
+
import { isLanded } from "./landed.js";
|
|
8
|
+
import { shellLine } from "./pr/decide.js";
|
|
7
9
|
import { CannotRun } from "./pr/read.js";
|
|
8
10
|
import { asked, git } from "./working-tree.js";
|
|
9
11
|
/** `git <args>` at `root`, or a stop naming its problem. */
|
|
@@ -14,6 +16,12 @@ export function must(root, args) {
|
|
|
14
16
|
}
|
|
15
17
|
return result.out;
|
|
16
18
|
}
|
|
19
|
+
/** The local branches of the repository at `root`. */
|
|
20
|
+
export function branchesOf(root) {
|
|
21
|
+
return must(root, ["for-each-ref", "--format=%(refname:lstrip=2)", "refs/heads"])
|
|
22
|
+
.split("\n")
|
|
23
|
+
.filter((name) => name !== "");
|
|
24
|
+
}
|
|
17
25
|
/** Every worktree of the repository, the main checkout first. */
|
|
18
26
|
export function worktreesOf(root) {
|
|
19
27
|
const trees = [];
|
|
@@ -70,3 +78,15 @@ export function pausedOperation(path) {
|
|
|
70
78
|
const dir = asked(path, ["rev-parse", "--absolute-git-dir"]);
|
|
71
79
|
return dir === undefined ? undefined : OPERATIONS.find(([file]) => existsSync(join(dir, file)))?.[1];
|
|
72
80
|
}
|
|
81
|
+
/** The dev's way off `branch`, checked out in the main checkout at `root`: null until it is Landed and nothing there could be lost. */
|
|
82
|
+
export function switchOff(root, base, branch) {
|
|
83
|
+
const files = dirtyFiles(root);
|
|
84
|
+
if ("error" in files ||
|
|
85
|
+
files.length > 0 ||
|
|
86
|
+
pausedOperation(root) !== undefined ||
|
|
87
|
+
!isLanded(root, base.ref, branch)) {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
const command = `${shellLine(["git", "switch", base.name])} && ${shellLine(["git", "pull", "--rebase"])}`;
|
|
91
|
+
return { reason: `checked out in the main checkout at ${root}, Landed on origin/${base.name}`, command };
|
|
92
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dw-sdlc",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "The Gate and the agentic config
|
|
3
|
+
"version": "0.19.0",
|
|
4
|
+
"description": "The Gate and the agentic config behind the sdlc Claude Code plugin: a bin with the Gate and its Checks, the setup, the doctor, the Sync of References, the Ship script that drives a pull request to merged, the Sweep, and Lead and Worker sessions in herdr; the resolver of a repository's .ai/agentic.config.json layers and its JSON Schema.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|