@vyuhlabs/dxkit 2.9.2 → 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 +68 -0
- package/README.md +20 -9
- 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/actions.d.ts +18 -1
- package/dist/analyzers/tests/actions.d.ts.map +1 -1
- package/dist/analyzers/tests/actions.js +37 -1
- package/dist/analyzers/tests/actions.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 +42 -23
- package/dist/analyzers/tests/detailed.js.map +1 -1
- package/dist/analyzers/tests/types.d.ts +10 -0
- package/dist/analyzers/tests/types.d.ts.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/generator.d.ts.map +1 -1
- package/dist/generator.js +12 -0
- package/dist/generator.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 +42 -1
- package/templates/.claude/skills/dxkit-docs/SKILL.md +2 -0
- package/templates/.claude/skills/dxkit-feature/SKILL.md +14 -3
- package/templates/.claude/skills/dxkit-init/SKILL.md +1 -1
- package/templates/.claude/skills/dxkit-onboard/SKILL.md +2 -2
- package/templates/.claude/skills/dxkit-pr/SKILL.md +163 -0
- package/templates/.claude/skills/dxkit-reports/SKILL.md +1 -1
- package/templates/.claude/skills/dxkit-test/SKILL.md +130 -0
- package/templates/.claude/skills/dxkit-update/SKILL.md +4 -0
- package/templates/AGENTS.md.template +9 -3
- package/templates/CLAUDE.md.template +9 -3
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseBlamePorcelain = parseBlamePorcelain;
|
|
4
|
+
exports.buildAttributionMap = buildAttributionMap;
|
|
5
|
+
exports.formatAttributionCell = formatAttributionCell;
|
|
6
|
+
exports.attributionProvenanceLine = attributionProvenanceLine;
|
|
7
|
+
/**
|
|
8
|
+
* Finding attribution — "who to ask" about a finding, grounded in the
|
|
9
|
+
* active-owner model. The OPT-IN historical counterpart to net-new
|
|
10
|
+
* attribution (a net-new finding's introducer is the PR's own commits;
|
|
11
|
+
* that needs no blame). This module answers it for pre-existing findings
|
|
12
|
+
* via `git blame`, then routes through who is still active.
|
|
13
|
+
*
|
|
14
|
+
* Honesty: `git blame` reports who LAST TOUCHED a line, not necessarily who
|
|
15
|
+
* introduced the finding — a formatter run or a move reassigns it. The
|
|
16
|
+
* provenance line states this; the routing softens it by pointing at the
|
|
17
|
+
* current owner when the blamed author has left.
|
|
18
|
+
*
|
|
19
|
+
* Privacy: emails are the internal join key only. Rendered output is the
|
|
20
|
+
* display name + GitHub @handle, never a raw email (same posture as the
|
|
21
|
+
* ownership model).
|
|
22
|
+
*
|
|
23
|
+
* Shape mirrors `explore/finding-context.ts` so threading `--attribute`
|
|
24
|
+
* through the detailed reports is identical to `--graph-context`.
|
|
25
|
+
*/
|
|
26
|
+
const child_process_1 = require("child_process");
|
|
27
|
+
const ownership_1 = require("../analyzers/developer/ownership");
|
|
28
|
+
const gather_1 = require("../analyzers/developer/gather");
|
|
29
|
+
const finding_context_1 = require("../explore/finding-context");
|
|
30
|
+
/** Parse `git blame --porcelain -L n,n` output for the author + commit of a
|
|
31
|
+
* single line. Pure — exported for tests. Returns `null` when the porcelain
|
|
32
|
+
* has no author (empty / error). */
|
|
33
|
+
function parseBlamePorcelain(out) {
|
|
34
|
+
const lines = out.split('\n');
|
|
35
|
+
const commit = lines[0]?.split(' ')[0];
|
|
36
|
+
let author = '';
|
|
37
|
+
let email = '';
|
|
38
|
+
for (const l of lines) {
|
|
39
|
+
if (l.startsWith('author '))
|
|
40
|
+
author = l.slice('author '.length).trim();
|
|
41
|
+
else if (l.startsWith('author-mail '))
|
|
42
|
+
email = l.slice('author-mail '.length).trim().replace(/^<|>$/g, '');
|
|
43
|
+
}
|
|
44
|
+
if (!commit || !author)
|
|
45
|
+
return null;
|
|
46
|
+
return { author, email, commit: commit.slice(0, 8) };
|
|
47
|
+
}
|
|
48
|
+
function blameLine(cwd, file, line) {
|
|
49
|
+
try {
|
|
50
|
+
const out = (0, child_process_1.execSync)(`git blame --porcelain -L ${line},${line} -- '${file.replace(/'/g, "'\\''")}'`, { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
|
|
51
|
+
return parseBlamePorcelain(out);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Build per-finding attribution for a list of locations. Fail-open: returns
|
|
59
|
+
* `undefined` when git produces nothing usable. Only locations with a `line`
|
|
60
|
+
* are blamed (a file-level finding has no single line to attribute).
|
|
61
|
+
*/
|
|
62
|
+
function buildAttributionMap(cwd, locations, opts = {}) {
|
|
63
|
+
const max = opts.maxFindings ?? 200;
|
|
64
|
+
const now = opts.now ?? new Date();
|
|
65
|
+
const activeEmails = (0, ownership_1.gatherActiveEmails)(cwd, opts.activeSince ?? '6 months ago');
|
|
66
|
+
const attributions = {};
|
|
67
|
+
const currentOwnerCache = new Map();
|
|
68
|
+
let blamed = 0;
|
|
69
|
+
for (const loc of locations) {
|
|
70
|
+
if (blamed >= max)
|
|
71
|
+
break;
|
|
72
|
+
const key = (0, finding_context_1.locationKey)(loc.file, loc.line);
|
|
73
|
+
if (key in attributions)
|
|
74
|
+
continue;
|
|
75
|
+
// File-level finding (no line, e.g. a test gap): attribute to the
|
|
76
|
+
// file's current active owner — "who should test/own this," not who
|
|
77
|
+
// wrote a specific line.
|
|
78
|
+
if (typeof loc.line !== 'number') {
|
|
79
|
+
if (!currentOwnerCache.has(loc.file)) {
|
|
80
|
+
currentOwnerCache.set(loc.file, topActiveOwner(cwd, loc.file, now));
|
|
81
|
+
}
|
|
82
|
+
const owner = currentOwnerCache.get(loc.file);
|
|
83
|
+
if (owner) {
|
|
84
|
+
attributions[key] = {
|
|
85
|
+
author: owner.name,
|
|
86
|
+
...(owner.handle ? { handle: owner.handle } : {}),
|
|
87
|
+
active: true,
|
|
88
|
+
fileLevel: true,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
const bl = blameLine(cwd, loc.file, loc.line);
|
|
94
|
+
blamed++;
|
|
95
|
+
if (!bl)
|
|
96
|
+
continue;
|
|
97
|
+
const active = activeEmails.has((0, gather_1.normalizeEmail)(bl.email));
|
|
98
|
+
const handle = (0, ownership_1.handleFromEmail)(bl.email);
|
|
99
|
+
let currentOwner;
|
|
100
|
+
if (!active) {
|
|
101
|
+
if (!currentOwnerCache.has(loc.file)) {
|
|
102
|
+
currentOwnerCache.set(loc.file, topActiveOwner(cwd, loc.file, now));
|
|
103
|
+
}
|
|
104
|
+
currentOwner = currentOwnerCache.get(loc.file);
|
|
105
|
+
}
|
|
106
|
+
attributions[key] = {
|
|
107
|
+
author: bl.author,
|
|
108
|
+
...(handle ? { handle } : {}),
|
|
109
|
+
commit: bl.commit,
|
|
110
|
+
active,
|
|
111
|
+
...(currentOwner ? { currentOwner } : {}),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
if (Object.keys(attributions).length === 0)
|
|
115
|
+
return undefined;
|
|
116
|
+
return { attributions };
|
|
117
|
+
}
|
|
118
|
+
function topActiveOwner(cwd, file, now) {
|
|
119
|
+
const owners = (0, ownership_1.ownersFor)(cwd, [file], { now });
|
|
120
|
+
const top = owners.ranked.find((o) => o.active);
|
|
121
|
+
if (!top)
|
|
122
|
+
return undefined;
|
|
123
|
+
return { name: top.name, ...(top.githubHandle ? { handle: top.githubHandle } : {}) };
|
|
124
|
+
}
|
|
125
|
+
/** Compact cell rendering — name/@handle, never email. `—` when unresolved. */
|
|
126
|
+
function formatAttributionCell(attr) {
|
|
127
|
+
if (!attr)
|
|
128
|
+
return '—';
|
|
129
|
+
const who = attr.handle ? `@${attr.handle}` : attr.author;
|
|
130
|
+
if (attr.fileLevel)
|
|
131
|
+
return `${who} (owner)`;
|
|
132
|
+
if (attr.active)
|
|
133
|
+
return `${who} (active)`;
|
|
134
|
+
if (attr.currentOwner) {
|
|
135
|
+
const owner = attr.currentOwner.handle
|
|
136
|
+
? `@${attr.currentOwner.handle}`
|
|
137
|
+
: attr.currentOwner.name;
|
|
138
|
+
return `${who} (inactive) → ask ${owner}`;
|
|
139
|
+
}
|
|
140
|
+
return `${who} (inactive)`;
|
|
141
|
+
}
|
|
142
|
+
/** Provenance + honesty line printed above an attributed section. */
|
|
143
|
+
function attributionProvenanceLine() {
|
|
144
|
+
return (`_"Who to ask" is the author who LAST TOUCHED the line (\`git blame\`), not ` +
|
|
145
|
+
`necessarily who introduced the finding — a formatter run or a move can reassign ` +
|
|
146
|
+
`it. An inactive author is routed to the file's current active owner. Names + ` +
|
|
147
|
+
`GitHub @handles only; never emails._`);
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=attribute.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attribute.js","sourceRoot":"","sources":["../../src/attribution/attribute.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAwRA,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAizDvD"}
|
package/dist/cli.js
CHANGED
|
@@ -82,6 +82,19 @@ async function buildGraphContextIfRequested(enabled, cwd, locations) {
|
|
|
82
82
|
logger.dim(`--graph-context: attached to ${enriched}/${locations.length} finding location(s).`);
|
|
83
83
|
return gc;
|
|
84
84
|
}
|
|
85
|
+
async function buildAttributionIfRequested(enabled, cwd, locations) {
|
|
86
|
+
if (!enabled)
|
|
87
|
+
return undefined;
|
|
88
|
+
const { buildAttributionMap } = await Promise.resolve().then(() => __importStar(require('./attribution/attribute')));
|
|
89
|
+
const attr = buildAttributionMap(cwd, locations);
|
|
90
|
+
if (!attr) {
|
|
91
|
+
logger.dim('--attribute: no attributable locations (git blame produced nothing) — skipped.');
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
const n = Object.keys(attr.attributions).length;
|
|
95
|
+
logger.dim(`--attribute: "who to ask" attached to ${n}/${locations.length} finding location(s).`);
|
|
96
|
+
return attr;
|
|
97
|
+
}
|
|
85
98
|
/**
|
|
86
99
|
* Apply `--fail-on-score` to a higher-is-better score. Exits with
|
|
87
100
|
* code 1 + a logged reason when the gate fires. Skips when the user
|
|
@@ -143,6 +156,11 @@ function printUsage() {
|
|
|
143
156
|
(hot-files / entry-points / file / feature / communities / api-surface / context)
|
|
144
157
|
vyuh-dxkit context <query> Slim structural slice for a query — token-efficient
|
|
145
158
|
codebase context for LLMs (--budget / --depth / --substring / --json)
|
|
159
|
+
vyuh-dxkit reviewers [--base <ref>|--staged] [--limit N] [--json]
|
|
160
|
+
Suggest reviewers for the change — active-owner model
|
|
161
|
+
(recency-weighted git history, bots + departed devs
|
|
162
|
+
filtered, excludes the author) blended with CODEOWNERS,
|
|
163
|
+
with a bus-factor signal. Names + @handles, never emails.
|
|
146
164
|
vyuh-dxkit to-xlsx <json> Convert a dxkit JSON report to 15-col XLSX
|
|
147
165
|
vyuh-dxkit tools [path] Show required analysis tools status
|
|
148
166
|
vyuh-dxkit tools install Interactively install missing tools
|
|
@@ -235,6 +253,11 @@ function printUsage() {
|
|
|
235
253
|
--graph-context Vulnerabilities/test-gaps/quality: attach per-finding graph context
|
|
236
254
|
(module + blast radius) to the detailed report. Needs a graph.json
|
|
237
255
|
(run health first); fail-open — skipped silently if absent.
|
|
256
|
+
--attribute Vulnerabilities/test-gaps/quality: attach a "who to ask" column to
|
|
257
|
+
the detailed report (git blame → active-owner model; inactive authors
|
|
258
|
+
routed to the current owner). Names + @handles, never emails. Opt-in;
|
|
259
|
+
fail-open. Historical only — net-new findings are introduced by your
|
|
260
|
+
own change.
|
|
238
261
|
--xlsx Licenses/bom: also write 15-col BOM XLSX
|
|
239
262
|
--since Dev-report: start date (YYYY-MM-DD)
|
|
240
263
|
--filter Bom: 'all' (default) or 'top-level' (keeps only root manifest deps;
|
|
@@ -334,11 +357,16 @@ async function run(argv) {
|
|
|
334
357
|
limit: { type: 'string' },
|
|
335
358
|
refresh: { type: 'boolean', default: false },
|
|
336
359
|
substring: { type: 'boolean', default: false },
|
|
360
|
+
// reviewers flags
|
|
361
|
+
staged: { type: 'boolean', default: false },
|
|
362
|
+
base: { type: 'string' },
|
|
337
363
|
// context flags
|
|
338
364
|
budget: { type: 'string' },
|
|
339
365
|
depth: { type: 'string' },
|
|
340
366
|
// graph-context enrichment for detailed reports (vuln/test-gaps/quality)
|
|
341
367
|
'graph-context': { type: 'boolean', default: false },
|
|
368
|
+
// attribution enrichment for detailed reports — "who to ask" (opt-in)
|
|
369
|
+
attribute: { type: 'boolean', default: false },
|
|
342
370
|
// ingest flags (external SAST engines → .dxkit/external snapshots)
|
|
343
371
|
sarif: { type: 'string' },
|
|
344
372
|
'from-snyk': { type: 'boolean', default: false },
|
|
@@ -784,8 +812,10 @@ async function run(argv) {
|
|
|
784
812
|
logger.success(`Report saved to ${path.relative(targetPath, reportPath)}`);
|
|
785
813
|
// D032 (2.4.7): detailed JSON + MD always written so dashboard finds fresh inputs.
|
|
786
814
|
const { buildSecurityDetailed, formatSecurityDetailedMarkdown } = await Promise.resolve().then(() => __importStar(require('./analyzers/security/detailed')));
|
|
787
|
-
const
|
|
788
|
-
const
|
|
815
|
+
const securityLocations = report.findings.map((f) => ({ file: f.file, line: f.line }));
|
|
816
|
+
const graphContext = await buildGraphContextIfRequested(!!values['graph-context'], targetPath, securityLocations);
|
|
817
|
+
const securityAttribution = await buildAttributionIfRequested(!!values.attribute, targetPath, securityLocations);
|
|
818
|
+
const securityDetailed = buildSecurityDetailed(report, graphContext, securityAttribution);
|
|
789
819
|
const securityDetailedJsonPath = path.join(reportDir, `vulnerability-scan-${date}-detailed.json`);
|
|
790
820
|
const securityDetailedMdPath = path.join(reportDir, `vulnerability-scan-${date}-detailed.md`);
|
|
791
821
|
fs.writeFileSync(securityDetailedJsonPath, JSON.stringify((0, report_schema_1.stampSchema)(securityDetailed, 'vulnerabilities-detailed'), null, 2));
|
|
@@ -863,7 +893,8 @@ async function run(argv) {
|
|
|
863
893
|
// D032 (2.4.7): detailed JSON + MD always written so dashboard finds fresh inputs.
|
|
864
894
|
const { buildTestGapsDetailed, formatTestGapsDetailedMarkdown } = await Promise.resolve().then(() => __importStar(require('./analyzers/tests/detailed')));
|
|
865
895
|
const testGapsGraphContext = await buildGraphContextIfRequested(!!values['graph-context'], targetPath, report.gaps.map((g) => ({ file: g.path })));
|
|
866
|
-
const
|
|
896
|
+
const testGapsAttribution = await buildAttributionIfRequested(!!values.attribute, targetPath, report.gaps.map((g) => ({ file: g.path })));
|
|
897
|
+
const testGapsDetailed = buildTestGapsDetailed(report, testGapsGraphContext, testGapsAttribution);
|
|
867
898
|
const testGapsDetailedJsonPath = path.join(reportDir, `test-gaps-${date}-detailed.json`);
|
|
868
899
|
const testGapsDetailedMdPath = path.join(reportDir, `test-gaps-${date}-detailed.md`);
|
|
869
900
|
fs.writeFileSync(testGapsDetailedJsonPath, JSON.stringify((0, report_schema_1.stampSchema)(testGapsDetailed, 'test-gaps-detailed'), null, 2));
|
|
@@ -940,8 +971,13 @@ async function run(argv) {
|
|
|
940
971
|
logger.success(`Report saved to ${path.relative(targetPath, reportPath)}`);
|
|
941
972
|
// D032 (2.4.7): detailed JSON + MD always written so dashboard finds fresh inputs.
|
|
942
973
|
const { buildQualityDetailed, formatQualityDetailedMarkdown } = await Promise.resolve().then(() => __importStar(require('./analyzers/quality/detailed')));
|
|
943
|
-
const
|
|
944
|
-
|
|
974
|
+
const qualityLocations = [
|
|
975
|
+
...(report.metrics.topConsoleFiles ?? []),
|
|
976
|
+
...(report.metrics.topTodoFiles ?? []),
|
|
977
|
+
].map((f) => ({ file: f.file }));
|
|
978
|
+
const qualityGraphContext = await buildGraphContextIfRequested(!!values['graph-context'], targetPath, qualityLocations);
|
|
979
|
+
const qualityAttribution = await buildAttributionIfRequested(!!values.attribute, targetPath, qualityLocations);
|
|
980
|
+
const qualityDetailed = buildQualityDetailed(report, qualityGraphContext, qualityAttribution);
|
|
945
981
|
const qualityDetailedJsonPath = path.join(reportDir, `quality-review-${date}-detailed.json`);
|
|
946
982
|
const qualityDetailedMdPath = path.join(reportDir, `quality-review-${date}-detailed.md`);
|
|
947
983
|
fs.writeFileSync(qualityDetailedJsonPath, JSON.stringify(qualityDetailed, null, 2));
|
|
@@ -1729,6 +1765,18 @@ async function run(argv) {
|
|
|
1729
1765
|
});
|
|
1730
1766
|
break;
|
|
1731
1767
|
}
|
|
1768
|
+
case 'reviewers': {
|
|
1769
|
+
const { runReviewers } = await Promise.resolve().then(() => __importStar(require('./reviewers-cli')));
|
|
1770
|
+
const limitRaw = values.limit;
|
|
1771
|
+
const limit = limitRaw ? parseInt(limitRaw, 10) : undefined;
|
|
1772
|
+
runReviewers(cwd, {
|
|
1773
|
+
base: values.base,
|
|
1774
|
+
staged: !!values.staged,
|
|
1775
|
+
json: !!values.json,
|
|
1776
|
+
...(Number.isFinite(limit) ? { limit } : {}),
|
|
1777
|
+
});
|
|
1778
|
+
break;
|
|
1779
|
+
}
|
|
1732
1780
|
case 'explore': {
|
|
1733
1781
|
const { runExplore } = await Promise.resolve().then(() => __importStar(require('./explore-cli')));
|
|
1734
1782
|
// positionals[0] is 'explore'; positionals[1..] are the
|