slopbrick 0.17.2 → 0.17.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.
@@ -4084,6 +4084,9 @@ var VERDICTS = [
4084
4084
  "HYGIENE",
4085
4085
  "DORMANT"
4086
4086
  ];
4087
+ function isDefaultOff(verdict) {
4088
+ return verdict === "NOISY" || verdict === "INVERTED" || verdict === "DORMANT";
4089
+ }
4087
4090
  var signalStrengthSchema = external_exports.record(
4088
4091
  external_exports.string(),
4089
4092
  // ruleId
@@ -4096,7 +4099,13 @@ var signalStrengthSchema = external_exports.record(
4096
4099
  verdict: external_exports.enum(VERDICTS),
4097
4100
  // defaultOff is opt-in per-rule. Absent = the rule follows
4098
4101
  // isDefaultOff(verdict). Present (true or false) = user override.
4099
- defaultOff: external_exports.boolean().optional()
4102
+ defaultOff: external_exports.boolean().optional(),
4103
+ // v0.17.3 (B5): mark rules whose signal is specific to AI
4104
+ // generation. Without this field, the composite scoring pipeline
4105
+ // can't distinguish "this rule fires on AI slop" from "this rule
4106
+ // fires on sloppy code in general" — and gives zero weight to
4107
+ // both. See packages/engine/src/composite-scoring.ts:136,164.
4108
+ aiSpecific: external_exports.boolean().optional()
4100
4109
  })
4101
4110
  );
4102
4111
 
@@ -4162,11 +4171,6 @@ function syntaxCandidates(filePath, source) {
4162
4171
  { syntax: "typescript", jsx: true },
4163
4172
  { syntax: "typescript", jsx: false, tsx: false }
4164
4173
  ];
4165
- case "mjs":
4166
- return [
4167
- { syntax: "ecmascript", jsx: true },
4168
- { syntax: "ecmascript", jsx: false }
4169
- ];
4170
4174
  default:
4171
4175
  return [
4172
4176
  { syntax: "typescript", jsx: false, tsx: true },
@@ -4363,7 +4367,11 @@ var FPR_FLOOR = 1e-6;
4363
4367
  var RECALL_FLOOR = 1e-6;
4364
4368
  var LLR_CAP = 13.8;
4365
4369
  var DEFAULT_PRIOR_PREVALENCE = 0.3;
4366
- var ELIGIBLE_VERDICTS = /* @__PURE__ */ new Set(["USEFUL", "OK"]);
4370
+ var ELIGIBLE_VERDICTS = new Set(
4371
+ ["USEFUL", "OK", "NOISY", "INVERTED", "HYGIENE", "DORMANT"].filter(
4372
+ (v) => v !== "HYGIENE" && !isDefaultOff(v)
4373
+ )
4374
+ );
4367
4375
  function loadSignals(signalData) {
4368
4376
  const map = /* @__PURE__ */ new Map();
4369
4377
  for (const [ruleId, entry] of Object.entries(signalData)) {
@@ -34908,7 +34916,7 @@ var mathElementUniformityRule = createRule({
34908
34916
  if (!anchor) anchor = { line: el.line, column: el.column };
34909
34917
  }
34910
34918
  }
34911
- const values = [counts.button, counts.input, counts.select].filter((v) => v > 0);
34919
+ const values = [counts.button, counts.input, counts.select].filter((v) => typeof v === "number" && v > 0);
34912
34920
  if (values.length < 2) return issues;
34913
34921
  const max = Math.max(...values);
34914
34922
  const min = Math.min(...values);
@@ -34957,7 +34965,7 @@ var mathGridUniformityRule = createRule({
34957
34965
  const { h, vocab, total } = shannonEntropy(counts);
34958
34966
  if (total < 4) return issues;
34959
34967
  if (h > 1) return issues;
34960
- const anchor = facts.v2 ? flatClassNames(facts.v2)[0] : { line: 1, column: 1 };
34968
+ const anchor = facts.v2 ? flatClassNames(facts.v2)[0] ?? { line: 1, column: 1 } : { line: 1, column: 1 };
34961
34969
  issues.push({
34962
34970
  ruleId: "layout/math-grid-uniformity",
34963
34971
  category: "layout",
@@ -35840,7 +35848,7 @@ var mathGiniClassUsageRule = createRule({
35840
35848
  if (g < 0.5) return issues;
35841
35849
  const sorted = [...counts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3);
35842
35850
  const topStr = sorted.map(([k, v]) => `${k}\xD7${v}`).join(", ");
35843
- const anchor = facts.v2 ? flatClassNames(facts.v2)[0] : { line: 1, column: 1 };
35851
+ const anchor = facts.v2 ? flatClassNames(facts.v2)[0] ?? { line: 1, column: 1 } : { line: 1, column: 1 };
35844
35852
  issues.push({
35845
35853
  ruleId: "logic/math-gini-class-usage",
35846
35854
  category: "logic",
@@ -37659,7 +37667,7 @@ function looksRealistic(value) {
37659
37667
  }
37660
37668
 
37661
37669
  // src/rules/test/missing-edge-case.ts
37662
- var import_core3 = require("@swc/core");
37670
+ var import_core4 = require("@swc/core");
37663
37671
  var import_node_fs11 = require("fs");
37664
37672
  var MAX_PER_FILE = 20;
37665
37673
  var missingEdgeCaseRule = createRule({
@@ -37703,7 +37711,7 @@ var missingEdgeCaseRule = createRule({
37703
37711
  if (!source) return issues;
37704
37712
  let ast;
37705
37713
  try {
37706
- ast = (0, import_core3.parseSync)(source, {
37714
+ ast = (0, import_core4.parseSync)(source, {
37707
37715
  syntax: "typescript",
37708
37716
  tsx: filePath.endsWith("tsx") || filePath.endsWith("jsx"),
37709
37717
  target: "es2022"
@@ -38820,7 +38828,7 @@ var mathFontEntropyRule = createRule({
38820
38828
  const { h, vocab, total } = shannonEntropy(counts);
38821
38829
  if (total < 6) return issues;
38822
38830
  if (h > 1.4) return issues;
38823
- const anchor = flatClassNames(facts.v2)[0];
38831
+ const anchor = flatClassNames(facts.v2)[0] ?? { line: 1, column: 1 };
38824
38832
  issues.push({
38825
38833
  ruleId: "visual/math-font-entropy",
38826
38834
  category: "visual",
@@ -38942,7 +38950,7 @@ var mathRoundedEntropyRule = createRule({
38942
38950
  const { h, vocab, total } = shannonEntropy(counts);
38943
38951
  if (total < 6) return issues;
38944
38952
  if (h > 1.8) return issues;
38945
- const anchor = flatClassNames(facts.v2)[0];
38953
+ const anchor = flatClassNames(facts.v2)[0] ?? { line: 1, column: 1 };
38946
38954
  issues.push({
38947
38955
  ruleId: "visual/math-rounded-entropy",
38948
38956
  category: "visual",
@@ -38981,7 +38989,7 @@ var mathSpacingEntropyRule = createRule({
38981
38989
  const { h, vocab, total } = shannonEntropy(counts);
38982
38990
  if (total < 10) return issues;
38983
38991
  if (h > 1.5) return issues;
38984
- const anchor = flatClassNames(facts.v2)[0];
38992
+ const anchor = flatClassNames(facts.v2)[0] ?? { line: 1, column: 1 };
38985
38993
  issues.push({
38986
38994
  ruleId: "visual/math-spacing-entropy",
38987
38995
  category: "visual",
@@ -4065,6 +4065,9 @@ var VERDICTS = [
4065
4065
  "HYGIENE",
4066
4066
  "DORMANT"
4067
4067
  ];
4068
+ function isDefaultOff(verdict) {
4069
+ return verdict === "NOISY" || verdict === "INVERTED" || verdict === "DORMANT";
4070
+ }
4068
4071
  var signalStrengthSchema = external_exports.record(
4069
4072
  external_exports.string(),
4070
4073
  // ruleId
@@ -4077,7 +4080,13 @@ var signalStrengthSchema = external_exports.record(
4077
4080
  verdict: external_exports.enum(VERDICTS),
4078
4081
  // defaultOff is opt-in per-rule. Absent = the rule follows
4079
4082
  // isDefaultOff(verdict). Present (true or false) = user override.
4080
- defaultOff: external_exports.boolean().optional()
4083
+ defaultOff: external_exports.boolean().optional(),
4084
+ // v0.17.3 (B5): mark rules whose signal is specific to AI
4085
+ // generation. Without this field, the composite scoring pipeline
4086
+ // can't distinguish "this rule fires on AI slop" from "this rule
4087
+ // fires on sloppy code in general" — and gives zero weight to
4088
+ // both. See packages/engine/src/composite-scoring.ts:136,164.
4089
+ aiSpecific: external_exports.boolean().optional()
4081
4090
  })
4082
4091
  );
4083
4092
 
@@ -4143,11 +4152,6 @@ function syntaxCandidates(filePath, source) {
4143
4152
  { syntax: "typescript", jsx: true },
4144
4153
  { syntax: "typescript", jsx: false, tsx: false }
4145
4154
  ];
4146
- case "mjs":
4147
- return [
4148
- { syntax: "ecmascript", jsx: true },
4149
- { syntax: "ecmascript", jsx: false }
4150
- ];
4151
4155
  default:
4152
4156
  return [
4153
4157
  { syntax: "typescript", jsx: false, tsx: true },
@@ -4344,7 +4348,11 @@ var FPR_FLOOR = 1e-6;
4344
4348
  var RECALL_FLOOR = 1e-6;
4345
4349
  var LLR_CAP = 13.8;
4346
4350
  var DEFAULT_PRIOR_PREVALENCE = 0.3;
4347
- var ELIGIBLE_VERDICTS = /* @__PURE__ */ new Set(["USEFUL", "OK"]);
4351
+ var ELIGIBLE_VERDICTS = new Set(
4352
+ ["USEFUL", "OK", "NOISY", "INVERTED", "HYGIENE", "DORMANT"].filter(
4353
+ (v) => v !== "HYGIENE" && !isDefaultOff(v)
4354
+ )
4355
+ );
4348
4356
  function loadSignals(signalData) {
4349
4357
  const map = /* @__PURE__ */ new Map();
4350
4358
  for (const [ruleId, entry] of Object.entries(signalData)) {
@@ -34889,7 +34897,7 @@ var mathElementUniformityRule = createRule({
34889
34897
  if (!anchor) anchor = { line: el.line, column: el.column };
34890
34898
  }
34891
34899
  }
34892
- const values = [counts.button, counts.input, counts.select].filter((v) => v > 0);
34900
+ const values = [counts.button, counts.input, counts.select].filter((v) => typeof v === "number" && v > 0);
34893
34901
  if (values.length < 2) return issues;
34894
34902
  const max = Math.max(...values);
34895
34903
  const min = Math.min(...values);
@@ -34938,7 +34946,7 @@ var mathGridUniformityRule = createRule({
34938
34946
  const { h, vocab, total } = shannonEntropy(counts);
34939
34947
  if (total < 4) return issues;
34940
34948
  if (h > 1) return issues;
34941
- const anchor = facts.v2 ? flatClassNames(facts.v2)[0] : { line: 1, column: 1 };
34949
+ const anchor = facts.v2 ? flatClassNames(facts.v2)[0] ?? { line: 1, column: 1 } : { line: 1, column: 1 };
34942
34950
  issues.push({
34943
34951
  ruleId: "layout/math-grid-uniformity",
34944
34952
  category: "layout",
@@ -35821,7 +35829,7 @@ var mathGiniClassUsageRule = createRule({
35821
35829
  if (g < 0.5) return issues;
35822
35830
  const sorted = [...counts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3);
35823
35831
  const topStr = sorted.map(([k, v]) => `${k}\xD7${v}`).join(", ");
35824
- const anchor = facts.v2 ? flatClassNames(facts.v2)[0] : { line: 1, column: 1 };
35832
+ const anchor = facts.v2 ? flatClassNames(facts.v2)[0] ?? { line: 1, column: 1 } : { line: 1, column: 1 };
35825
35833
  issues.push({
35826
35834
  ruleId: "logic/math-gini-class-usage",
35827
35835
  category: "logic",
@@ -38801,7 +38809,7 @@ var mathFontEntropyRule = createRule({
38801
38809
  const { h, vocab, total } = shannonEntropy(counts);
38802
38810
  if (total < 6) return issues;
38803
38811
  if (h > 1.4) return issues;
38804
- const anchor = flatClassNames(facts.v2)[0];
38812
+ const anchor = flatClassNames(facts.v2)[0] ?? { line: 1, column: 1 };
38805
38813
  issues.push({
38806
38814
  ruleId: "visual/math-font-entropy",
38807
38815
  category: "visual",
@@ -38923,7 +38931,7 @@ var mathRoundedEntropyRule = createRule({
38923
38931
  const { h, vocab, total } = shannonEntropy(counts);
38924
38932
  if (total < 6) return issues;
38925
38933
  if (h > 1.8) return issues;
38926
- const anchor = flatClassNames(facts.v2)[0];
38934
+ const anchor = flatClassNames(facts.v2)[0] ?? { line: 1, column: 1 };
38927
38935
  issues.push({
38928
38936
  ruleId: "visual/math-rounded-entropy",
38929
38937
  category: "visual",
@@ -38962,7 +38970,7 @@ var mathSpacingEntropyRule = createRule({
38962
38970
  const { h, vocab, total } = shannonEntropy(counts);
38963
38971
  if (total < 10) return issues;
38964
38972
  if (h > 1.5) return issues;
38965
- const anchor = flatClassNames(facts.v2)[0];
38973
+ const anchor = flatClassNames(facts.v2)[0] ?? { line: 1, column: 1 };
38966
38974
  issues.push({
38967
38975
  ruleId: "visual/math-spacing-entropy",
38968
38976
  category: "visual",
package/dist/index.cjs CHANGED
@@ -30242,7 +30242,7 @@ var init_math_element_uniformity = __esm({
30242
30242
  if (!anchor) anchor = { line: el.line, column: el.column };
30243
30243
  }
30244
30244
  }
30245
- const values = [counts.button, counts.input, counts.select].filter((v) => v > 0);
30245
+ const values = [counts.button, counts.input, counts.select].filter((v) => typeof v === "number" && v > 0);
30246
30246
  if (values.length < 2) return issues;
30247
30247
  const max = Math.max(...values);
30248
30248
  const min = Math.min(...values);
@@ -30300,7 +30300,7 @@ var init_math_grid_uniformity = __esm({
30300
30300
  const { h, vocab, total } = shannonEntropy(counts);
30301
30301
  if (total < 4) return issues;
30302
30302
  if (h > 1) return issues;
30303
- const anchor = facts.v2 ? flatClassNames(facts.v2)[0] : { line: 1, column: 1 };
30303
+ const anchor = facts.v2 ? flatClassNames(facts.v2)[0] ?? { line: 1, column: 1 } : { line: 1, column: 1 };
30304
30304
  issues.push({
30305
30305
  ruleId: "layout/math-grid-uniformity",
30306
30306
  category: "layout",
@@ -34733,59 +34733,77 @@ __export(dist_exports, {
34733
34733
  writeCacheFromInventory: () => writeCacheFromInventory,
34734
34734
  writeJsonAtomic: () => writeJsonAtomic
34735
34735
  });
34736
+ function isRecord(v) {
34737
+ return typeof v === "object" && v !== null && !Array.isArray(v);
34738
+ }
34739
+ function isStringArray(v) {
34740
+ return Array.isArray(v) && v.every((i) => typeof i === "string");
34741
+ }
34742
+ function isNumber(v) {
34743
+ return typeof v === "number" && Number.isFinite(v);
34744
+ }
34736
34745
  function isStructurePattern(value) {
34737
- if (typeof value !== "object" || value === null) return false;
34738
- const v = value;
34739
- return typeof v.category === "string" && typeof v.name === "string" && Array.isArray(v.imports) && v.imports.every((i) => typeof i === "string") && typeof v.fileCount === "number";
34746
+ if (!isRecord(value)) return false;
34747
+ return typeof value.category === "string" && typeof value.name === "string" && isStringArray(value.imports) && isNumber(value.fileCount);
34740
34748
  }
34741
34749
  function isComponentFingerprint(value) {
34742
- if (typeof value !== "object" || value === null) return false;
34743
- const v = value;
34744
- return typeof v.name === "string" && Array.isArray(v.files) && v.files.every((f) => typeof f === "string") && typeof v.fingerprint === "string" && Array.isArray(v.hooks) && v.hooks.every((h) => typeof h === "string") && Array.isArray(v.props) && v.props.every((p) => typeof p === "string") && typeof v.line === "number" && typeof v.endLine === "number";
34750
+ if (!isRecord(value)) return false;
34751
+ return typeof value.name === "string" && isStringArray(value.files) && typeof value.fingerprint === "string" && isStringArray(value.hooks) && isStringArray(value.props) && isNumber(value.line) && isNumber(value.endLine);
34752
+ }
34753
+ function isVersion3(value) {
34754
+ return value === STRUCTURE_SCHEMA_VERSION;
34745
34755
  }
34746
34756
  function isInventoryFile(value) {
34747
- if (typeof value !== "object" || value === null) return false;
34748
- const v = value;
34749
- return v.version === STRUCTURE_SCHEMA_VERSION && typeof v.generatedAt === "string" && typeof v.workspace === "string" && typeof v.scannedFiles === "number" && typeof v.scanDurationMs === "number" && Array.isArray(v.patterns) && Array.isArray(v.components) && v.patterns.every(isStructurePattern) && v.components.every(isComponentFingerprint);
34757
+ if (!isRecord(value)) return false;
34758
+ if (!isVersion3(value.version)) return false;
34759
+ if (typeof value.generatedAt !== "string") return false;
34760
+ if (typeof value.workspace !== "string") return false;
34761
+ if (!isNumber(value.scannedFiles)) return false;
34762
+ if (!isNumber(value.scanDurationMs)) return false;
34763
+ if (!Array.isArray(value.patterns) || !value.patterns.every(isStructurePattern)) return false;
34764
+ if (!Array.isArray(value.components) || !value.components.every(isComponentFingerprint)) return false;
34765
+ return true;
34750
34766
  }
34751
34767
  function isConstitutionFile(value) {
34752
- if (typeof value !== "object" || value === null) return false;
34753
- const v = value;
34754
- return v.version === STRUCTURE_SCHEMA_VERSION && typeof v.generatedAt === "string" && typeof v.workspace === "string" && typeof v.declared === "object" && v.declared !== null && Array.isArray(v.forbidden) && v.forbidden.every((f) => typeof f === "string") && Array.isArray(v.forbiddenPrefixes) && v.forbiddenPrefixes.every((f) => typeof f === "string");
34768
+ if (!isRecord(value)) return false;
34769
+ if (!isVersion3(value.version)) return false;
34770
+ if (typeof value.generatedAt !== "string") return false;
34771
+ if (typeof value.workspace !== "string") return false;
34772
+ if (!isRecord(value.declared)) return false;
34773
+ if (!isStringArray(value.forbidden)) return false;
34774
+ if (!isStringArray(value.forbiddenPrefixes)) return false;
34775
+ return true;
34755
34776
  }
34756
34777
  function isFileMtimeEntry(value) {
34757
- if (typeof value !== "object" || value === null) return false;
34758
- const v = value;
34759
- return typeof v.file === "string" && typeof v.mtimeMs === "number" && typeof v.hash === "string";
34778
+ if (!isRecord(value)) return false;
34779
+ return typeof value.file === "string" && isNumber(value.mtimeMs) && typeof value.hash === "string";
34760
34780
  }
34761
34781
  function isHealthFile(value) {
34762
- if (typeof value !== "object" || value === null) return false;
34763
- const v = value;
34764
- if (v.version !== STRUCTURE_SCHEMA_VERSION) return false;
34765
- if (typeof v.generatedAt !== "string") return false;
34766
- if (typeof v.workspace !== "string") return false;
34767
- if (typeof v.aiQuality !== "number") return false;
34768
- if (typeof v.engineeringHygiene !== "number") return false;
34769
- if (typeof v.security !== "number") return false;
34770
- if (typeof v.repositoryHealth !== "number") return false;
34771
- if (typeof v.issueCounts !== "object" || v.issueCounts === null) return false;
34772
- const counts = v.issueCounts;
34773
- if (typeof counts.high !== "number") return false;
34774
- if (typeof counts.medium !== "number") return false;
34775
- if (typeof counts.low !== "number") return false;
34776
- if (v.slopIndex !== void 0 && typeof v.slopIndex !== "number") return false;
34777
- if (v.categoryScores !== void 0) {
34778
- if (typeof v.categoryScores !== "object" || v.categoryScores === null) return false;
34779
- for (const score of Object.values(v.categoryScores)) {
34780
- if (typeof score !== "number") return false;
34781
- }
34782
- }
34783
- if (v.constitutionDrift !== void 0 && typeof v.constitutionDrift !== "number") return false;
34784
- if (v.topOffenseIds !== void 0) {
34785
- if (!Array.isArray(v.topOffenseIds)) return false;
34786
- if (!v.topOffenseIds.every((id) => typeof id === "string")) return false;
34787
- }
34788
- if (v.scanDurationMs !== void 0 && typeof v.scanDurationMs !== "number") return false;
34782
+ if (!isRecord(value)) return false;
34783
+ if (!isVersion3(value.version)) return false;
34784
+ if (typeof value.generatedAt !== "string") return false;
34785
+ if (typeof value.workspace !== "string") return false;
34786
+ if (!isNumber(value.aiQuality)) return false;
34787
+ if (!isNumber(value.engineeringHygiene)) return false;
34788
+ if (!isNumber(value.security)) return false;
34789
+ if (!isNumber(value.repositoryHealth)) return false;
34790
+ if (!isRecord(value.issueCounts)) return false;
34791
+ const counts = value.issueCounts;
34792
+ if (!isNumber(counts.high)) return false;
34793
+ if (!isNumber(counts.medium)) return false;
34794
+ if (!isNumber(counts.low)) return false;
34795
+ if (value.slopIndex !== void 0 && !isNumber(value.slopIndex)) return false;
34796
+ if (value.categoryScores !== void 0) {
34797
+ if (!isRecord(value.categoryScores)) return false;
34798
+ for (const score of Object.values(value.categoryScores)) {
34799
+ if (!isNumber(score)) return false;
34800
+ }
34801
+ }
34802
+ if (value.constitutionDrift !== void 0 && !isNumber(value.constitutionDrift)) return false;
34803
+ if (value.topOffenseIds !== void 0) {
34804
+ if (!isStringArray(value.topOffenseIds)) return false;
34805
+ }
34806
+ if (value.scanDurationMs !== void 0 && !isNumber(value.scanDurationMs)) return false;
34789
34807
  return true;
34790
34808
  }
34791
34809
  function inventoryPath(workspaceDir) {
@@ -34945,7 +34963,13 @@ var init_dist = __esm({
34945
34963
  verdict: external_exports.enum(VERDICTS),
34946
34964
  // defaultOff is opt-in per-rule. Absent = the rule follows
34947
34965
  // isDefaultOff(verdict). Present (true or false) = user override.
34948
- defaultOff: external_exports.boolean().optional()
34966
+ defaultOff: external_exports.boolean().optional(),
34967
+ // v0.17.3 (B5): mark rules whose signal is specific to AI
34968
+ // generation. Without this field, the composite scoring pipeline
34969
+ // can't distinguish "this rule fires on AI slop" from "this rule
34970
+ // fires on sloppy code in general" — and gives zero weight to
34971
+ // both. See packages/engine/src/composite-scoring.ts:136,164.
34972
+ aiSpecific: external_exports.boolean().optional()
34949
34973
  })
34950
34974
  );
34951
34975
  }
@@ -35133,11 +35157,6 @@ function syntaxCandidates(filePath, source) {
35133
35157
  { syntax: "typescript", jsx: true },
35134
35158
  { syntax: "typescript", jsx: false, tsx: false }
35135
35159
  ];
35136
- case "mjs":
35137
- return [
35138
- { syntax: "ecmascript", jsx: true },
35139
- { syntax: "ecmascript", jsx: false }
35140
- ];
35141
35160
  default:
35142
35161
  return [
35143
35162
  { syntax: "typescript", jsx: false, tsx: true },
@@ -36800,6 +36819,7 @@ var init_dist2 = __esm({
36800
36819
  import_path3 = require("path");
36801
36820
  import_crypto2 = require("crypto");
36802
36821
  init_dist();
36822
+ init_dist();
36803
36823
  import_crypto3 = require("crypto");
36804
36824
  import_promises2 = require("fs/promises");
36805
36825
  import_path4 = require("path");
@@ -36811,7 +36831,11 @@ var init_dist2 = __esm({
36811
36831
  RECALL_FLOOR = 1e-6;
36812
36832
  LLR_CAP = 13.8;
36813
36833
  DEFAULT_PRIOR_PREVALENCE = 0.3;
36814
- ELIGIBLE_VERDICTS = /* @__PURE__ */ new Set(["USEFUL", "OK"]);
36834
+ ELIGIBLE_VERDICTS = new Set(
36835
+ ["USEFUL", "OK", "NOISY", "INVERTED", "HYGIENE", "DORMANT"].filter(
36836
+ (v) => v !== "HYGIENE" && !isDefaultOff(v)
36837
+ )
36838
+ );
36815
36839
  SUFFIXES_TO_STRIP = [
36816
36840
  "RepositoryClient",
36817
36841
  "ServiceFactory",
@@ -37651,7 +37675,7 @@ var init_math_gini_class_usage = __esm({
37651
37675
  if (g < 0.5) return issues;
37652
37676
  const sorted = [...counts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3);
37653
37677
  const topStr = sorted.map(([k, v]) => `${k}\xD7${v}`).join(", ");
37654
- const anchor = facts.v2 ? flatClassNames(facts.v2)[0] : { line: 1, column: 1 };
37678
+ const anchor = facts.v2 ? flatClassNames(facts.v2)[0] ?? { line: 1, column: 1 } : { line: 1, column: 1 };
37655
37679
  issues.push({
37656
37680
  ruleId: "logic/math-gini-class-usage",
37657
37681
  category: "logic",
@@ -39911,11 +39935,11 @@ function coveredFunctionNames(names, testFileSources, cwd) {
39911
39935
  }
39912
39936
  return covered;
39913
39937
  }
39914
- var import_core3, import_node_fs10, MAX_PER_FILE, missingEdgeCaseRule;
39938
+ var import_core4, import_node_fs10, MAX_PER_FILE, missingEdgeCaseRule;
39915
39939
  var init_missing_edge_case = __esm({
39916
39940
  "src/rules/test/missing-edge-case.ts"() {
39917
39941
  "use strict";
39918
- import_core3 = require("@swc/core");
39942
+ import_core4 = require("@swc/core");
39919
39943
  import_node_fs10 = require("fs");
39920
39944
  init_rule();
39921
39945
  MAX_PER_FILE = 20;
@@ -39960,7 +39984,7 @@ var init_missing_edge_case = __esm({
39960
39984
  if (!source) return issues;
39961
39985
  let ast;
39962
39986
  try {
39963
- ast = (0, import_core3.parseSync)(source, {
39987
+ ast = (0, import_core4.parseSync)(source, {
39964
39988
  syntax: "typescript",
39965
39989
  tsx: filePath.endsWith("tsx") || filePath.endsWith("jsx"),
39966
39990
  target: "es2022"
@@ -40954,7 +40978,7 @@ var init_math_font_entropy = __esm({
40954
40978
  const { h, vocab, total } = shannonEntropy(counts);
40955
40979
  if (total < 6) return issues;
40956
40980
  if (h > 1.4) return issues;
40957
- const anchor = flatClassNames(facts.v2)[0];
40981
+ const anchor = flatClassNames(facts.v2)[0] ?? { line: 1, column: 1 };
40958
40982
  issues.push({
40959
40983
  ruleId: "visual/math-font-entropy",
40960
40984
  category: "visual",
@@ -41094,7 +41118,7 @@ var init_math_rounded_entropy = __esm({
41094
41118
  const { h, vocab, total } = shannonEntropy(counts);
41095
41119
  if (total < 6) return issues;
41096
41120
  if (h > 1.8) return issues;
41097
- const anchor = flatClassNames(facts.v2)[0];
41121
+ const anchor = flatClassNames(facts.v2)[0] ?? { line: 1, column: 1 };
41098
41122
  issues.push({
41099
41123
  ruleId: "visual/math-rounded-entropy",
41100
41124
  category: "visual",
@@ -41142,7 +41166,7 @@ var init_math_spacing_entropy = __esm({
41142
41166
  const { h, vocab, total } = shannonEntropy(counts);
41143
41167
  if (total < 10) return issues;
41144
41168
  if (h > 1.5) return issues;
41145
- const anchor = flatClassNames(facts.v2)[0];
41169
+ const anchor = flatClassNames(facts.v2)[0] ?? { line: 1, column: 1 };
41146
41170
  issues.push({
41147
41171
  ruleId: "visual/math-spacing-entropy",
41148
41172
  category: "visual",
@@ -48205,7 +48229,7 @@ function confirmMagicRateLiteral(line) {
48205
48229
  const probe = `let __probe = (${line});`;
48206
48230
  let ast;
48207
48231
  try {
48208
- ast = (0, import_core5.parseSync)(probe, { syntax: "ecmascript", target: "es2022" });
48232
+ ast = (0, import_core6.parseSync)(probe, { syntax: "ecmascript", target: "es2022" });
48209
48233
  } catch {
48210
48234
  return true;
48211
48235
  }
@@ -48484,11 +48508,11 @@ function extractIdentifier(line) {
48484
48508
  const m = line.match(/Math\.round\s*\(\s*([A-Za-z_$][\w$.]*)/);
48485
48509
  return m ? m[1] ?? "" : "<expr>";
48486
48510
  }
48487
- var import_core5, BUSINESS_LOGIC_WEIGHTS, CURRENCY_IDENTIFIERS, COMMON_RATE_LITERALS, MATH_ROUND_CENTS, MAGIC_RATE_DECIMAL, HARDCODED_CURRENCY_SYMBOL, SOURCE_EXTENSION, ZOD_STRING, ZOD_STRING_CALL, REQUIRED_ERROR, INVALID_TYPE_ERROR, HARDCODED_ISO_DATE, LOCALE_STRING_NO_OPTIONS, RAW_CURRENCY_IN_TEMPLATE;
48511
+ var import_core6, BUSINESS_LOGIC_WEIGHTS, CURRENCY_IDENTIFIERS, COMMON_RATE_LITERALS, MATH_ROUND_CENTS, MAGIC_RATE_DECIMAL, HARDCODED_CURRENCY_SYMBOL, SOURCE_EXTENSION, ZOD_STRING, ZOD_STRING_CALL, REQUIRED_ERROR, INVALID_TYPE_ERROR, HARDCODED_ISO_DATE, LOCALE_STRING_NO_OPTIONS, RAW_CURRENCY_IN_TEMPLATE;
48488
48512
  var init_business_logic = __esm({
48489
48513
  "src/engine/business-logic.ts"() {
48490
48514
  "use strict";
48491
- import_core5 = require("@swc/core");
48515
+ import_core6 = require("@swc/core");
48492
48516
  BUSINESS_LOGIC_WEIGHTS = {
48493
48517
  pricing: 3,
48494
48518
  validation: 2,
@@ -49859,6 +49883,13 @@ function mergeComponentsByName(components) {
49859
49883
  }
49860
49884
  byName.set(c.name, {
49861
49885
  ...c,
49886
+ // All visitors (rust.ts, php.ts, go.ts, …) push Components with
49887
+ // `files: [filePath]` — non-empty by construction. The JSON
49888
+ // Schema (inventory.schema.json) requires `files` to be
49889
+ // `[string, ...string[]]` (at least 1); the cast is safe under
49890
+ // the visitor invariant. If a future visitor ever produces an
49891
+ // empty files array, the runtime validator
49892
+ // (`isInventoryFile`) will reject the artifact at write time.
49862
49893
  files: c.files.slice(),
49863
49894
  hooks: c.hooks.slice(),
49864
49895
  props: c.props.slice()
@@ -55052,7 +55083,7 @@ function promptMultiSelect(rl, question, options, defaultValue) {
55052
55083
  resolve18([]);
55053
55084
  return;
55054
55085
  }
55055
- const selected = [...new Set(numbers.map((n) => options[n - 2]))];
55086
+ const selected = [...new Set(numbers.map((n) => options[n - 2]).filter((v) => v !== void 0))];
55056
55087
  resolve18(selected);
55057
55088
  });
55058
55089
  }
package/dist/index.js CHANGED
@@ -30235,7 +30235,7 @@ var init_math_element_uniformity = __esm({
30235
30235
  if (!anchor) anchor = { line: el.line, column: el.column };
30236
30236
  }
30237
30237
  }
30238
- const values = [counts.button, counts.input, counts.select].filter((v) => v > 0);
30238
+ const values = [counts.button, counts.input, counts.select].filter((v) => typeof v === "number" && v > 0);
30239
30239
  if (values.length < 2) return issues;
30240
30240
  const max = Math.max(...values);
30241
30241
  const min = Math.min(...values);
@@ -30293,7 +30293,7 @@ var init_math_grid_uniformity = __esm({
30293
30293
  const { h, vocab, total } = shannonEntropy(counts);
30294
30294
  if (total < 4) return issues;
30295
30295
  if (h > 1) return issues;
30296
- const anchor = facts.v2 ? flatClassNames(facts.v2)[0] : { line: 1, column: 1 };
30296
+ const anchor = facts.v2 ? flatClassNames(facts.v2)[0] ?? { line: 1, column: 1 } : { line: 1, column: 1 };
30297
30297
  issues.push({
30298
30298
  ruleId: "layout/math-grid-uniformity",
30299
30299
  category: "layout",
@@ -34728,59 +34728,77 @@ __export(dist_exports, {
34728
34728
  });
34729
34729
  import { writeFileSync, renameSync, existsSync as existsSync8, readFileSync as readFileSync9, statSync as statSync4, mkdirSync } from "fs";
34730
34730
  import { join as join9, dirname as dirname5 } from "path";
34731
+ function isRecord(v) {
34732
+ return typeof v === "object" && v !== null && !Array.isArray(v);
34733
+ }
34734
+ function isStringArray(v) {
34735
+ return Array.isArray(v) && v.every((i) => typeof i === "string");
34736
+ }
34737
+ function isNumber(v) {
34738
+ return typeof v === "number" && Number.isFinite(v);
34739
+ }
34731
34740
  function isStructurePattern(value) {
34732
- if (typeof value !== "object" || value === null) return false;
34733
- const v = value;
34734
- return typeof v.category === "string" && typeof v.name === "string" && Array.isArray(v.imports) && v.imports.every((i) => typeof i === "string") && typeof v.fileCount === "number";
34741
+ if (!isRecord(value)) return false;
34742
+ return typeof value.category === "string" && typeof value.name === "string" && isStringArray(value.imports) && isNumber(value.fileCount);
34735
34743
  }
34736
34744
  function isComponentFingerprint(value) {
34737
- if (typeof value !== "object" || value === null) return false;
34738
- const v = value;
34739
- return typeof v.name === "string" && Array.isArray(v.files) && v.files.every((f) => typeof f === "string") && typeof v.fingerprint === "string" && Array.isArray(v.hooks) && v.hooks.every((h) => typeof h === "string") && Array.isArray(v.props) && v.props.every((p) => typeof p === "string") && typeof v.line === "number" && typeof v.endLine === "number";
34745
+ if (!isRecord(value)) return false;
34746
+ return typeof value.name === "string" && isStringArray(value.files) && typeof value.fingerprint === "string" && isStringArray(value.hooks) && isStringArray(value.props) && isNumber(value.line) && isNumber(value.endLine);
34747
+ }
34748
+ function isVersion3(value) {
34749
+ return value === STRUCTURE_SCHEMA_VERSION;
34740
34750
  }
34741
34751
  function isInventoryFile(value) {
34742
- if (typeof value !== "object" || value === null) return false;
34743
- const v = value;
34744
- return v.version === STRUCTURE_SCHEMA_VERSION && typeof v.generatedAt === "string" && typeof v.workspace === "string" && typeof v.scannedFiles === "number" && typeof v.scanDurationMs === "number" && Array.isArray(v.patterns) && Array.isArray(v.components) && v.patterns.every(isStructurePattern) && v.components.every(isComponentFingerprint);
34752
+ if (!isRecord(value)) return false;
34753
+ if (!isVersion3(value.version)) return false;
34754
+ if (typeof value.generatedAt !== "string") return false;
34755
+ if (typeof value.workspace !== "string") return false;
34756
+ if (!isNumber(value.scannedFiles)) return false;
34757
+ if (!isNumber(value.scanDurationMs)) return false;
34758
+ if (!Array.isArray(value.patterns) || !value.patterns.every(isStructurePattern)) return false;
34759
+ if (!Array.isArray(value.components) || !value.components.every(isComponentFingerprint)) return false;
34760
+ return true;
34745
34761
  }
34746
34762
  function isConstitutionFile(value) {
34747
- if (typeof value !== "object" || value === null) return false;
34748
- const v = value;
34749
- return v.version === STRUCTURE_SCHEMA_VERSION && typeof v.generatedAt === "string" && typeof v.workspace === "string" && typeof v.declared === "object" && v.declared !== null && Array.isArray(v.forbidden) && v.forbidden.every((f) => typeof f === "string") && Array.isArray(v.forbiddenPrefixes) && v.forbiddenPrefixes.every((f) => typeof f === "string");
34763
+ if (!isRecord(value)) return false;
34764
+ if (!isVersion3(value.version)) return false;
34765
+ if (typeof value.generatedAt !== "string") return false;
34766
+ if (typeof value.workspace !== "string") return false;
34767
+ if (!isRecord(value.declared)) return false;
34768
+ if (!isStringArray(value.forbidden)) return false;
34769
+ if (!isStringArray(value.forbiddenPrefixes)) return false;
34770
+ return true;
34750
34771
  }
34751
34772
  function isFileMtimeEntry(value) {
34752
- if (typeof value !== "object" || value === null) return false;
34753
- const v = value;
34754
- return typeof v.file === "string" && typeof v.mtimeMs === "number" && typeof v.hash === "string";
34773
+ if (!isRecord(value)) return false;
34774
+ return typeof value.file === "string" && isNumber(value.mtimeMs) && typeof value.hash === "string";
34755
34775
  }
34756
34776
  function isHealthFile(value) {
34757
- if (typeof value !== "object" || value === null) return false;
34758
- const v = value;
34759
- if (v.version !== STRUCTURE_SCHEMA_VERSION) return false;
34760
- if (typeof v.generatedAt !== "string") return false;
34761
- if (typeof v.workspace !== "string") return false;
34762
- if (typeof v.aiQuality !== "number") return false;
34763
- if (typeof v.engineeringHygiene !== "number") return false;
34764
- if (typeof v.security !== "number") return false;
34765
- if (typeof v.repositoryHealth !== "number") return false;
34766
- if (typeof v.issueCounts !== "object" || v.issueCounts === null) return false;
34767
- const counts = v.issueCounts;
34768
- if (typeof counts.high !== "number") return false;
34769
- if (typeof counts.medium !== "number") return false;
34770
- if (typeof counts.low !== "number") return false;
34771
- if (v.slopIndex !== void 0 && typeof v.slopIndex !== "number") return false;
34772
- if (v.categoryScores !== void 0) {
34773
- if (typeof v.categoryScores !== "object" || v.categoryScores === null) return false;
34774
- for (const score of Object.values(v.categoryScores)) {
34775
- if (typeof score !== "number") return false;
34776
- }
34777
- }
34778
- if (v.constitutionDrift !== void 0 && typeof v.constitutionDrift !== "number") return false;
34779
- if (v.topOffenseIds !== void 0) {
34780
- if (!Array.isArray(v.topOffenseIds)) return false;
34781
- if (!v.topOffenseIds.every((id) => typeof id === "string")) return false;
34782
- }
34783
- if (v.scanDurationMs !== void 0 && typeof v.scanDurationMs !== "number") return false;
34777
+ if (!isRecord(value)) return false;
34778
+ if (!isVersion3(value.version)) return false;
34779
+ if (typeof value.generatedAt !== "string") return false;
34780
+ if (typeof value.workspace !== "string") return false;
34781
+ if (!isNumber(value.aiQuality)) return false;
34782
+ if (!isNumber(value.engineeringHygiene)) return false;
34783
+ if (!isNumber(value.security)) return false;
34784
+ if (!isNumber(value.repositoryHealth)) return false;
34785
+ if (!isRecord(value.issueCounts)) return false;
34786
+ const counts = value.issueCounts;
34787
+ if (!isNumber(counts.high)) return false;
34788
+ if (!isNumber(counts.medium)) return false;
34789
+ if (!isNumber(counts.low)) return false;
34790
+ if (value.slopIndex !== void 0 && !isNumber(value.slopIndex)) return false;
34791
+ if (value.categoryScores !== void 0) {
34792
+ if (!isRecord(value.categoryScores)) return false;
34793
+ for (const score of Object.values(value.categoryScores)) {
34794
+ if (!isNumber(score)) return false;
34795
+ }
34796
+ }
34797
+ if (value.constitutionDrift !== void 0 && !isNumber(value.constitutionDrift)) return false;
34798
+ if (value.topOffenseIds !== void 0) {
34799
+ if (!isStringArray(value.topOffenseIds)) return false;
34800
+ }
34801
+ if (value.scanDurationMs !== void 0 && !isNumber(value.scanDurationMs)) return false;
34784
34802
  return true;
34785
34803
  }
34786
34804
  function inventoryPath(workspaceDir) {
@@ -34938,7 +34956,13 @@ var init_dist = __esm({
34938
34956
  verdict: external_exports.enum(VERDICTS),
34939
34957
  // defaultOff is opt-in per-rule. Absent = the rule follows
34940
34958
  // isDefaultOff(verdict). Present (true or false) = user override.
34941
- defaultOff: external_exports.boolean().optional()
34959
+ defaultOff: external_exports.boolean().optional(),
34960
+ // v0.17.3 (B5): mark rules whose signal is specific to AI
34961
+ // generation. Without this field, the composite scoring pipeline
34962
+ // can't distinguish "this rule fires on AI slop" from "this rule
34963
+ // fires on sloppy code in general" — and gives zero weight to
34964
+ // both. See packages/engine/src/composite-scoring.ts:136,164.
34965
+ aiSpecific: external_exports.boolean().optional()
34942
34966
  })
34943
34967
  );
34944
34968
  }
@@ -35135,11 +35159,6 @@ function syntaxCandidates(filePath, source) {
35135
35159
  { syntax: "typescript", jsx: true },
35136
35160
  { syntax: "typescript", jsx: false, tsx: false }
35137
35161
  ];
35138
- case "mjs":
35139
- return [
35140
- { syntax: "ecmascript", jsx: true },
35141
- { syntax: "ecmascript", jsx: false }
35142
- ];
35143
35162
  default:
35144
35163
  return [
35145
35164
  { syntax: "typescript", jsx: false, tsx: true },
@@ -36796,6 +36815,7 @@ var init_dist2 = __esm({
36796
36815
  "../engine/dist/index.js"() {
36797
36816
  "use strict";
36798
36817
  init_dist();
36818
+ init_dist();
36799
36819
  SMOOTHING = 0.5;
36800
36820
  DEFAULT_PRIOR = { pAI: 0.5, pHuman: 0.5 };
36801
36821
  TELEMETRY_FILE = join22(".slopbrick", "structure.json");
@@ -36804,7 +36824,11 @@ var init_dist2 = __esm({
36804
36824
  RECALL_FLOOR = 1e-6;
36805
36825
  LLR_CAP = 13.8;
36806
36826
  DEFAULT_PRIOR_PREVALENCE = 0.3;
36807
- ELIGIBLE_VERDICTS = /* @__PURE__ */ new Set(["USEFUL", "OK"]);
36827
+ ELIGIBLE_VERDICTS = new Set(
36828
+ ["USEFUL", "OK", "NOISY", "INVERTED", "HYGIENE", "DORMANT"].filter(
36829
+ (v) => v !== "HYGIENE" && !isDefaultOff(v)
36830
+ )
36831
+ );
36808
36832
  SUFFIXES_TO_STRIP = [
36809
36833
  "RepositoryClient",
36810
36834
  "ServiceFactory",
@@ -37644,7 +37668,7 @@ var init_math_gini_class_usage = __esm({
37644
37668
  if (g < 0.5) return issues;
37645
37669
  const sorted = [...counts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3);
37646
37670
  const topStr = sorted.map(([k, v]) => `${k}\xD7${v}`).join(", ");
37647
- const anchor = facts.v2 ? flatClassNames(facts.v2)[0] : { line: 1, column: 1 };
37671
+ const anchor = facts.v2 ? flatClassNames(facts.v2)[0] ?? { line: 1, column: 1 } : { line: 1, column: 1 };
37648
37672
  issues.push({
37649
37673
  ruleId: "logic/math-gini-class-usage",
37650
37674
  category: "logic",
@@ -40947,7 +40971,7 @@ var init_math_font_entropy = __esm({
40947
40971
  const { h, vocab, total } = shannonEntropy(counts);
40948
40972
  if (total < 6) return issues;
40949
40973
  if (h > 1.4) return issues;
40950
- const anchor = flatClassNames(facts.v2)[0];
40974
+ const anchor = flatClassNames(facts.v2)[0] ?? { line: 1, column: 1 };
40951
40975
  issues.push({
40952
40976
  ruleId: "visual/math-font-entropy",
40953
40977
  category: "visual",
@@ -41087,7 +41111,7 @@ var init_math_rounded_entropy = __esm({
41087
41111
  const { h, vocab, total } = shannonEntropy(counts);
41088
41112
  if (total < 6) return issues;
41089
41113
  if (h > 1.8) return issues;
41090
- const anchor = flatClassNames(facts.v2)[0];
41114
+ const anchor = flatClassNames(facts.v2)[0] ?? { line: 1, column: 1 };
41091
41115
  issues.push({
41092
41116
  ruleId: "visual/math-rounded-entropy",
41093
41117
  category: "visual",
@@ -41135,7 +41159,7 @@ var init_math_spacing_entropy = __esm({
41135
41159
  const { h, vocab, total } = shannonEntropy(counts);
41136
41160
  if (total < 10) return issues;
41137
41161
  if (h > 1.5) return issues;
41138
- const anchor = flatClassNames(facts.v2)[0];
41162
+ const anchor = flatClassNames(facts.v2)[0] ?? { line: 1, column: 1 };
41139
41163
  issues.push({
41140
41164
  ruleId: "visual/math-spacing-entropy",
41141
41165
  category: "visual",
@@ -49858,6 +49882,13 @@ function mergeComponentsByName(components) {
49858
49882
  }
49859
49883
  byName.set(c.name, {
49860
49884
  ...c,
49885
+ // All visitors (rust.ts, php.ts, go.ts, …) push Components with
49886
+ // `files: [filePath]` — non-empty by construction. The JSON
49887
+ // Schema (inventory.schema.json) requires `files` to be
49888
+ // `[string, ...string[]]` (at least 1); the cast is safe under
49889
+ // the visitor invariant. If a future visitor ever produces an
49890
+ // empty files array, the runtime validator
49891
+ // (`isInventoryFile`) will reject the artifact at write time.
49861
49892
  files: c.files.slice(),
49862
49893
  hooks: c.hooks.slice(),
49863
49894
  props: c.props.slice()
@@ -54971,7 +55002,7 @@ function promptMultiSelect(rl, question, options, defaultValue) {
54971
55002
  resolve18([]);
54972
55003
  return;
54973
55004
  }
54974
- const selected = [...new Set(numbers.map((n) => options[n - 2]))];
55005
+ const selected = [...new Set(numbers.map((n) => options[n - 2]).filter((v) => v !== void 0))];
54975
55006
  resolve18(selected);
54976
55007
  });
54977
55008
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slopbrick",
3
- "version": "0.17.2",
3
+ "version": "0.17.4",
4
4
  "description": "Discovered, modeled, and governed repository structure. SlopBrick scans source code, classifies it against 95 rules in 15 categories, computes 4 scores (aiQuality, engineeringHygiene, security, repositoryHealth), and persists the structure for AI agents and CI.",
5
5
  "type": "module",
6
6
  "bin": {