@decantr/verifier 1.0.3 → 1.0.5

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/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/index.ts
2
- import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2 } from "fs";
2
+ import { existsSync as existsSync2, realpathSync, readdirSync, readFileSync as readFileSync2 } from "fs";
3
3
  import { readFile } from "fs/promises";
4
4
  import { extname as extname2, isAbsolute, join as join2, relative, resolve } from "path";
5
5
  import { evaluateGuard, isV3, validateEssence } from "@decantr/essence-spec";
@@ -1218,7 +1218,7 @@ function isAnonymousEntryLikeRoute(route) {
1218
1218
  return route === "/" || /(?:^|\/)(?:auth|login|log-in|signin|sign-in|signup|sign-up|register)(?:\/|$)/i.test(route);
1219
1219
  }
1220
1220
  function isProtectedLikeRoute(route) {
1221
- return /(?:^|\/)(?:app|dashboard|workspace|settings|billing|account|profile|admin|agents?|marketplace|transparency|logs?|metrics|config|console|studio|pipelines?|projects?|monitoring|overview)(?:\/|$)/i.test(
1221
+ return /(?:^|\/)(?:app|dashboard|workspace|settings|billing|account|profile|admin|agents?|marketplace|browse|search|listings?|seller|buyer|bookings?|favorites|messages?|orders?|checkout|cart|transparency|logs?|metrics|config|console|studio|pipelines?|projects?|monitoring|overview)(?:\/|$)/i.test(
1222
1222
  route
1223
1223
  );
1224
1224
  }
@@ -1378,7 +1378,7 @@ function appendTopologyFindings(findings, essence, reviewPack) {
1378
1378
  evidence: [
1379
1379
  "Protected app surfaces need explicit routes so post-auth entry points are unambiguous."
1380
1380
  ],
1381
- suggestedFix: "Add explicit primary routes such as `/dashboard` or `/app` for the authenticated experience."
1381
+ suggestedFix: "Add explicit primary routes such as `/dashboard`, `/app`, `/browse`, or `/listings` for the authenticated experience."
1382
1382
  })
1383
1383
  );
1384
1384
  }
@@ -1414,7 +1414,7 @@ function appendTopologyFindings(findings, essence, reviewPack) {
1414
1414
  severity: "warn",
1415
1415
  message: "Primary routes only expose auth-like destinations and do not appear to include a post-auth application surface.",
1416
1416
  evidence: [`Primary routes: ${topology.primaryRoutes.join(", ")}`],
1417
- suggestedFix: "Keep login and registration routes in the gateway section, and add at least one primary app route such as `/dashboard`, `/workspace`, or `/app`."
1417
+ suggestedFix: "Keep login and registration routes in the gateway section, and add at least one primary app route such as `/dashboard`, `/workspace`, `/app`, `/browse`, or `/listings`."
1418
1418
  })
1419
1419
  );
1420
1420
  }
@@ -1426,7 +1426,7 @@ function appendTopologyFindings(findings, essence, reviewPack) {
1426
1426
  severity: "warn",
1427
1427
  message: "Primary routes do not appear to include a clear post-auth application destination.",
1428
1428
  evidence: [`Primary routes: ${topology.primaryRoutes.join(", ")}`],
1429
- suggestedFix: "Use at least one primary route like `/dashboard`, `/workspace`, `/settings`, or `/app` so the authenticated surface is explicit."
1429
+ suggestedFix: "Use at least one primary route like `/dashboard`, `/workspace`, `/settings`, `/app`, `/browse`, or `/listings` so the authenticated surface is explicit."
1430
1430
  })
1431
1431
  );
1432
1432
  }
@@ -2022,13 +2022,13 @@ function appendSourceAuditFindings(findings, sourceAudit, essence, reviewPack) {
2022
2022
  id: "source-inline-styles-present",
2023
2023
  category: "Source Audit",
2024
2024
  severity: "warn",
2025
- message: "Source files still contain inline style attributes, which undermines the compiled treatment contract.",
2025
+ message: "Source files still contain disallowed inline style attributes, which undermines the compiled treatment contract.",
2026
2026
  evidence: buildSourceAuditEvidence(
2027
2027
  sourceAudit,
2028
2028
  sourceAudit.inlineStyles,
2029
- "Inline style attributes"
2029
+ "Disallowed inline style attributes"
2030
2030
  ),
2031
- suggestedFix: "Move inline styling into treatments, atoms, or design-token-backed classes so generation stays aligned with the Decantr contract."
2031
+ suggestedFix: "Move static visual styling into treatments, atoms, or design-token-backed classes. Inline style remains acceptable for Decantr CSS-variable writes and truly dynamic geometry."
2032
2032
  })
2033
2033
  );
2034
2034
  }
@@ -2894,7 +2894,7 @@ function appendSourceAuditFindings(findings, sourceAudit, essence, reviewPack) {
2894
2894
  })
2895
2895
  );
2896
2896
  }
2897
- if (topology.hasAuthFeature && topology.primaryRoutes.length > 0 && (sourceAudit.signInFlowSignals.count > 0 || sourceAudit.authEntrySignals.count > 0 && sourceAudit.signUpFlowSignals.count === 0 && sourceAudit.recoveryFlowSignals.count === 0) && sourceAudit.authProtectedRedirectSignals.count === 0) {
2897
+ if (topology.hasAuthFeature && topology.primaryRoutes.length > 0 && (sourceAudit.signInFlowSignals.count > 0 || sourceAudit.authEntrySignals.count > 0 && sourceAudit.signUpFlowSignals.count === 0 && sourceAudit.recoveryFlowSignals.count === 0) && sourceAudit.authProtectedRedirectSignals.count === 0 && sourceAudit.authSuccessSignals.count === 0) {
2898
2898
  findings.push(
2899
2899
  makeFinding({
2900
2900
  id: "source-auth-success-redirect-missing",
@@ -4378,6 +4378,7 @@ function getSkipNavTargetId(attributes, tagName) {
4378
4378
  function isAuthStorageKeyLiteral(node) {
4379
4379
  if (!node) return false;
4380
4380
  if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) {
4381
+ if (node.text === "decantr_authenticated") return false;
4381
4382
  return /(?:token|auth|jwt|session|access_token|refresh_token)/i.test(node.text);
4382
4383
  }
4383
4384
  return false;
@@ -8745,6 +8746,87 @@ function countLocalhostEndpointSignals(code) {
8745
8746
  function countWildcardPostMessageSignals(code) {
8746
8747
  return code.match(/\bpostMessage\s*\([^)]*,\s*['"`]\*['"`]/g)?.length ?? 0;
8747
8748
  }
8749
+ var DYNAMIC_GEOMETRY_STYLE_PROPS = /* @__PURE__ */ new Set([
8750
+ "top",
8751
+ "right",
8752
+ "bottom",
8753
+ "left",
8754
+ "inset",
8755
+ "width",
8756
+ "height",
8757
+ "minWidth",
8758
+ "minHeight",
8759
+ "maxWidth",
8760
+ "maxHeight",
8761
+ "transform",
8762
+ "transformOrigin",
8763
+ "opacity",
8764
+ "offsetPath",
8765
+ "offsetDistance",
8766
+ "cx",
8767
+ "cy",
8768
+ "x",
8769
+ "y"
8770
+ ]);
8771
+ function getJsxAttributeExpression(initializer) {
8772
+ if (!initializer || !ts.isJsxExpression(initializer)) return null;
8773
+ return initializer.expression ?? null;
8774
+ }
8775
+ function getStylePropertyName(name) {
8776
+ if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name)) {
8777
+ return name.text;
8778
+ }
8779
+ if (ts.isComputedPropertyName(name)) {
8780
+ return getExpressionLiteralValue(name.expression);
8781
+ }
8782
+ return null;
8783
+ }
8784
+ function unwrapStyleExpression(expression) {
8785
+ let current = expression;
8786
+ while (ts.isParenthesizedExpression(current) || ts.isAsExpression(current) || ts.isTypeAssertionExpression(current) || ts.isNonNullExpression(current) || ts.isSatisfiesExpression(current)) {
8787
+ current = current.expression;
8788
+ }
8789
+ return current;
8790
+ }
8791
+ function isDynamicStyleValue(expression) {
8792
+ const unwrapped = unwrapStyleExpression(expression);
8793
+ if (ts.isStringLiteral(unwrapped) || ts.isNoSubstitutionTemplateLiteral(unwrapped) || ts.isNumericLiteral(unwrapped) || unwrapped.kind === ts.SyntaxKind.TrueKeyword || unwrapped.kind === ts.SyntaxKind.FalseKeyword || unwrapped.kind === ts.SyntaxKind.NullKeyword) {
8794
+ return false;
8795
+ }
8796
+ return true;
8797
+ }
8798
+ function isAllowedInlineStyleObject(objectLiteral) {
8799
+ let sawGeometryProperty = false;
8800
+ let sawDynamicGeometryValue = false;
8801
+ for (const property of objectLiteral.properties) {
8802
+ if (!ts.isPropertyAssignment(property)) return false;
8803
+ const propertyName = getStylePropertyName(property.name);
8804
+ if (!propertyName) return false;
8805
+ if (propertyName.startsWith("--d-")) {
8806
+ continue;
8807
+ }
8808
+ if (!DYNAMIC_GEOMETRY_STYLE_PROPS.has(propertyName)) {
8809
+ return false;
8810
+ }
8811
+ sawGeometryProperty = true;
8812
+ if (isDynamicStyleValue(property.initializer)) {
8813
+ sawDynamicGeometryValue = true;
8814
+ }
8815
+ }
8816
+ return !sawGeometryProperty || sawDynamicGeometryValue;
8817
+ }
8818
+ function isAllowedInlineStyleAttribute(attribute, sourceFile, namedExpressions, namedPropertyAliases) {
8819
+ const expression = getJsxAttributeExpression(attribute.initializer);
8820
+ if (!expression) return false;
8821
+ const resolved = resolveObjectLiteralExpression(
8822
+ expression,
8823
+ sourceFile,
8824
+ namedExpressions,
8825
+ namedPropertyAliases,
8826
+ /* @__PURE__ */ new Set()
8827
+ );
8828
+ return resolved ? isAllowedInlineStyleObject(resolved.objectLiteral) : false;
8829
+ }
8748
8830
  function analyzeAstSignals(filePath, code) {
8749
8831
  const sourceFile = ts.createSourceFile(
8750
8832
  filePath,
@@ -8836,7 +8918,14 @@ function analyzeAstSignals(filePath, code) {
8836
8918
  const walk = (node) => {
8837
8919
  if (ts.isJsxAttribute(node)) {
8838
8920
  if (isPropertyNamed(node.name, "style") && node.initializer) {
8839
- signals.inlineStyleAttributeCount += 1;
8921
+ if (!isAllowedInlineStyleAttribute(
8922
+ node,
8923
+ sourceFile,
8924
+ namedExpressionInitializers,
8925
+ namedPropertyAliases
8926
+ )) {
8927
+ signals.inlineStyleAttributeCount += 1;
8928
+ }
8840
8929
  }
8841
8930
  if (isPropertyNamed(node.name, "dangerouslySetInnerHTML")) {
8842
8931
  signals.dangerousHtmlCount += 1;
@@ -10400,7 +10489,7 @@ function critiqueSource({
10400
10489
  })
10401
10490
  );
10402
10491
  }
10403
- const hasInlineStyleLiterals = astSignals.inlineStyleAttributeCount > 0 || /style\s*=\s*(?:\{\{|["'])/.test(code);
10492
+ const hasInlineStyleLiterals = astSignals.inlineStyleAttributeCount > 0 || /style\s*=\s*["']/.test(code);
10404
10493
  if (antiPatternIds.has("inline-styles") && hasInlineStyleLiterals) {
10405
10494
  findings.push(
10406
10495
  makeFinding({
@@ -10411,9 +10500,12 @@ function critiqueSource({
10411
10500
  "theme-consistency"
10412
10501
  ]),
10413
10502
  message: "Inline style literals were detected in the reviewed file.",
10414
- evidence: [filePath, `Inline style attributes: ${astSignals.inlineStyleAttributeCount}`],
10503
+ evidence: [
10504
+ filePath,
10505
+ `Disallowed inline style attributes: ${astSignals.inlineStyleAttributeCount}`
10506
+ ],
10415
10507
  file: filePath,
10416
- suggestedFix: "Replace inline visual values with treatments, decorators, and CSS variables from the compiled contract."
10508
+ suggestedFix: "Replace static inline visual values with treatments, decorators, and CSS variables from the compiled contract. Keep inline style only for Decantr CSS-variable writes and truly dynamic geometry."
10417
10509
  })
10418
10510
  );
10419
10511
  }
@@ -11070,8 +11162,18 @@ function critiqueSource({
11070
11162
  reviewPack
11071
11163
  };
11072
11164
  }
11165
+ function resolveProjectFilePath(projectRoot, filePath) {
11166
+ const root = existsSync2(projectRoot) ? realpathSync.native(projectRoot) : resolve(projectRoot);
11167
+ const candidatePath = isAbsolute(filePath) ? resolve(filePath) : resolve(root, filePath);
11168
+ const resolvedPath = existsSync2(candidatePath) ? realpathSync.native(candidatePath) : candidatePath;
11169
+ const relativePath = relative(root, resolvedPath);
11170
+ if (relativePath.startsWith("..") || isAbsolute(relativePath)) {
11171
+ throw new Error(`Path escapes the project root: ${filePath}`);
11172
+ }
11173
+ return resolvedPath;
11174
+ }
11073
11175
  async function critiqueFile(filePath, projectRoot) {
11074
- const resolvedPath = isAbsolute(filePath) ? filePath : resolve(projectRoot, filePath);
11176
+ const resolvedPath = resolveProjectFilePath(projectRoot, filePath);
11075
11177
  const code = await readFile(resolvedPath, "utf-8");
11076
11178
  const treatmentsCss = readTextIfExists(join2(projectRoot, "src", "styles", "treatments.css"));
11077
11179
  const reviewPack = loadReviewPack(projectRoot);