dw-sdlc 0.19.0 → 0.20.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/config/agentic.config.schema.json +2 -2
- package/dist/agentic-config.d.ts +5 -14
- package/dist/agentic-config.js +12 -6
- package/dist/commands/doctor.d.ts +1 -1
- package/dist/commands/doctor.js +1 -1
- package/dist/commands/setup.js +2 -2
- package/dist/doctor-plugins.js +1 -1
- package/dist/pr/decide.js +6 -3
- package/dist/pr/read.js +36 -5
- package/dist/pr/state.d.ts +7 -1
- package/dist/pr/state.js +6 -1
- package/package.json +1 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
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`, `pr.merge` and `pr.review` but never loosen them. A key neither file sets is inferred: the Node package manager from the lockfile
|
|
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 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": {
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
"type": "object",
|
|
93
93
|
"properties": {
|
|
94
94
|
"convention": {
|
|
95
|
-
"description": "The subject preset `pattern` falls back to. `ticket-key`: `[KEY-123] type: subject`, the tracker's key in brackets before a type from a fixed list. `conventional`: Conventional Commits
|
|
95
|
+
"description": "The subject preset `pattern` falls back to. `ticket-key`: `[KEY-123] type: subject`, the tracker's key in brackets before a type from a fixed list. `conventional`: Conventional Commits. Never inferred: with no layer setting this or `pattern`, the subject goes unchecked.",
|
|
96
96
|
"enum": ["ticket-key", "conventional"]
|
|
97
97
|
},
|
|
98
98
|
"pattern": {
|
package/dist/agentic-config.d.ts
CHANGED
|
@@ -125,8 +125,8 @@ export type ResolvedConfig = {
|
|
|
125
125
|
};
|
|
126
126
|
guardrails: Guardrails;
|
|
127
127
|
commit: {
|
|
128
|
-
convention
|
|
129
|
-
pattern
|
|
128
|
+
convention?: CommitConvention;
|
|
129
|
+
pattern?: string;
|
|
130
130
|
trailer?: string;
|
|
131
131
|
};
|
|
132
132
|
gate: Gate;
|
|
@@ -136,14 +136,6 @@ export type ResolvedConfig = {
|
|
|
136
136
|
setup: string[];
|
|
137
137
|
};
|
|
138
138
|
};
|
|
139
|
-
/** The layers merged, before `commit.pattern` falls back to the preset its convention names. */
|
|
140
|
-
type Merged = Omit<ResolvedConfig, "commit"> & {
|
|
141
|
-
commit: {
|
|
142
|
-
convention: CommitConvention;
|
|
143
|
-
pattern?: string;
|
|
144
|
-
trailer?: string;
|
|
145
|
-
};
|
|
146
|
-
};
|
|
147
139
|
export type Problem = {
|
|
148
140
|
file: string;
|
|
149
141
|
path: string;
|
|
@@ -210,13 +202,12 @@ export declare function repositoryRoot(dir: string): string;
|
|
|
210
202
|
export declare function mainCheckout(root: string): string | undefined;
|
|
211
203
|
/**
|
|
212
204
|
* The bottom layer: the package manager the lockfile implies, the Gate command the manifest's `check`
|
|
213
|
-
* script implies, plus the defaults every key falls back to.
|
|
214
|
-
* `resolveConfig
|
|
205
|
+
* script implies, plus the defaults every key falls back to. No commit convention: only a file sets
|
|
206
|
+
* one, and `resolveConfig` fills `commit.pattern` from it when no layer sets a pattern.
|
|
215
207
|
*/
|
|
216
|
-
export declare function inferConfig(root: string):
|
|
208
|
+
export declare function inferConfig(root: string): ResolvedConfig;
|
|
217
209
|
/**
|
|
218
210
|
* Local over committed over inferred, for the repository at `root`. A linked worktree reads a layer it
|
|
219
211
|
* lacks from its main checkout, where a repository that keeps `.ai/` out of git has the only copy.
|
|
220
212
|
*/
|
|
221
213
|
export declare function resolveConfig(root: string): Resolution;
|
|
222
|
-
export {};
|
package/dist/agentic-config.js
CHANGED
|
@@ -207,8 +207,8 @@ function inferGate(root) {
|
|
|
207
207
|
}
|
|
208
208
|
/**
|
|
209
209
|
* The bottom layer: the package manager the lockfile implies, the Gate command the manifest's `check`
|
|
210
|
-
* script implies, plus the defaults every key falls back to.
|
|
211
|
-
* `resolveConfig
|
|
210
|
+
* script implies, plus the defaults every key falls back to. No commit convention: only a file sets
|
|
211
|
+
* one, and `resolveConfig` fills `commit.pattern` from it when no layer sets a pattern.
|
|
212
212
|
*/
|
|
213
213
|
export function inferConfig(root) {
|
|
214
214
|
return {
|
|
@@ -216,7 +216,7 @@ export function inferConfig(root) {
|
|
|
216
216
|
baseBranch: "auto",
|
|
217
217
|
packageManagers: inferPackageManagers(root),
|
|
218
218
|
guardrails: cloneGuardrails(DEFAULT_GUARDRAILS),
|
|
219
|
-
commit: {
|
|
219
|
+
commit: {},
|
|
220
220
|
gate: inferGate(root),
|
|
221
221
|
pr: { ...DEFAULT_PR },
|
|
222
222
|
worker: { ...DEFAULT_WORKER },
|
|
@@ -307,10 +307,16 @@ function prLoosenings(base, local) {
|
|
|
307
307
|
return [{ file: LOCAL_FILE, path: `pr.${key}`, message }];
|
|
308
308
|
});
|
|
309
309
|
}
|
|
310
|
-
/**
|
|
310
|
+
/**
|
|
311
|
+
* `commit.pattern` from the convention's preset when no layer set one; a set pattern wins. Neither
|
|
312
|
+
* set, no pattern: the subject goes unchecked.
|
|
313
|
+
*/
|
|
311
314
|
function finalize(merged) {
|
|
312
|
-
const
|
|
313
|
-
|
|
315
|
+
const { convention, pattern } = merged.commit;
|
|
316
|
+
if (pattern !== undefined || convention === undefined) {
|
|
317
|
+
return merged;
|
|
318
|
+
}
|
|
319
|
+
return { ...merged, commit: { ...merged.commit, pattern: COMMIT_PATTERNS[convention] } };
|
|
314
320
|
}
|
|
315
321
|
/**
|
|
316
322
|
* Local over committed over inferred, for the repository at `root`. A linked worktree reads a layer it
|
package/dist/commands/doctor.js
CHANGED
|
@@ -223,7 +223,7 @@ function checkConfig(config, root) {
|
|
|
223
223
|
return {
|
|
224
224
|
name: "config",
|
|
225
225
|
level: "PASS",
|
|
226
|
-
detail: `commit ${config.config.commit.convention}${borrowed.join("")}`
|
|
226
|
+
detail: `commit ${config.config.commit.convention ?? "unchecked"}${borrowed.join("")}`
|
|
227
227
|
};
|
|
228
228
|
}
|
|
229
229
|
export function diagnose(inputs) {
|
package/dist/commands/setup.js
CHANGED
|
@@ -168,8 +168,8 @@ function localConfig(root, claudeDir, convention, guardrails, worker) {
|
|
|
168
168
|
if (worker !== undefined) {
|
|
169
169
|
config.worker = worker;
|
|
170
170
|
}
|
|
171
|
-
// Only one asked for, by `--convention` or the file's pin:
|
|
172
|
-
//
|
|
171
|
+
// Only one asked for, by `--convention` or the file's pin: pinning one here would override a
|
|
172
|
+
// convention the repository commits later.
|
|
173
173
|
if (convention !== undefined) {
|
|
174
174
|
config.commit = { convention };
|
|
175
175
|
}
|
package/dist/doctor-plugins.js
CHANGED
|
@@ -146,7 +146,7 @@ export function checkSessions(probe, claude) {
|
|
|
146
146
|
if (entry === undefined) {
|
|
147
147
|
const detail = list === undefined
|
|
148
148
|
? "claude plugin list did not answer"
|
|
149
|
-
: `no ${SESSIONS_ID}, so no /sdlc-recap, /sdlc-
|
|
149
|
+
: `no ${SESSIONS_ID}, so no /sdlc-recap, /sdlc-kickoff or /sdlc-delegate. claude plugin install ${SESSIONS_ID} --scope user`;
|
|
150
150
|
return { name, level: "INFO", detail };
|
|
151
151
|
}
|
|
152
152
|
const version = typeof entry.version === "string" ? entry.version : "?";
|
package/dist/pr/decide.js
CHANGED
|
@@ -232,8 +232,10 @@ const ROWS = [
|
|
|
232
232
|
return { ...head, action: "mark-reviewed", actor: "script", details: after };
|
|
233
233
|
}
|
|
234
234
|
const { depth, source } = depthOf(pr, policy);
|
|
235
|
-
// The dev's own depth buys one Review past the limit
|
|
236
|
-
|
|
235
|
+
// The dev's own depth buys one Review past the limit, and only one: the flag rides on every
|
|
236
|
+
// later call. A Review under off needs none.
|
|
237
|
+
const limit = source === "flag" ? REVIEW_LIMIT + 1 : REVIEW_LIMIT;
|
|
238
|
+
if (depth !== "off" && pr.marks.count >= limit) {
|
|
237
239
|
return reviewLimit(pr);
|
|
238
240
|
}
|
|
239
241
|
const byScript = depth === "off";
|
|
@@ -246,7 +248,8 @@ const ROWS = [
|
|
|
246
248
|
source,
|
|
247
249
|
// The agent may raise a routed depth to full, so auto names the command at any depth.
|
|
248
250
|
...(depth === "full" || source === "auto" ? { reviewCommand: policy.reviewCommand } : {}),
|
|
249
|
-
...after
|
|
251
|
+
...after,
|
|
252
|
+
...(pr.scope === null ? {} : { paths: pr.scope })
|
|
250
253
|
}
|
|
251
254
|
};
|
|
252
255
|
}
|
package/dist/pr/read.js
CHANGED
|
@@ -205,16 +205,47 @@ function withCarried(pr) {
|
|
|
205
205
|
return { ...pr, carried: sameChanges(ownChanges(pr, last.commit), ownChanges(pr, pr.head)) };
|
|
206
206
|
}
|
|
207
207
|
/**
|
|
208
|
-
*
|
|
209
|
-
*
|
|
208
|
+
* The files whose own change differs between `from` and the head of `pr`, a rename under both names:
|
|
209
|
+
* what a fix touched, without the files a merge of the base brought in. Null when a compare cannot show
|
|
210
|
+
* the own changes whole.
|
|
211
|
+
*/
|
|
212
|
+
function ownScope(pr, from) {
|
|
213
|
+
const before = ownChanges(pr, from);
|
|
214
|
+
const after = ownChanges(pr, pr.head);
|
|
215
|
+
if (before === null || after === null) {
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
const moved = [
|
|
219
|
+
...after.filter((change) => !before.includes(change)),
|
|
220
|
+
...before.filter((change) => !after.includes(change))
|
|
221
|
+
];
|
|
222
|
+
const names = moved.flatMap((change) => {
|
|
223
|
+
const [path, , previous] = JSON.parse(change);
|
|
224
|
+
return previous === null ? [path] : [path, previous];
|
|
225
|
+
});
|
|
226
|
+
return [...new Set(names)].toSorted();
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* `pr` and, when a Review awaits a head that carries none, its `scope` after a merge of the base, and
|
|
230
|
+
* when `sized`, the files the Review would read: the compare from the last commit a Review read, else
|
|
231
|
+
* from the base, to the head, kept to the scope when there is one.
|
|
210
232
|
*/
|
|
211
233
|
function withChanged(pr, sized) {
|
|
212
|
-
if (
|
|
213
|
-
return { ...pr, changed: null };
|
|
234
|
+
if (pr.carried || pr.state !== "OPEN" || !awaitsReview(pr)) {
|
|
235
|
+
return { ...pr, changed: null, scope: null };
|
|
236
|
+
}
|
|
237
|
+
const scope = pr.lastRead?.merges === true ? ownScope(pr, pr.lastRead.commit) : null;
|
|
238
|
+
if (!sized) {
|
|
239
|
+
return { ...pr, changed: null, scope };
|
|
214
240
|
}
|
|
215
241
|
const from = pr.lastRead?.commit ?? pr.baseOid;
|
|
216
242
|
const reply = compared(pr, from, pr.head, `the compare of ${from.slice(0, 7)}...${pr.head.slice(0, 7)}`);
|
|
217
|
-
|
|
243
|
+
const changed = parseOrCannotRun(() => changedOf(reply));
|
|
244
|
+
if (scope === null || changed === "unsized") {
|
|
245
|
+
return { ...pr, changed, scope };
|
|
246
|
+
}
|
|
247
|
+
const within = changed.filter(({ path, previous }) => scope.includes(path) || (previous !== null && scope.includes(previous)));
|
|
248
|
+
return { ...pr, changed: within, scope };
|
|
218
249
|
}
|
|
219
250
|
/** The state of pull request `number` of `repo`, from one GraphQL request and the compares a Review needs. */
|
|
220
251
|
function query({ repo, number }, sized) {
|
package/dist/pr/state.d.ts
CHANGED
|
@@ -88,6 +88,11 @@ export type PrState = {
|
|
|
88
88
|
* auto` to route on: `unsized` when the compare cannot list them all, null when not read.
|
|
89
89
|
*/
|
|
90
90
|
changed: ChangedFile[] | "unsized" | null;
|
|
91
|
+
/**
|
|
92
|
+
* The files whose own change moved since `lastRead`, when a merge of the base came after it: the files
|
|
93
|
+
* a fix touched, the base's own left out. Null with no such merge, or when a compare cannot show them.
|
|
94
|
+
*/
|
|
95
|
+
scope: string[] | null;
|
|
91
96
|
};
|
|
92
97
|
/**
|
|
93
98
|
* A file a Review would read: its name, its name before a rename, and the lines it adds and removes,
|
|
@@ -106,6 +111,7 @@ export type LastRead = {
|
|
|
106
111
|
commit: string;
|
|
107
112
|
clean: boolean;
|
|
108
113
|
mergesOnly: boolean;
|
|
114
|
+
merges: boolean;
|
|
109
115
|
};
|
|
110
116
|
/**
|
|
111
117
|
* The Reviews that left a Finding open since the last review success, a success under off and the dev's
|
|
@@ -144,5 +150,5 @@ export declare function parseView(reply: string): {
|
|
|
144
150
|
/** The default branch of the repository whose `gh repo view --json defaultBranchRef` printed `reply`. */
|
|
145
151
|
export declare function parseDefaultBranch(reply: string): string;
|
|
146
152
|
/** The state of the pull request in `reply`, the text `gh api graphql` printed for QUERY on `repo`. */
|
|
147
|
-
export declare function parsePr(reply: string, repo: string): Omit<PrState, "carried" | "changed">;
|
|
153
|
+
export declare function parsePr(reply: string, repo: string): Omit<PrState, "carried" | "changed" | "scope">;
|
|
148
154
|
export {};
|
package/dist/pr/state.js
CHANGED
|
@@ -253,7 +253,12 @@ function lastReadOf(history, head) {
|
|
|
253
253
|
if (last === undefined) {
|
|
254
254
|
return null;
|
|
255
255
|
}
|
|
256
|
-
return {
|
|
256
|
+
return {
|
|
257
|
+
commit: last.oid,
|
|
258
|
+
clean: last.read === "clean",
|
|
259
|
+
mergesOnly: mergesOnlyAfter(index),
|
|
260
|
+
merges: commits.slice(index + 1).some(({ merge }) => merge)
|
|
261
|
+
};
|
|
257
262
|
}
|
|
258
263
|
/**
|
|
259
264
|
* Whether a non-merge commit follows `commit` in the history, oldest first, as `lastReadOf` judges a read
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dw-sdlc",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
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": {
|