@vyuhlabs/dxkit 2.9.3 → 2.9.4
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 +32 -0
- package/README.md +9 -0
- package/dist/analyzers/developer/gather.d.ts +16 -0
- package/dist/analyzers/developer/gather.d.ts.map +1 -1
- package/dist/analyzers/developer/gather.js +2 -0
- package/dist/analyzers/developer/gather.js.map +1 -1
- package/dist/analyzers/developer/ownership.d.ts +86 -0
- package/dist/analyzers/developer/ownership.d.ts.map +1 -0
- package/dist/analyzers/developer/ownership.js +180 -0
- package/dist/analyzers/developer/ownership.js.map +1 -0
- package/dist/analyzers/quality/detailed.d.ts +5 -1
- package/dist/analyzers/quality/detailed.d.ts.map +1 -1
- package/dist/analyzers/quality/detailed.js +30 -29
- package/dist/analyzers/quality/detailed.js.map +1 -1
- package/dist/analyzers/security/detailed.d.ts +7 -1
- package/dist/analyzers/security/detailed.d.ts.map +1 -1
- package/dist/analyzers/security/detailed.js +31 -15
- package/dist/analyzers/security/detailed.js.map +1 -1
- package/dist/analyzers/tests/detailed.d.ts +5 -1
- package/dist/analyzers/tests/detailed.d.ts.map +1 -1
- package/dist/analyzers/tests/detailed.js +27 -20
- package/dist/analyzers/tests/detailed.js.map +1 -1
- package/dist/attribution/attribute.d.ts +57 -0
- package/dist/attribution/attribute.d.ts.map +1 -0
- package/dist/attribution/attribute.js +149 -0
- package/dist/attribution/attribute.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +53 -5
- package/dist/cli.js.map +1 -1
- package/dist/reviewers-cli.d.ts +57 -0
- package/dist/reviewers-cli.d.ts.map +1 -0
- package/dist/reviewers-cli.js +263 -0
- package/dist/reviewers-cli.js.map +1 -0
- package/package.json +1 -1
- package/templates/.claude/skills/dxkit-action/SKILL.md +2 -0
- package/templates/.claude/skills/dxkit-pr/SKILL.md +22 -1
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.parseCodeowners = parseCodeowners;
|
|
37
|
+
exports.matchCodeowners = matchCodeowners;
|
|
38
|
+
exports.buildSuggestions = buildSuggestions;
|
|
39
|
+
exports.runReviewers = runReviewers;
|
|
40
|
+
/**
|
|
41
|
+
* `vyuh-dxkit reviewers` — suggest reviewers for a change, grounded on the
|
|
42
|
+
* active-owner model (dev-report git history) rather than naive last-touch
|
|
43
|
+
* blame, blended with CODEOWNERS.
|
|
44
|
+
*
|
|
45
|
+
* The differentiation over a platform's built-in suggested-reviewers: the
|
|
46
|
+
* candidates are activity-weighted (recent, sustained work ranks highest),
|
|
47
|
+
* scoped to who is still active (a departed owner is never silently
|
|
48
|
+
* suggested), bot-free, exclude the change's own author, and carry a
|
|
49
|
+
* bus-factor signal. CODEOWNERS, when present, is authoritative and merged
|
|
50
|
+
* in. Output renders names + GitHub @handles — never raw emails.
|
|
51
|
+
*
|
|
52
|
+
* Pure helpers (`parseCodeowners`, `matchCodeowners`, `buildSuggestions`)
|
|
53
|
+
* are unit-tested without git; `runReviewers` is the IO entry point.
|
|
54
|
+
*/
|
|
55
|
+
const fs = __importStar(require("fs"));
|
|
56
|
+
const path = __importStar(require("path"));
|
|
57
|
+
const child_process_1 = require("child_process");
|
|
58
|
+
const logger = __importStar(require("./logger"));
|
|
59
|
+
const ownership_1 = require("./analyzers/developer/ownership");
|
|
60
|
+
const gather_1 = require("./analyzers/developer/gather");
|
|
61
|
+
/**
|
|
62
|
+
* Parse a CODEOWNERS file. Each non-comment line is `<pattern> <owner>...`.
|
|
63
|
+
* Returns rules in file order; CODEOWNERS semantics are "last matching rule
|
|
64
|
+
* wins," so callers iterate in reverse.
|
|
65
|
+
*/
|
|
66
|
+
function parseCodeowners(content) {
|
|
67
|
+
const rules = [];
|
|
68
|
+
for (const raw of content.split(/\r?\n/)) {
|
|
69
|
+
const line = raw.replace(/#.*$/, '').trim();
|
|
70
|
+
if (!line)
|
|
71
|
+
continue;
|
|
72
|
+
const parts = line.split(/\s+/);
|
|
73
|
+
const pattern = parts[0];
|
|
74
|
+
const owners = parts.slice(1).filter(Boolean);
|
|
75
|
+
if (pattern && owners.length > 0)
|
|
76
|
+
rules.push({ pattern, owners });
|
|
77
|
+
}
|
|
78
|
+
return rules;
|
|
79
|
+
}
|
|
80
|
+
/** Translate a CODEOWNERS pattern to a RegExp (subset: `*`, `**`, leading
|
|
81
|
+
* `/`, trailing `/`). Good enough for the common cases; unmatched exotic
|
|
82
|
+
* globs simply don't match (conservative — never over-claims ownership). */
|
|
83
|
+
function patternToRegExp(pattern) {
|
|
84
|
+
let p = pattern;
|
|
85
|
+
const anchored = p.startsWith('/');
|
|
86
|
+
if (anchored)
|
|
87
|
+
p = p.slice(1);
|
|
88
|
+
const dirOnly = p.endsWith('/');
|
|
89
|
+
if (dirOnly)
|
|
90
|
+
p = p.slice(0, -1);
|
|
91
|
+
// Tokenize so `**` -> `.*` and `*` -> `[^/]*` without a fragile placeholder;
|
|
92
|
+
// every other regex-special char is escaped.
|
|
93
|
+
let body = '';
|
|
94
|
+
for (let i = 0; i < p.length; i++) {
|
|
95
|
+
const c = p[i];
|
|
96
|
+
if (c === '*') {
|
|
97
|
+
if (p[i + 1] === '*') {
|
|
98
|
+
body += '.*';
|
|
99
|
+
i++;
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
body += '[^/]*';
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
else if (/[.+^${}()|[\]\\?]/.test(c)) {
|
|
106
|
+
body += '\\' + c;
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
body += c;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const full = dirOnly ? `${body}/.*` : `${body}(?:/.*)?`;
|
|
113
|
+
return new RegExp(anchored ? `^${full}$` : `(?:^|/)${full}$`);
|
|
114
|
+
}
|
|
115
|
+
/** Owners for one file: the LAST matching CODEOWNERS rule wins. Empty when
|
|
116
|
+
* no rule matches. */
|
|
117
|
+
function matchCodeowners(rules, file) {
|
|
118
|
+
for (let i = rules.length - 1; i >= 0; i--) {
|
|
119
|
+
if (patternToRegExp(rules[i].pattern).test(file))
|
|
120
|
+
return [...rules[i].owners];
|
|
121
|
+
}
|
|
122
|
+
return [];
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Compose the active-owner ranking with CODEOWNERS into a ranked reviewer
|
|
126
|
+
* list. Pure. CODEOWNERS owners are authoritative (always included, flagged);
|
|
127
|
+
* active git owners follow, ranked by score; inactive owners are dropped from
|
|
128
|
+
* the suggestion list (they're surfaced only via the `note` fallback).
|
|
129
|
+
*/
|
|
130
|
+
function buildSuggestions(ownership, opts = {}) {
|
|
131
|
+
const limit = opts.limit ?? 3;
|
|
132
|
+
const codeowners = dedupe(opts.codeowners ?? []);
|
|
133
|
+
const out = [];
|
|
134
|
+
const seen = new Set();
|
|
135
|
+
// 1. CODEOWNERS first — authoritative.
|
|
136
|
+
for (const token of codeowners) {
|
|
137
|
+
const handle = token.startsWith('@') ? token.slice(1) : undefined;
|
|
138
|
+
const key = token.toLowerCase();
|
|
139
|
+
if (seen.has(key))
|
|
140
|
+
continue;
|
|
141
|
+
seen.add(key);
|
|
142
|
+
out.push({
|
|
143
|
+
name: token,
|
|
144
|
+
...(handle ? { handle } : {}),
|
|
145
|
+
active: true, // CODEOWNERS is a maintained, current declaration
|
|
146
|
+
isCodeowner: true,
|
|
147
|
+
reason: 'listed in CODEOWNERS for the touched paths',
|
|
148
|
+
score: Number.POSITIVE_INFINITY,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
// 2. Active git owners, ranked.
|
|
152
|
+
const activeOwners = ownership.ranked.filter((o) => o.active);
|
|
153
|
+
for (const o of activeOwners) {
|
|
154
|
+
const handle = o.githubHandle;
|
|
155
|
+
const key = (handle ? `@${handle}` : o.name).toLowerCase();
|
|
156
|
+
if (seen.has(key))
|
|
157
|
+
continue;
|
|
158
|
+
seen.add(key);
|
|
159
|
+
out.push({
|
|
160
|
+
name: o.name,
|
|
161
|
+
...(handle ? { handle } : {}),
|
|
162
|
+
active: true,
|
|
163
|
+
isCodeowner: false,
|
|
164
|
+
reason: `${o.commits} recent commit${o.commits === 1 ? '' : 's'} to the touched files (last ${o.lastTouched})`,
|
|
165
|
+
score: o.score,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
const reviewers = out.slice(0, limit);
|
|
169
|
+
let note;
|
|
170
|
+
if (activeOwners.length === 0 && codeowners.length === 0) {
|
|
171
|
+
note =
|
|
172
|
+
ownership.ranked.length > 0
|
|
173
|
+
? 'Every contributor who has touched these files is inactive — no current owner to suggest. Route by current team ownership.'
|
|
174
|
+
: 'No git history or CODEOWNERS for the touched files — no reviewer signal.';
|
|
175
|
+
}
|
|
176
|
+
else if (activeOwners.length === 0) {
|
|
177
|
+
note = 'Original authors are inactive — suggesting by CODEOWNERS only.';
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
touchedFiles: [],
|
|
181
|
+
reviewers,
|
|
182
|
+
busFactor: ownership.busFactor,
|
|
183
|
+
...(note ? { note } : {}),
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
function dedupe(xs) {
|
|
187
|
+
const seen = new Set();
|
|
188
|
+
const out = [];
|
|
189
|
+
for (const x of xs) {
|
|
190
|
+
const k = x.toLowerCase();
|
|
191
|
+
if (!seen.has(k)) {
|
|
192
|
+
seen.add(k);
|
|
193
|
+
out.push(x);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return out;
|
|
197
|
+
}
|
|
198
|
+
function gitOut(cmd, cwd) {
|
|
199
|
+
try {
|
|
200
|
+
return (0, child_process_1.execSync)(cmd, { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
return '';
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
function touchedFiles(cwd, opts) {
|
|
207
|
+
if (opts.staged) {
|
|
208
|
+
return gitOut('git diff --cached --name-only', cwd).split('\n').filter(Boolean);
|
|
209
|
+
}
|
|
210
|
+
const base = opts.base || gitOut('git rev-parse --abbrev-ref origin/HEAD', cwd) || 'origin/main';
|
|
211
|
+
const out = gitOut(`git diff --name-only ${base}...HEAD`, cwd);
|
|
212
|
+
return out.split('\n').filter(Boolean);
|
|
213
|
+
}
|
|
214
|
+
function readCodeowners(cwd) {
|
|
215
|
+
for (const rel of ['.github/CODEOWNERS', 'CODEOWNERS', 'docs/CODEOWNERS']) {
|
|
216
|
+
const p = path.join(cwd, rel);
|
|
217
|
+
if (fs.existsSync(p))
|
|
218
|
+
return parseCodeowners(fs.readFileSync(p, 'utf8'));
|
|
219
|
+
}
|
|
220
|
+
return [];
|
|
221
|
+
}
|
|
222
|
+
function runReviewers(cwd, opts) {
|
|
223
|
+
const files = touchedFiles(cwd, opts);
|
|
224
|
+
if (files.length === 0) {
|
|
225
|
+
logger.info('No changed files detected (pass --base <ref> or --staged). Nothing to suggest.');
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
// Exclude the change author from suggestions (never review your own PR).
|
|
229
|
+
const authorEmail = gitOut('git config --get user.email', cwd);
|
|
230
|
+
const excludeEmails = authorEmail ? new Set([(0, gather_1.normalizeEmail)(authorEmail)]) : undefined;
|
|
231
|
+
const ownership = (0, ownership_1.ownersFor)(cwd, files, { ...(excludeEmails ? { excludeEmails } : {}) });
|
|
232
|
+
// CODEOWNERS owners for the touched files (deduped across files).
|
|
233
|
+
const rules = readCodeowners(cwd);
|
|
234
|
+
const coOwners = [];
|
|
235
|
+
for (const f of files)
|
|
236
|
+
coOwners.push(...matchCodeowners(rules, f));
|
|
237
|
+
const result = {
|
|
238
|
+
...buildSuggestions(ownership, {
|
|
239
|
+
...(opts.limit !== undefined ? { limit: opts.limit } : {}),
|
|
240
|
+
codeowners: coOwners,
|
|
241
|
+
}),
|
|
242
|
+
touchedFiles: files,
|
|
243
|
+
};
|
|
244
|
+
if (opts.json) {
|
|
245
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
logger.info(`Suggested reviewers for ${files.length} changed file${files.length === 1 ? '' : 's'}:`);
|
|
249
|
+
if (result.reviewers.length === 0) {
|
|
250
|
+
logger.info(' (none)');
|
|
251
|
+
}
|
|
252
|
+
for (const r of result.reviewers) {
|
|
253
|
+
const who = r.handle ? `@${r.handle}` : r.name;
|
|
254
|
+
const tag = r.isCodeowner ? ' [CODEOWNERS]' : '';
|
|
255
|
+
logger.info(` ${who}${tag} — ${r.reason}`);
|
|
256
|
+
}
|
|
257
|
+
if (result.busFactor === 1) {
|
|
258
|
+
logger.warn(' Bus factor 1: a single active owner covers these files — consider spreading knowledge.');
|
|
259
|
+
}
|
|
260
|
+
if (result.note)
|
|
261
|
+
logger.dim(` ${result.note}`);
|
|
262
|
+
}
|
|
263
|
+
//# sourceMappingURL=reviewers-cli.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reviewers-cli.js","sourceRoot":"","sources":["../src/reviewers-cli.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -28,6 +28,8 @@ npx vyuh-dxkit vulnerabilities --detailed --graph-context # or test-gaps / qua
|
|
|
28
28
|
|
|
29
29
|
`--graph-context` adds a "Graph context" column (the module a finding lives in + its blast radius — how many files call into it) so you can plan the fix without separately discovering structure. It's a structural HINT, not ground truth — read "Graph context" below for how to use it safely.
|
|
30
30
|
|
|
31
|
+
Add **`--attribute`** when you need to know *who to ask* about a pre-existing finding — it adds a "Who to ask" column from `git blame` routed through the active-owner model, so an inactive author is forwarded to the file's current owner (names + @handles, never emails). It's opt-in and **historical only**: a net-new finding the guardrail just blocked was introduced by your own change, so "who to ask" there is simply the PR author — no blame needed. Honor the honesty caveat (blame is last-touch, not necessarily who introduced it).
|
|
32
|
+
|
|
31
33
|
## Scoped fix — fix one category at a time
|
|
32
34
|
|
|
33
35
|
Often the user doesn't want "fix everything" — they want to burn down **one
|
|
@@ -78,6 +78,22 @@ Put in the body:
|
|
|
78
78
|
- **Score deltas** — only when the change targeted a dimension (e.g. "Tests
|
|
79
79
|
62 → 71 after closing the auth gaps"). Don't pad with unchanged scores.
|
|
80
80
|
|
|
81
|
+
### Suggested reviewers
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
npx vyuh-dxkit reviewers --base <base-branch> --json
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
This ranks reviewers by the **active-owner model** — recency-weighted git
|
|
88
|
+
history on the touched files, with bots and departed contributors filtered, the
|
|
89
|
+
PR author excluded, blended with `CODEOWNERS`. Better signal than the platform's
|
|
90
|
+
naive last-touch suggestion. Surface the top few in the body with the *why*
|
|
91
|
+
("@alice — owns 3/4 touched files, active"), and you can pass them to
|
|
92
|
+
`gh pr create --reviewer`. Honor the output's caveats: if it returns a
|
|
93
|
+
`busFactor: 1`, note the single-point-of-failure; if it returns a `note`
|
|
94
|
+
(original authors inactive / no signal), say so rather than inventing a
|
|
95
|
+
reviewer. Renders `@handle`s, never emails.
|
|
96
|
+
|
|
81
97
|
## [4] Draft — title, body, reviewer checklist
|
|
82
98
|
|
|
83
99
|
**Title** — imperative, scoped, specific. `feat(auth): add refresh-token
|
|
@@ -100,6 +116,10 @@ rotation`, not `Updates`. Match the repo's existing PR/commit convention
|
|
|
100
116
|
- Allowlist: <new suppressions + reason + expiry, or "no changes">
|
|
101
117
|
- Scores: <dimension deltas, if the change targeted one>
|
|
102
118
|
|
|
119
|
+
## Suggested reviewers
|
|
120
|
+
- @alice — owns 3/4 touched files, active · @bob — CODEOWNERS
|
|
121
|
+
(or the `note` when there's no active-owner signal)
|
|
122
|
+
|
|
103
123
|
## Reviewer checklist
|
|
104
124
|
- [ ] Change matches the description; scope isn't broader than stated
|
|
105
125
|
- [ ] <feature>: behavior verified (how to exercise it)
|
|
@@ -120,7 +140,8 @@ Show the user the full draft (title + body) and confirm. On yes:
|
|
|
120
140
|
|
|
121
141
|
```bash
|
|
122
142
|
git push -u origin HEAD # if not already pushed
|
|
123
|
-
gh pr create --base main --title "<title>" --body "<body>"
|
|
143
|
+
gh pr create --base main --title "<title>" --body "<body>" \
|
|
144
|
+
--reviewer <handle1>,<handle2> # the active owners from `reviewers`, if any
|
|
124
145
|
```
|
|
125
146
|
|
|
126
147
|
If `gh` isn't authenticated, print the title + body for the user to paste, and
|