@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.
Files changed (36) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +9 -0
  3. package/dist/analyzers/developer/gather.d.ts +16 -0
  4. package/dist/analyzers/developer/gather.d.ts.map +1 -1
  5. package/dist/analyzers/developer/gather.js +2 -0
  6. package/dist/analyzers/developer/gather.js.map +1 -1
  7. package/dist/analyzers/developer/ownership.d.ts +86 -0
  8. package/dist/analyzers/developer/ownership.d.ts.map +1 -0
  9. package/dist/analyzers/developer/ownership.js +180 -0
  10. package/dist/analyzers/developer/ownership.js.map +1 -0
  11. package/dist/analyzers/quality/detailed.d.ts +5 -1
  12. package/dist/analyzers/quality/detailed.d.ts.map +1 -1
  13. package/dist/analyzers/quality/detailed.js +30 -29
  14. package/dist/analyzers/quality/detailed.js.map +1 -1
  15. package/dist/analyzers/security/detailed.d.ts +7 -1
  16. package/dist/analyzers/security/detailed.d.ts.map +1 -1
  17. package/dist/analyzers/security/detailed.js +31 -15
  18. package/dist/analyzers/security/detailed.js.map +1 -1
  19. package/dist/analyzers/tests/detailed.d.ts +5 -1
  20. package/dist/analyzers/tests/detailed.d.ts.map +1 -1
  21. package/dist/analyzers/tests/detailed.js +27 -20
  22. package/dist/analyzers/tests/detailed.js.map +1 -1
  23. package/dist/attribution/attribute.d.ts +57 -0
  24. package/dist/attribution/attribute.d.ts.map +1 -0
  25. package/dist/attribution/attribute.js +149 -0
  26. package/dist/attribution/attribute.js.map +1 -0
  27. package/dist/cli.d.ts.map +1 -1
  28. package/dist/cli.js +53 -5
  29. package/dist/cli.js.map +1 -1
  30. package/dist/reviewers-cli.d.ts +57 -0
  31. package/dist/reviewers-cli.d.ts.map +1 -0
  32. package/dist/reviewers-cli.js +263 -0
  33. package/dist/reviewers-cli.js.map +1 -0
  34. package/package.json +1 -1
  35. package/templates/.claude/skills/dxkit-action/SKILL.md +2 -0
  36. 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vyuhlabs/dxkit",
3
- "version": "2.9.3",
3
+ "version": "2.9.4",
4
4
  "description": "AI-native developer experience toolkit for any codebase",
5
5
  "license": "MIT",
6
6
  "author": "Vyuh Labs",
@@ -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