@vyuhlabs/dxkit 2.9.3 → 2.10.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.
Files changed (123) hide show
  1. package/CHANGELOG.md +170 -0
  2. package/README.md +9 -0
  3. package/dist/allowlist/annotate.d.ts +71 -0
  4. package/dist/allowlist/annotate.d.ts.map +1 -0
  5. package/dist/allowlist/annotate.js +105 -0
  6. package/dist/allowlist/annotate.js.map +1 -0
  7. package/dist/allowlist/cli.d.ts +6 -0
  8. package/dist/allowlist/cli.d.ts.map +1 -1
  9. package/dist/allowlist/cli.js +70 -37
  10. package/dist/allowlist/cli.js.map +1 -1
  11. package/dist/analyzers/dashboard/index.d.ts.map +1 -1
  12. package/dist/analyzers/dashboard/index.js +6 -1
  13. package/dist/analyzers/dashboard/index.js.map +1 -1
  14. package/dist/analyzers/developer/gather.d.ts +16 -0
  15. package/dist/analyzers/developer/gather.d.ts.map +1 -1
  16. package/dist/analyzers/developer/gather.js +2 -0
  17. package/dist/analyzers/developer/gather.js.map +1 -1
  18. package/dist/analyzers/developer/ownership.d.ts +86 -0
  19. package/dist/analyzers/developer/ownership.d.ts.map +1 -0
  20. package/dist/analyzers/developer/ownership.js +180 -0
  21. package/dist/analyzers/developer/ownership.js.map +1 -0
  22. package/dist/analyzers/health.d.ts.map +1 -1
  23. package/dist/analyzers/health.js +17 -2
  24. package/dist/analyzers/health.js.map +1 -1
  25. package/dist/analyzers/quality/detailed.d.ts +5 -1
  26. package/dist/analyzers/quality/detailed.d.ts.map +1 -1
  27. package/dist/analyzers/quality/detailed.js +30 -29
  28. package/dist/analyzers/quality/detailed.js.map +1 -1
  29. package/dist/analyzers/security/actions.d.ts.map +1 -1
  30. package/dist/analyzers/security/actions.js +13 -0
  31. package/dist/analyzers/security/actions.js.map +1 -1
  32. package/dist/analyzers/security/aggregator.d.ts +18 -0
  33. package/dist/analyzers/security/aggregator.d.ts.map +1 -1
  34. package/dist/analyzers/security/aggregator.js +28 -0
  35. package/dist/analyzers/security/aggregator.js.map +1 -1
  36. package/dist/analyzers/security/detailed.d.ts +7 -1
  37. package/dist/analyzers/security/detailed.d.ts.map +1 -1
  38. package/dist/analyzers/security/detailed.js +31 -15
  39. package/dist/analyzers/security/detailed.js.map +1 -1
  40. package/dist/analyzers/security/gather.d.ts.map +1 -1
  41. package/dist/analyzers/security/gather.js +6 -0
  42. package/dist/analyzers/security/gather.js.map +1 -1
  43. package/dist/analyzers/security/index.d.ts.map +1 -1
  44. package/dist/analyzers/security/index.js +81 -2
  45. package/dist/analyzers/security/index.js.map +1 -1
  46. package/dist/analyzers/security/scanner-drift.d.ts +21 -0
  47. package/dist/analyzers/security/scanner-drift.d.ts.map +1 -0
  48. package/dist/analyzers/security/scanner-drift.js +113 -0
  49. package/dist/analyzers/security/scanner-drift.js.map +1 -0
  50. package/dist/analyzers/security/shallow.d.ts.map +1 -1
  51. package/dist/analyzers/security/shallow.js +24 -2
  52. package/dist/analyzers/security/shallow.js.map +1 -1
  53. package/dist/analyzers/security/types.d.ts +38 -0
  54. package/dist/analyzers/security/types.d.ts.map +1 -1
  55. package/dist/analyzers/tests/detailed.d.ts +5 -1
  56. package/dist/analyzers/tests/detailed.d.ts.map +1 -1
  57. package/dist/analyzers/tests/detailed.js +27 -20
  58. package/dist/analyzers/tests/detailed.js.map +1 -1
  59. package/dist/analyzers/tools/graphify.d.ts +11 -0
  60. package/dist/analyzers/tools/graphify.d.ts.map +1 -1
  61. package/dist/analyzers/tools/graphify.js +429 -413
  62. package/dist/analyzers/tools/graphify.js.map +1 -1
  63. package/dist/analyzers/tools/grep-secrets.d.ts.map +1 -1
  64. package/dist/analyzers/tools/grep-secrets.js +9 -0
  65. package/dist/analyzers/tools/grep-secrets.js.map +1 -1
  66. package/dist/analyzers/tools/osv-scanner-fix.d.ts.map +1 -1
  67. package/dist/analyzers/tools/osv-scanner-fix.js +12 -1
  68. package/dist/analyzers/tools/osv-scanner-fix.js.map +1 -1
  69. package/dist/analyzers/tools/tool-registry.d.ts.map +1 -1
  70. package/dist/analyzers/tools/tool-registry.js +78 -43
  71. package/dist/analyzers/tools/tool-registry.js.map +1 -1
  72. package/dist/analyzers/tools/walk-source-files.d.ts +10 -0
  73. package/dist/analyzers/tools/walk-source-files.d.ts.map +1 -1
  74. package/dist/analyzers/tools/walk-source-files.js +14 -0
  75. package/dist/analyzers/tools/walk-source-files.js.map +1 -1
  76. package/dist/analyzers/types.d.ts +9 -0
  77. package/dist/analyzers/types.d.ts.map +1 -1
  78. package/dist/attribution/attribute.d.ts +57 -0
  79. package/dist/attribution/attribute.d.ts.map +1 -0
  80. package/dist/attribution/attribute.js +149 -0
  81. package/dist/attribution/attribute.js.map +1 -0
  82. package/dist/baseline/entry-to-located.d.ts +12 -5
  83. package/dist/baseline/entry-to-located.d.ts.map +1 -1
  84. package/dist/baseline/entry-to-located.js +21 -7
  85. package/dist/baseline/entry-to-located.js.map +1 -1
  86. package/dist/baseline/git-aware-match.d.ts +7 -5
  87. package/dist/baseline/git-aware-match.d.ts.map +1 -1
  88. package/dist/baseline/git-aware-match.js +78 -5
  89. package/dist/baseline/git-aware-match.js.map +1 -1
  90. package/dist/cli.d.ts.map +1 -1
  91. package/dist/cli.js +53 -5
  92. package/dist/cli.js.map +1 -1
  93. package/dist/explore/context-hook.d.ts +49 -29
  94. package/dist/explore/context-hook.d.ts.map +1 -1
  95. package/dist/explore/context-hook.js +304 -29
  96. package/dist/explore/context-hook.js.map +1 -1
  97. package/dist/generator.d.ts.map +1 -1
  98. package/dist/generator.js +13 -7
  99. package/dist/generator.js.map +1 -1
  100. package/dist/ingest/snyk-policy.d.ts +22 -1
  101. package/dist/ingest/snyk-policy.d.ts.map +1 -1
  102. package/dist/ingest/snyk-policy.js +75 -18
  103. package/dist/ingest/snyk-policy.js.map +1 -1
  104. package/dist/languages/index.d.ts +28 -5
  105. package/dist/languages/index.d.ts.map +1 -1
  106. package/dist/languages/index.js +38 -7
  107. package/dist/languages/index.js.map +1 -1
  108. package/dist/languages/typescript.d.ts.map +1 -1
  109. package/dist/languages/typescript.js +19 -0
  110. package/dist/languages/typescript.js.map +1 -1
  111. package/dist/reviewers-cli.d.ts +57 -0
  112. package/dist/reviewers-cli.d.ts.map +1 -0
  113. package/dist/reviewers-cli.js +263 -0
  114. package/dist/reviewers-cli.js.map +1 -0
  115. package/dist/scoring/dimensions/security.d.ts +17 -0
  116. package/dist/scoring/dimensions/security.d.ts.map +1 -1
  117. package/dist/scoring/dimensions/security.js +12 -0
  118. package/dist/scoring/dimensions/security.js.map +1 -1
  119. package/package.json +1 -1
  120. package/templates/.claude/skills/dxkit-action/SKILL.md +13 -2
  121. package/templates/.claude/skills/dxkit-allowlist/SKILL.md +9 -0
  122. package/templates/.claude/skills/dxkit-onboard/SKILL.md +2 -2
  123. 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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"}
@@ -78,6 +78,23 @@ export interface SecurityScoreInput {
78
78
  * Adapters MUST populate this from `DepVulnSummary.available`.
79
79
  */
80
80
  depVulnsAvailable: boolean;
81
+ /**
82
+ * Pre-2.10 the unavailability cap was asymmetric — a missing
83
+ * dep-vuln scan capped at the uncertainty tier while missing
84
+ * secret/code scanners silently scored as "0 findings". An upgrade
85
+ * that merely turned the secret scanners ON then read as a score
86
+ * drop on an unchanged commit. These two flags give
87
+ * every measurement axis the same honest treatment: scanner didn't
88
+ * run → uncertainty cap, never a confident clean score.
89
+ *
90
+ * Same attempted-and-failed semantics as `depVulnsAvailable`:
91
+ * false ONLY when the gather was attempted and no provider
92
+ * succeeded. "No active provider" stays vacuously true.
93
+ */
94
+ secretsAvailable: boolean;
95
+ /** See `secretsAvailable` — same contract for the semgrep /
96
+ * code-patterns axis. */
97
+ codePatternsAvailable: boolean;
81
98
  }
82
99
  export declare const SECURITY_SCORING_SPEC: DimensionScoringSpec<SecurityScoreInput>;
83
100
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"security.d.ts","sourceRoot":"","sources":["../../../src/scoring/dimensions/security.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAEpD,oEAAoE;AACpE,UAAU,cAAc;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IACjC,mEAAmE;IACnE,cAAc,EAAE,MAAM,CAAC;IACvB,uDAAuD;IACvD,eAAe,EAAE,MAAM,CAAC;IACxB,iCAAiC;IACjC,aAAa,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,YAAY,EAAE,cAAc,CAAC;IAC7B,mEAAmE;IACnE,QAAQ,EAAE,cAAc,CAAC;IACzB;;;;;;;;;;;OAWG;IACH,iBAAiB,EAAE,OAAO,CAAC;CAC5B;AAUD,eAAO,MAAM,qBAAqB,EAAE,oBAAoB,CAAC,kBAAkB,CAoF1E,CAAC"}
1
+ {"version":3,"file":"security.d.ts","sourceRoot":"","sources":["../../../src/scoring/dimensions/security.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAEpD,oEAAoE;AACpE,UAAU,cAAc;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IACjC,mEAAmE;IACnE,cAAc,EAAE,MAAM,CAAC;IACvB,uDAAuD;IACvD,eAAe,EAAE,MAAM,CAAC;IACxB,iCAAiC;IACjC,aAAa,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,YAAY,EAAE,cAAc,CAAC;IAC7B,mEAAmE;IACnE,QAAQ,EAAE,cAAc,CAAC;IACzB;;;;;;;;;;;OAWG;IACH,iBAAiB,EAAE,OAAO,CAAC;IAC3B;;;;;;;;;;;;OAYG;IACH,gBAAgB,EAAE,OAAO,CAAC;IAC1B;8BAC0B;IAC1B,qBAAqB,EAAE,OAAO,CAAC;CAChC;AAUD,eAAO,MAAM,qBAAqB,EAAE,oBAAoB,CAAC,kBAAkB,CAgG1E,CAAC"}
@@ -121,6 +121,18 @@ exports.SECURITY_SCORING_SPEC = {
121
121
  describe: () => `dependency vulnerability scan did not run`,
122
122
  applies: (i) => !i.depVulnsAvailable,
123
123
  },
124
+ {
125
+ id: 'secrets-unavailable',
126
+ tier: 'uncertainty',
127
+ describe: () => `secret scan did not run`,
128
+ applies: (i) => !i.secretsAvailable,
129
+ },
130
+ {
131
+ id: 'code-patterns-unavailable',
132
+ tier: 'uncertainty',
133
+ describe: () => `static-analysis (code-pattern) scan did not run`,
134
+ applies: (i) => !i.codePatternsAvailable,
135
+ },
124
136
  {
125
137
  id: 'high-plus-code-open',
126
138
  tier: 'fixable-finding',
@@ -1 +1 @@
1
- {"version":3,"file":"security.js","sourceRoot":"","sources":["../../../src/scoring/dimensions/security.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"security.js","sourceRoot":"","sources":["../../../src/scoring/dimensions/security.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.10.0",
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
@@ -99,7 +101,16 @@ npx vyuh-dxkit vulnerabilities --json | jq '.summary.findings'
99
101
 
100
102
  Don't try to redact the secret in place — the git history still has it. Rotation is the only true fix.
101
103
 
102
- If the "secret" is actually a placeholder in test code (e.g., `"sk_test_xxxxxxxxxxxx"` with no real credential value), confirm with the developer and allowlist via `dxkit-allow:test-fixture` — see "Allowlisting (when fix is not viable)" below.
104
+ #### Secrets in test files TRIAGE, don't assume
105
+
106
+ dxkit deliberately does **not** lower a secret's severity just because it sits in a test file: the scanner can't tell a throwaway fixture from a real credential someone pasted into an integration test, and a real leak in a test is still a leak. So test-file secrets reach you at full severity, and the report flags how many are test-located — that's your cue to triage each one, not to dismiss them:
107
+
108
+ 1. **Open the line and judge the value.** Obvious throwaway fixture (`'password1'`, `'changeme'`, `'sk_test_xxxx'`, low-entropy / dictionary)? Or does it look like a real credential (high-entropy token, a real-looking host/user/password, something that would actually authenticate)?
109
+ 2. **Fixture → allowlist it.** `vyuh-dxkit allowlist add <file>:<line> --category=test-fixture --reason="..."`. This is the right tool: it records *why*, and `test-fixture` (like `false-positive`) **lifts the finding from the Security score**, not just the guardrail — so a properly-triaged test file stops dragging the score down. Don't waste effort "rotating" a fake value.
110
+ 3. **Real credential → treat it as a live leak.** Rotate in the provider, remove from the file, scrub git history (`git filter-repo` / BFG). Being in a test does not make it safe.
111
+ 4. **Unsure? Ask the developer** before allowlisting — never allowlist a value you haven't confirmed is fake.
112
+
113
+ Allowlisting under `test-fixture` is a deliberate per-finding judgment, not a blanket "ignore tests" — don't add a `.dxkit-ignore` for the test directory to make the noise go away (that also drops the files from coverage + test-gap analysis, and would hide a real leak the same way). See "Allowlisting (when fix is not viable)" below.
103
114
 
104
115
  ### SAST finding (semgrep)
105
116
 
@@ -164,7 +175,7 @@ If the finding is a false positive, add `// slop-ok: <reason>` on the offending
164
175
 
165
176
  ## Allowlisting (when fix is not viable)
166
177
 
167
- **Fix first.** The allowlist is the SECOND option, not the first. When you reach for it, choose deliberately — every allowlist entry is a future maintenance burden the customer's team will revisit.
178
+ **Fix first.** The allowlist is the SECOND option, not the first. When you reach for it, choose deliberately — every allowlist entry is a future maintenance burden the team will revisit.
168
179
 
169
180
  Five typed categories signal WHY the suppression is in place:
170
181
 
@@ -7,6 +7,15 @@ description: Manage the dxkit allowlist over its whole lifecycle — list, inspe
7
7
 
8
8
  The allowlist is dxkit's per-finding suppression surface: a reviewed finding that the team has categorized (`false-positive`, `test-fixture`, `mitigated-externally`, `accepted-risk`, `deferred`) with a reason, so the guardrail lets it pass on future runs. It's the single source of truth across every scanner — native semgrep/gitleaks and ingested Snyk Code / CodeQL findings alike, all keyed on one fingerprint.
9
9
 
10
+ ### Categories affect the score, not just the guardrail
11
+
12
+ The category isn't just a label — it decides whether the finding still counts toward the **Security score**:
13
+
14
+ - **`false-positive` / `test-fixture`** declare the finding is *not a real finding* (a scanner misfire, or throwaway test data). These are **lifted from the Security penalties and caps**, so a repo that has genuinely triaged its noise scores honestly instead of staying capped on findings it has already reviewed and accepted. This is also why test-file secrets (which dxkit never auto-downgrades by path) should be allowlisted as `test-fixture` once confirmed fake — that's what removes them from the score.
15
+ - **`accepted-risk` / `deferred` / `mitigated-externally`** accept a *real* exposure. The guardrail stops blocking, but the score keeps counting them — accepting a real risk can't earn an A. (`accepted-risk` / `deferred` also require an expiry so the acceptance ages out.)
16
+
17
+ So `false-positive`/`test-fixture` are the only categories that recover score. Reserve them for findings that genuinely aren't real — miscategorizing a real risk as `false-positive` to lift the score is exactly the self-deception the typed categories exist to prevent.
18
+
10
19
  This skill manages the allowlist's **lifecycle**: reviewing what's there, keeping it honest, and propagating decisions outward. For the upstream question — *should this be fixed instead of suppressed, and how do I add an entry* — that decision and the `add` path live in **dxkit-action**. Fix first; suppress second.
11
20
 
12
21
  ## The lifecycle at a glance
@@ -104,7 +104,7 @@ For each fixable signal in doctor's output, dispatch through `dxkit-fix`'s recov
104
104
 
105
105
  - `git hooks active` not active → `npx vyuh-dxkit hooks activate`
106
106
  - `baseline captured` missing → defer to step 5 (we'll handle that explicitly with the secrets warning)
107
- - `vyuh-dxkit on PATH` missing → `npm install -g @vyuhlabs/dxkit`
107
+ - `vyuh-dxkit on PATH` missing → `npm install -g @vyuhlabs/dxkit` (a global install — it affects every Node project on the machine and may need elevated permissions; a project-local install or a Node version manager works too)
108
108
  - `scanner toolchain` incomplete → `npx vyuh-dxkit tools install --yes`
109
109
 
110
110
  Don't auto-execute baseline capture here — step 5 has a values-laden warning that needs explicit customer confirmation.
@@ -195,7 +195,7 @@ Local hooks are fast feedback; CI is the unbypassable enforcement. Branch protec
195
195
 
196
196
  ASK:
197
197
 
198
- > **Configure branch protection now?** Default yes. Adds `dxkit-guardrails` as a required status check on `<default branch>`. Requires admin permission on the GitHub repo. Without this, the CI workflow is informational — PRs can merge even on guardrail failures.
198
+ > **Configure branch protection now?** Default yes. This **modifies your GitHub repository settings** — it adds `dxkit-guardrails` as a required status check on `<default branch>`, and so needs admin permission on the repo. Without it, the CI workflow is informational — PRs can merge even on guardrail failures.
199
199
 
200
200
  If yes:
201
201
 
@@ -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