@shrkcrft/quality-gates 0.1.0-alpha.25 → 0.1.0-alpha.26

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.
@@ -1 +1 @@
1
- {"version":3,"file":"arch-gate.d.ts","sourceRoot":"","sources":["../../src/gates/arch-gate.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AACvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAE7D;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,QAAQ,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,WAAW,CA+GzF"}
1
+ {"version":3,"file":"arch-gate.d.ts","sourceRoot":"","sources":["../../src/gates/arch-gate.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AACvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAE7D;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,QAAQ,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,WAAW,CAyKzF"}
@@ -39,16 +39,49 @@ export function archGate(projectRoot, options = {}) {
39
39
  const delta = diffSnapshots(baseline, current);
40
40
  const newIds = new Set(delta.newViolationIds);
41
41
  const newViolations = report.violations.filter((v) => newIds.has(violationId(v)));
42
- const newErrors = newViolations.filter((v) => v.severity === 'error').length;
43
- const newWarnings = newViolations.filter((v) => v.severity === 'warning').length;
44
42
  const baselineErrors = baseline.countsBySeverity.error;
43
+ // §3.1 — change-scoped attribution. "NEW since baseline" is a drift measure
44
+ // against a frozen (possibly months-old) snapshot; on its own it red-fails
45
+ // on structural debt the current change never touched, training the agent to
46
+ // ignore the gate. Redefine the BLOCKING set as the intersection of the
47
+ // baseline-new violations with the files the working diff actually touched
48
+ // (diff vs HEAD). A NEW violation whose origin file is NOT in the changed set
49
+ // becomes INFORMATIONAL baseline drift — it never flips the exit code
50
+ // (mirroring how baseline debt is already non-blocking). When no changed set
51
+ // is supplied (`changedFiles === undefined`), fall back to the legacy
52
+ // behaviour where any NEW violation blocks.
53
+ const scoped = options.changedFiles !== undefined;
54
+ const changedSet = new Set((options.changedFiles ?? []).map(normaliseRel));
55
+ const attributable = scoped
56
+ ? newViolations.filter((v) => fileInChangedSet(v.file, changedSet))
57
+ : newViolations;
58
+ const drift = scoped
59
+ ? newViolations.filter((v) => !fileInChangedSet(v.file, changedSet))
60
+ : [];
61
+ const newErrors = attributable.filter((v) => v.severity === 'error').length;
62
+ const newWarnings = attributable.filter((v) => v.severity === 'warning').length;
63
+ const driftErrors = drift.filter((v) => v.severity === 'error').length;
64
+ const driftWarnings = drift.filter((v) => v.severity === 'warning').length;
65
+ const driftNote = scoped && driftErrors + driftWarnings > 0
66
+ ? ` Baseline drift in untouched files: ${driftErrors} error(s), ${driftWarnings} warning(s) (informational — re-freeze with \`shrk gate baseline --refreeze\`).`
67
+ : '';
45
68
  if (newErrors > 0) {
46
69
  return {
47
70
  id: 'arch',
48
71
  label: 'Architecture',
49
72
  status: 'fail',
50
- message: `${newErrors} NEW architecture error(s) since baseline (baseline debt: ${baselineErrors}, informational).`,
51
- details: { newErrors, newWarnings, baselineErrors, totalErrors: errors, newViolationIds: delta.newViolationIds },
73
+ message: `${newErrors} NEW architecture error(s) introduced by this change (baseline debt: ${baselineErrors}, informational).${driftNote}`,
74
+ details: {
75
+ newErrors,
76
+ newWarnings,
77
+ baselineErrors,
78
+ totalErrors: errors,
79
+ driftErrors,
80
+ driftWarnings,
81
+ changeScoped: scoped,
82
+ newViolationIds: attributable.map(violationId),
83
+ driftViolationIds: drift.map(violationId),
84
+ },
52
85
  nextCommands: ['shrk arch check', 'shrk arch baseline show'],
53
86
  durationMs: Date.now() - start,
54
87
  };
@@ -58,20 +91,41 @@ export function archGate(projectRoot, options = {}) {
58
91
  id: 'arch',
59
92
  label: 'Architecture',
60
93
  status: 'warn',
61
- message: `${newWarnings} new architecture warning(s) since baseline (baseline debt: ${baselineErrors} error(s), informational).`,
62
- details: { newWarnings, baselineErrors, totalErrors: errors, totalWarnings: warnings },
94
+ message: `${newWarnings} new architecture warning(s) introduced by this change (baseline debt: ${baselineErrors} error(s), informational).${driftNote}`,
95
+ details: {
96
+ newWarnings,
97
+ baselineErrors,
98
+ totalErrors: errors,
99
+ totalWarnings: warnings,
100
+ driftErrors,
101
+ driftWarnings,
102
+ changeScoped: scoped,
103
+ },
63
104
  nextCommands: ['shrk arch check'],
64
105
  durationMs: Date.now() - start,
65
106
  };
66
107
  }
108
+ // No change-attributable NEW violation. This is a PASS for THIS change even
109
+ // when baseline drift persists in untouched files — surface that drift as
110
+ // informational rather than reporting a misleading all-clear over zero
111
+ // attributed errors (the honest exit-code posture).
112
+ const passMessage = scoped && driftErrors + driftWarnings > 0
113
+ ? `No change-attributable architecture errors (baseline debt: ${baselineErrors} error(s), informational).${driftNote}`
114
+ : errors > 0
115
+ ? `No NEW architecture violations (baseline debt: ${baselineErrors} error(s), informational).`
116
+ : 'No architecture violations.';
67
117
  return {
68
118
  id: 'arch',
69
119
  label: 'Architecture',
70
120
  status: 'pass',
71
- message: errors > 0
72
- ? `No NEW architecture violations (baseline debt: ${baselineErrors} error(s), informational).`
73
- : 'No architecture violations.',
74
- details: { baselineErrors, totalErrors: errors },
121
+ message: passMessage,
122
+ details: {
123
+ baselineErrors,
124
+ totalErrors: errors,
125
+ driftErrors,
126
+ driftWarnings,
127
+ changeScoped: scoped,
128
+ },
75
129
  durationMs: Date.now() - start,
76
130
  };
77
131
  }
@@ -125,3 +179,26 @@ export function archGate(projectRoot, options = {}) {
125
179
  durationMs: Date.now() - start,
126
180
  };
127
181
  }
182
+ /** Slash-normalise a project-relative path and strip a leading `./`. */
183
+ function normaliseRel(input) {
184
+ return input.split(/[\\/]/).join('/').replace(/^\.\//, '');
185
+ }
186
+ /**
187
+ * True when a violation's origin file is in the changed-file set. Both sides are
188
+ * project-relative; the suffix match is a safety net for callers that pass
189
+ * changed paths with a differing prefix depth (mirrors the boundary changed-only
190
+ * filter's fuzzy tail match).
191
+ */
192
+ function fileInChangedSet(file, changedSet) {
193
+ if (changedSet.size === 0)
194
+ return false;
195
+ const norm = normaliseRel(file);
196
+ if (changedSet.has(norm))
197
+ return true;
198
+ for (const cf of changedSet) {
199
+ if (cf.length > 0 && (norm === cf || norm.endsWith('/' + cf) || cf.endsWith('/' + norm))) {
200
+ return true;
201
+ }
202
+ }
203
+ return false;
204
+ }
@@ -12,5 +12,20 @@ export interface IArchGateOptions {
12
12
  * behavior; useful for a clean-tree CI demand).
13
13
  */
14
14
  baselineRelative?: boolean;
15
+ /**
16
+ * Change-scoped attribution set: the project-relative ('/'-normalised) files
17
+ * the working diff actually touched (diff vs HEAD). When provided (even an
18
+ * empty array), a NEW-since-baseline violation is BLOCKING only if its origin
19
+ * file is in this set; a NEW violation in an untouched file is demoted to
20
+ * INFORMATIONAL "baseline drift" and never flips the exit code. This is the
21
+ * fix for §3.1: "NEW" must mean *introduced by this change*, not drift against
22
+ * a frozen (possibly months-old) baseline.
23
+ *
24
+ * `undefined` disables change-scoped attribution (legacy behaviour: any
25
+ * NEW-since-baseline violation blocks). An empty array means "clean tree — no
26
+ * change-attributable violations" (a pass for THIS change), keeping the
27
+ * informational drift line visible.
28
+ */
29
+ changedFiles?: readonly string[];
15
30
  }
16
31
  //# sourceMappingURL=arch-gate-options.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"arch-gate-options.d.ts","sourceRoot":"","sources":["../../src/schema/arch-gate-options.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;;;;;;OAQG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B"}
1
+ {"version":3,"file":"arch-gate-options.d.ts","sourceRoot":"","sources":["../../src/schema/arch-gate-options.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;;;;;;OAQG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;;;;;;;;;;;OAaG;IACH,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAClC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shrkcrft/quality-gates",
3
- "version": "0.1.0-alpha.25",
3
+ "version": "0.1.0-alpha.26",
4
4
  "description": "SharkCraft quality gates: single aggregator that runs the code-intelligence checks (graph index, boundaries, architecture, impact summary) and emits one pass/fail report. The pre-merge gate for AI-agent-authored changes.",
5
5
  "license": "MIT",
6
6
  "author": "SharkCraft contributors",
@@ -43,15 +43,15 @@
43
43
  "typecheck": "tsc --noEmit -p tsconfig.json"
44
44
  },
45
45
  "dependencies": {
46
- "@shrkcrft/core": "^0.1.0-alpha.25",
47
- "@shrkcrft/graph": "^0.1.0-alpha.25",
48
- "@shrkcrft/architecture-guard": "^0.1.0-alpha.25",
49
- "@shrkcrft/impact-engine": "^0.1.0-alpha.25",
50
- "@shrkcrft/api-surface-diff": "^0.1.0-alpha.25",
51
- "@shrkcrft/boundaries": "^0.1.0-alpha.25",
52
- "@shrkcrft/structural-search": "^0.1.0-alpha.25",
53
- "@shrkcrft/context-planner": "^0.1.0-alpha.25",
54
- "@shrkcrft/inspector": "^0.1.0-alpha.25"
46
+ "@shrkcrft/core": "^0.1.0-alpha.26",
47
+ "@shrkcrft/graph": "^0.1.0-alpha.26",
48
+ "@shrkcrft/architecture-guard": "^0.1.0-alpha.26",
49
+ "@shrkcrft/impact-engine": "^0.1.0-alpha.26",
50
+ "@shrkcrft/api-surface-diff": "^0.1.0-alpha.26",
51
+ "@shrkcrft/boundaries": "^0.1.0-alpha.26",
52
+ "@shrkcrft/structural-search": "^0.1.0-alpha.26",
53
+ "@shrkcrft/context-planner": "^0.1.0-alpha.26",
54
+ "@shrkcrft/inspector": "^0.1.0-alpha.26"
55
55
  },
56
56
  "publishConfig": {
57
57
  "access": "public"