dw-sdlc 0.15.0 → 0.15.2
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/dist/commands/setup.d.ts +2 -2
- package/dist/commands/setup.js +8 -4
- package/dist/commands/worker-start.js +10 -2
- package/dist/exec.js +24 -5
- package/dist/pr/decide.js +8 -4
- package/dist/pr/read.d.ts +2 -0
- package/dist/pr/read.js +4 -0
- package/dist/pr/state.d.ts +5 -1
- package/dist/pr/state.js +6 -2
- package/dist/worker/origin.d.ts +6 -0
- package/dist/worker/origin.js +42 -0
- package/package.json +1 -1
package/dist/commands/setup.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type CommitConvention, type CommitOnProtected, type ForcePush } from "../agentic-config.ts";
|
|
2
|
-
/** `by hand` is an edit the setup names and leaves to the user. */
|
|
3
|
-
export type Outcome = "wrote" | "unchanged" | "refused" | "by hand";
|
|
2
|
+
/** `by hand` is an edit the setup names and leaves to the user; `skipped` is a file it has no reason to write. */
|
|
3
|
+
export type Outcome = "wrote" | "unchanged" | "skipped" | "refused" | "by hand";
|
|
4
4
|
export type Step = {
|
|
5
5
|
file: string;
|
|
6
6
|
outcome: Outcome;
|
package/dist/commands/setup.js
CHANGED
|
@@ -28,7 +28,8 @@ is kept, and --force-push and --commit-on-protected pin those two keys in it, re
|
|
|
28
28
|
loosen what the committed config sets; --force overwrites a local config that differs in anything
|
|
29
29
|
else; --dry-run reports without writing. Where cswap is on PATH, the local config also gets
|
|
30
30
|
worker.command, the command a Worker starts with: cswap run -- --dangerously-skip-permissions. A
|
|
31
|
-
worker block the file holds is kept as it is, and without cswap nothing is written.
|
|
31
|
+
worker block the file holds is kept as it is, and without cswap nothing is written. A local config
|
|
32
|
+
holding only $schema is not written, and an existing one stays: the run prints "skipped".
|
|
32
33
|
|
|
33
34
|
Where the repository has a package.json, the setup adopts the Gate. --add-package pins dw-sdlc
|
|
34
35
|
there as a devDependency, at the version running the setup; a new dependency needs the owner's yes,
|
|
@@ -251,8 +252,8 @@ export function setup(options) {
|
|
|
251
252
|
const root = repositoryRoot(options.root);
|
|
252
253
|
const { checkout, gitDir } = mainCheckout(root);
|
|
253
254
|
const claudeDir = options.claudeDir ?? join(options.home, ".claude");
|
|
254
|
-
// 1. The local config
|
|
255
|
-
// the
|
|
255
|
+
// 1. The local config: inference, the convention (the flag's, else the file's pin), the guardrails block
|
|
256
|
+
// the file holds with the flags' pins. A layer of `$schema` alone tells the guards nothing: not written.
|
|
256
257
|
const localPath = join(checkout, LOCAL_FILE);
|
|
257
258
|
const localCurrent = existsSync(localPath) ? readFileSync(localPath, "utf8") : undefined;
|
|
258
259
|
const existing = localCurrent === undefined ? undefined : parseObject(localCurrent);
|
|
@@ -264,7 +265,10 @@ export function setup(options) {
|
|
|
264
265
|
else {
|
|
265
266
|
const { worker, detail } = pinnedWorker(keptWorker(existing), options.path ?? process.env.PATH);
|
|
266
267
|
const local = localConfig(checkout, claudeDir, convention, pinned.guardrails, worker);
|
|
267
|
-
if (localCurrent
|
|
268
|
+
if (Object.keys(local).length === 1 && (localCurrent === undefined || sameContent(existing, local))) {
|
|
269
|
+
writer.steps.push({ file: LOCAL_FILE, outcome: "skipped", detail: "the layer would hold only $schema" });
|
|
270
|
+
}
|
|
271
|
+
else if (localCurrent !== undefined && !sameContent(existing, local) && !options.force) {
|
|
268
272
|
writer.refuse(LOCAL_FILE, "exists with other content; pass --force to overwrite it");
|
|
269
273
|
}
|
|
270
274
|
else {
|
|
@@ -7,6 +7,7 @@ import { parseArgs } from "node:util";
|
|
|
7
7
|
import { mainCheckout, repositoryRoot, resolveConfig, WORKER_EFFORTS } from "../agentic-config.js";
|
|
8
8
|
import { isObject } from "../json-text.js";
|
|
9
9
|
import { describe, hcObject, isLive, lead, refuse, Refusal, wholeNumber, workersOf } from "../worker/herdr.js";
|
|
10
|
+
import { freshenOrigin } from "../worker/origin.js";
|
|
10
11
|
import { nameTab } from "../worker/tabs.js";
|
|
11
12
|
export const summary = "start a Worker on a Brief in a herdr terminal";
|
|
12
13
|
/** The most seconds the command looks for the Worker's tab in the window, beside the read-back. */
|
|
@@ -27,12 +28,17 @@ turns working, blocked or done. It fails when herdr reports the first message as
|
|
|
27
28
|
ended, the Worker reports an error, or the Worker is still idle or silent when the time is up. It never sends
|
|
28
29
|
the Brief again: read the terminal with hc read first.
|
|
29
30
|
|
|
31
|
+
Before it starts the Worker it fetches origin's default branch into the clone (the branch origin/HEAD names,
|
|
32
|
+
else baseBranch of the config, else every branch of origin), so the Worker's worktree, which Claude Code
|
|
33
|
+
branches from origin/<default>, begins from origin's tip. A clone with no origin skips the fetch. When the
|
|
34
|
+
fetch fails the command starts no Worker and exits 2.
|
|
35
|
+
|
|
30
36
|
It refuses outside herdr ($HC_TERMINAL_ID unset) and when the Lead already has --max-workers live
|
|
31
37
|
Workers (default 3). The name is 1-32 of a-z, 0-9, - and _; it names the worktree too.
|
|
32
38
|
|
|
33
39
|
One line, or one JSON document with --json.
|
|
34
40
|
|
|
35
|
-
Exit 0 started and delivered, 1 started but not delivered, 2 when it cannot run.`;
|
|
41
|
+
Exit 0 started and delivered, 1 started but not delivered, 2 when it cannot run, a failed fetch included.`;
|
|
36
42
|
const DEFAULT_MAX_WORKERS = 3;
|
|
37
43
|
const DEFAULT_WAIT = 30;
|
|
38
44
|
/** hc names a terminal with 1-32 of a-z, 0-9, - and _. */
|
|
@@ -156,7 +162,7 @@ async function start(cli) {
|
|
|
156
162
|
const checkout = repositoryRoot(process.cwd());
|
|
157
163
|
// The Worker's --worktree makes a worktree of the repository itself, never of a linked one.
|
|
158
164
|
const root = mainCheckout(checkout) ?? checkout;
|
|
159
|
-
const worker = resolveConfig(checkout).config
|
|
165
|
+
const { baseBranch, worker } = resolveConfig(checkout).config;
|
|
160
166
|
const command = worker.command.split(/\s+/).filter(Boolean);
|
|
161
167
|
if (command.length === 0) {
|
|
162
168
|
throw new Refusal("worker.command holds no command", "set it in .ai/agentic.config.local.json, or remove the key");
|
|
@@ -167,6 +173,8 @@ async function start(cli) {
|
|
|
167
173
|
if (live.length >= cli.max) {
|
|
168
174
|
throw new Refusal(`${leadId} already has ${live.length} live Workers (the limit is ${cli.max})`, `carry one through first, or raise the limit with --max-workers ${live.length + 1}`);
|
|
169
175
|
}
|
|
176
|
+
// Before hc run, so a failed fetch starts no terminal.
|
|
177
|
+
freshenOrigin(root, baseBranch);
|
|
170
178
|
const space = spaceOf(leadId);
|
|
171
179
|
const { address } = hcObject([
|
|
172
180
|
"run",
|
package/dist/exec.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
// Every short-lived child process the package runs:
|
|
2
|
-
//
|
|
3
|
-
// through `run-command.ts
|
|
1
|
+
// Every short-lived child process the package runs: in a session of its own, so a time limit stops it with
|
|
2
|
+
// every child it started; no terminal, so no prompt, ssh's included; Ctrl-C stops the package, not the
|
|
3
|
+
// tool. A Step of the Gate runs through `run-command.ts`, as long as it takes.
|
|
4
4
|
import { spawnSync } from "node:child_process";
|
|
5
5
|
const LIMIT_MS = 120_000;
|
|
6
6
|
/** For what the network or a repository's size can stretch: a clone, a fetch, a scan of its history. */
|
|
@@ -19,20 +19,39 @@ function limitOf(asked) {
|
|
|
19
19
|
function shown(tool, args) {
|
|
20
20
|
return [tool, ...args].join(" ").replaceAll(/\s+/g, " ").trim().slice(0, SHOWN);
|
|
21
21
|
}
|
|
22
|
+
/** Every process left in the group the tool led; the tool itself is stopped and reaped by now. */
|
|
23
|
+
function stopGroup(pid) {
|
|
24
|
+
// A pid of 0 would make the kill reach this process's own group.
|
|
25
|
+
if (pid <= 0) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
process.kill(-pid, "SIGKILL");
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
// ESRCH: no process is left in the group.
|
|
33
|
+
if (!(error instanceof Error && "code" in error && error.code === "ESRCH")) {
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
22
38
|
/** `tool args` as a child process; a tool's own exit, or its absence, is the caller's. Throws `TimedOut` at the limit. */
|
|
23
39
|
export function exec(tool, args, options = {}) {
|
|
24
40
|
const { cwd, env = process.env, input } = options;
|
|
25
41
|
const limitMs = limitOf(options.limitMs ?? LIMIT_MS);
|
|
26
|
-
const
|
|
42
|
+
const spawnOptions = {
|
|
27
43
|
...(cwd === undefined ? {} : { cwd }),
|
|
28
44
|
...(input === undefined ? {} : { input }),
|
|
45
|
+
detached: true,
|
|
29
46
|
encoding: "utf8",
|
|
30
47
|
env: { ...env, GIT_TERMINAL_PROMPT: "0" },
|
|
31
48
|
killSignal: "SIGKILL",
|
|
32
49
|
maxBuffer: MAX_BUFFER,
|
|
33
50
|
timeout: limitMs
|
|
34
|
-
}
|
|
51
|
+
};
|
|
52
|
+
const result = spawnSync(tool, args, spawnOptions);
|
|
35
53
|
if (result.error !== undefined && "code" in result.error && result.error.code === "ETIMEDOUT") {
|
|
54
|
+
stopGroup(result.pid);
|
|
36
55
|
throw new TimedOut(`${shown(tool, args)} did not answer within ${limitMs / 1000} s`);
|
|
37
56
|
}
|
|
38
57
|
return result;
|
package/dist/pr/decide.js
CHANGED
|
@@ -157,7 +157,7 @@ const ROWS = [
|
|
|
157
157
|
action: "done",
|
|
158
158
|
actor: "dev",
|
|
159
159
|
reason: pr.mergeCommit === null ? "merged" : `merged as ${pr.mergeCommit.slice(0, 7)}`,
|
|
160
|
-
command: shellLine(["git", "push", "origin", "--delete", pr.branch]),
|
|
160
|
+
...(pr.branchStands ? { command: shellLine(["git", "push", "origin", "--delete", pr.branch]) } : {}),
|
|
161
161
|
...(pr.mergeCommit === null ? {} : { details: { mergeCommit: pr.mergeCommit } })
|
|
162
162
|
}),
|
|
163
163
|
alone: true
|
|
@@ -249,14 +249,18 @@ const ROWS = [
|
|
|
249
249
|
}
|
|
250
250
|
},
|
|
251
251
|
{
|
|
252
|
-
|
|
253
|
-
|
|
252
|
+
// A head whose every check was skipped has run nothing: a draft's are, until `ready` starts the run.
|
|
253
|
+
when: (pr) => ciOf(pr).length === 0 || (!pr.draft && ciOf(pr).every((check) => check.bucket === "skipping")),
|
|
254
|
+
answer: (pr, now, policy) => now - Math.max(Date.parse(pr.headCommittedAt), Date.parse(pr.readyAt ?? pr.headCommittedAt)) <=
|
|
255
|
+
GRACE_SECONDS * 1000
|
|
254
256
|
? { status: "wait", action: "wait", actor: "script", reason: "unverified", recheckAfter: 15 }
|
|
255
257
|
: {
|
|
256
258
|
status: "park",
|
|
257
259
|
action: "unverified",
|
|
258
260
|
actor: "dev",
|
|
259
|
-
reason:
|
|
261
|
+
reason: ciOf(pr).length === 0
|
|
262
|
+
? `no check reported on ${pr.head.slice(0, 7)} within ${GRACE_SECONDS} s of its commit`
|
|
263
|
+
: `every check on ${pr.head.slice(0, 7)} was skipped, ${GRACE_SECONDS} s after its commit or its ready`,
|
|
260
264
|
command: devCommand(pr, "merge", policy)
|
|
261
265
|
}
|
|
262
266
|
},
|
package/dist/pr/read.d.ts
CHANGED
|
@@ -39,6 +39,8 @@ export declare function defaultBranch(): string;
|
|
|
39
39
|
export declare function now(): number;
|
|
40
40
|
/** The state of `pr` once more, after an action the script performed on it: the same pull request. */
|
|
41
41
|
export declare function readAgain(pr: PrState, sized: boolean): PrState;
|
|
42
|
+
/** For a test that runs the command in its own process: each case starts with no compare read. */
|
|
43
|
+
export declare function forgetCompares(): void;
|
|
42
44
|
/** Whether the head of `after` carries the base `before` read: the compare shows it behind by none. */
|
|
43
45
|
export declare function carriesBase(before: PrState, after: PrState): boolean;
|
|
44
46
|
/**
|
package/dist/pr/read.js
CHANGED
|
@@ -164,6 +164,10 @@ export function readAgain(pr, sized) {
|
|
|
164
164
|
* once: a compare of two commits never changes.
|
|
165
165
|
*/
|
|
166
166
|
const COMPARES = new Map();
|
|
167
|
+
/** For a test that runs the command in its own process: each case starts with no compare read. */
|
|
168
|
+
export function forgetCompares() {
|
|
169
|
+
COMPARES.clear();
|
|
170
|
+
}
|
|
167
171
|
/** The reply of the compare from `from` to `commit`, read once per process; `what` names it when gh refuses it. */
|
|
168
172
|
function compared(pr, from, commit, what) {
|
|
169
173
|
const key = `${pr.repo} ${from}...${commit}`;
|
package/dist/pr/state.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export declare const MARK_STATE = "failure";
|
|
|
10
10
|
export declare const READ_MARK = "read, findings open";
|
|
11
11
|
/** How the description of the success the dev's `--accept` posts reads: the head, taken with its Findings open. */
|
|
12
12
|
export declare const ACCEPTED = "accepted by the dev, findings open";
|
|
13
|
-
export declare const QUERY = "query($owner: String!, $name: String!, $number: Int!) {\n repository(owner: $owner, name: $name) {\n pullRequest(number: $number) {\n url number state isDraft mergeable mergeStateStatus reviewDecision headRefOid headRefName baseRefName
|
|
13
|
+
export declare const QUERY = "query($owner: String!, $name: String!, $number: Int!) {\n repository(owner: $owner, name: $name) {\n pullRequest(number: $number) {\n url number state isDraft mergeable mergeStateStatus reviewDecision headRefOid headRefName headRef { name } baseRefName\n baseRefOid baseRef { target { oid } } viewerDidAuthor mergeCommit { oid }\n ready: timelineItems(last: 1, itemTypes: [READY_FOR_REVIEW_EVENT]) { nodes { ... on ReadyForReviewEvent { createdAt } } }\n latestReviews(first: 100) { nodes { state author { login } commit { oid } } }\n reviewThreads(first: 100) { pageInfo { hasNextPage } nodes { id isResolved isOutdated path line\n comments(first: 1) { nodes { author { login } body } }\n latest: comments(last: 1) { nodes { viewerDidAuthor } } } }\n commits(last: 1) { nodes { commit { committedDate statusCheckRollup {\n contexts(first: 50) { pageInfo { hasNextPage } nodes {\n ... on CheckRun { name status conclusion detailsUrl startedAt isRequired(pullRequestNumber: $number) }\n ... on StatusContext { context state targetUrl createdAt isRequired(pullRequestNumber: $number) } } } } } } }\n history: commits(last: 100) { nodes { commit { oid parents { totalCount } status { context(name: \"dw-sdlc/review\") { state description } } } } }\n }\n }\n}";
|
|
14
14
|
export type Bucket = "pass" | "fail" | "pending" | "skipping";
|
|
15
15
|
export type CiCheck = {
|
|
16
16
|
name: string;
|
|
@@ -51,7 +51,11 @@ export type PrState = {
|
|
|
51
51
|
viewerDidAuthor: boolean;
|
|
52
52
|
head: string;
|
|
53
53
|
headCommittedAt: string;
|
|
54
|
+
/** When the pull request last left draft; null when it never was one. */
|
|
55
|
+
readyAt: string | null;
|
|
54
56
|
branch: string;
|
|
57
|
+
/** Whether the head branch still stands on the remote: GitHub reports no `headRef` once it is gone. */
|
|
58
|
+
branchStands: boolean;
|
|
55
59
|
base: string;
|
|
56
60
|
/**
|
|
57
61
|
* The commit the base branch points at, read from the branch itself: GitHub's `baseRefOid` lags a
|
package/dist/pr/state.js
CHANGED
|
@@ -18,8 +18,9 @@ const THREADS_READ = 100;
|
|
|
18
18
|
export const QUERY = `query($owner: String!, $name: String!, $number: Int!) {
|
|
19
19
|
repository(owner: $owner, name: $name) {
|
|
20
20
|
pullRequest(number: $number) {
|
|
21
|
-
url number state isDraft mergeable mergeStateStatus reviewDecision headRefOid headRefName baseRefName
|
|
22
|
-
baseRef { target { oid } } viewerDidAuthor mergeCommit { oid }
|
|
21
|
+
url number state isDraft mergeable mergeStateStatus reviewDecision headRefOid headRefName headRef { name } baseRefName
|
|
22
|
+
baseRefOid baseRef { target { oid } } viewerDidAuthor mergeCommit { oid }
|
|
23
|
+
ready: timelineItems(last: 1, itemTypes: [READY_FOR_REVIEW_EVENT]) { nodes { ... on ReadyForReviewEvent { createdAt } } }
|
|
23
24
|
latestReviews(first: 100) { nodes { state author { login } commit { oid } } }
|
|
24
25
|
reviewThreads(first: ${THREADS_READ}) { pageInfo { hasNextPage } nodes { id isResolved isOutdated path line
|
|
25
26
|
comments(first: 1) { nodes { author { login } body } }
|
|
@@ -302,6 +303,7 @@ export function parsePr(reply, repo) {
|
|
|
302
303
|
const head = string(pr, "headRefOid");
|
|
303
304
|
const baseRef = childOrNull(pr, "baseRef");
|
|
304
305
|
const history = historyOf(pr);
|
|
306
|
+
const [ready] = nodes(pr, "ready");
|
|
305
307
|
return {
|
|
306
308
|
url: string(pr, "url"),
|
|
307
309
|
repo,
|
|
@@ -314,7 +316,9 @@ export function parsePr(reply, repo) {
|
|
|
314
316
|
viewerDidAuthor: boolean(pr, "viewerDidAuthor"),
|
|
315
317
|
head,
|
|
316
318
|
headCommittedAt: time(commit, "committedDate"),
|
|
319
|
+
readyAt: ready === undefined ? null : time(ready, "createdAt"),
|
|
317
320
|
branch: string(pr, "headRefName"),
|
|
321
|
+
branchStands: childOrNull(pr, "headRef") !== null,
|
|
318
322
|
base: string(pr, "baseRefName"),
|
|
319
323
|
baseOid: baseRef === null ? string(pr, "baseRefOid") : string(child(baseRef, "target"), "oid"),
|
|
320
324
|
mergeCommit: mergeCommit === null ? null : string(mergeCommit, "oid"),
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fetches origin's default branch, or every branch of origin when no default is known, into the remote-tracking
|
|
3
|
+
* refs at `root`; a clone with no origin has nothing to fetch and is left alone. Throws a `Refusal` when the
|
|
4
|
+
* fetch fails, since a Worker started on a stale base is the fault this prevents.
|
|
5
|
+
*/
|
|
6
|
+
export declare function freshenOrigin(root: string, baseBranch: string): void;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Brings the clone's view of origin's default branch up to date before a Worker starts: Claude Code's
|
|
2
|
+
// `--worktree` branches from the cached `origin/<default>` and fetches it only when the clone has gone a
|
|
3
|
+
// day without a fetch, so a Worker could otherwise begin from a ref that is hours or days behind.
|
|
4
|
+
import { exec, SLOW_LIMIT_MS, TimedOut } from "../exec.js";
|
|
5
|
+
import { repositoryFreeEnv } from "../git-env.js";
|
|
6
|
+
import { asked } from "../working-tree.js";
|
|
7
|
+
import { Refusal } from "./herdr.js";
|
|
8
|
+
const REMOTE_HEAD = "refs/remotes/origin/HEAD";
|
|
9
|
+
const PREFIX = "refs/remotes/origin/";
|
|
10
|
+
/** The branch to fetch: the one origin/HEAD names, else `baseBranch` of the config; undefined when neither names one. */
|
|
11
|
+
function defaultBranch(root, baseBranch) {
|
|
12
|
+
const ref = asked(root, ["symbolic-ref", "-q", REMOTE_HEAD]);
|
|
13
|
+
if (ref !== undefined && ref.startsWith(PREFIX) && ref.length > PREFIX.length) {
|
|
14
|
+
return ref.slice(PREFIX.length);
|
|
15
|
+
}
|
|
16
|
+
return baseBranch === "" || baseBranch === "auto" ? undefined : baseBranch;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Fetches origin's default branch, or every branch of origin when no default is known, into the remote-tracking
|
|
20
|
+
* refs at `root`; a clone with no origin has nothing to fetch and is left alone. Throws a `Refusal` when the
|
|
21
|
+
* fetch fails, since a Worker started on a stale base is the fault this prevents.
|
|
22
|
+
*/
|
|
23
|
+
export function freshenOrigin(root, baseBranch) {
|
|
24
|
+
if (asked(root, ["remote", "get-url", "origin"]) === undefined) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const branch = defaultBranch(root, baseBranch);
|
|
28
|
+
const args = ["-C", root, "fetch", "--quiet", "origin", ...(branch === undefined ? [] : [branch])];
|
|
29
|
+
const fetched = `git fetch origin${branch === undefined ? "" : ` ${branch}`}`;
|
|
30
|
+
const stale = (why) => new Refusal(`${fetched} failed, so the Worker would begin from a stale base: ${why}`, "check the network and the access to origin, then start the Worker again");
|
|
31
|
+
let result;
|
|
32
|
+
try {
|
|
33
|
+
result = exec("git", args, { env: repositoryFreeEnv(), limitMs: SLOW_LIMIT_MS });
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
throw error instanceof TimedOut ? stale(error.message) : error;
|
|
37
|
+
}
|
|
38
|
+
if (result.status !== 0) {
|
|
39
|
+
const said = result.stderr.split("\n").find((line) => line.trim() !== "");
|
|
40
|
+
throw stale(said?.trim() ?? `exited ${result.status ?? "on a signal"}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dw-sdlc",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.2",
|
|
4
4
|
"description": "The Gate and the agentic config of a repository: the dw-sdlc bin with the Gate, the setup, the doctor, the config check, the Sync and setup of References, and the Ship script that drives a pull request to merged; the resolver of the config's layers, its JSON Schema and the validator that reads it.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|