@decantr/verifier 2.3.0 → 2.3.2

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
@@ -896,7 +896,7 @@ function createContractAssertions(projectRoot, audit) {
896
896
  status: packManifest ? "passed" : "failed",
897
897
  message: packManifest ? "Compiled execution pack manifest is present." : "Compiled execution pack manifest is missing.",
898
898
  evidence: [redactEvidenceText(projectRoot, join2(contextDir, "pack-manifest.json"))],
899
- suggestedFix: packManifest ? void 0 : "Run `decantr refresh` to regenerate context packs."
899
+ suggestedFix: packManifest ? void 0 : "Run `decantr registry compile-packs decantr.essence.json --write-context` to hydrate the full context pack bundle."
900
900
  })
901
901
  );
902
902
  assertions.push(
@@ -908,7 +908,7 @@ function createContractAssertions(projectRoot, audit) {
908
908
  status: audit?.reviewPack ? "passed" : "failed",
909
909
  message: audit?.reviewPack ? "Compiled review pack is present." : "Compiled review pack is missing.",
910
910
  evidence: [redactEvidenceText(projectRoot, join2(contextDir, "review-pack.json"))],
911
- suggestedFix: audit?.reviewPack ? void 0 : "Run `decantr refresh` or hydrate the review pack from the registry."
911
+ suggestedFix: audit?.reviewPack ? void 0 : "Run `decantr registry compile-packs decantr.essence.json --write-context` so critique has the compiled review contract."
912
912
  })
913
913
  );
914
914
  const tokensPath = join2(projectRoot, "src", "styles", "tokens.css");
@@ -1043,6 +1043,71 @@ function loadPackManifest(projectRoot) {
1043
1043
  join2(projectRoot, ".decantr", "context", "pack-manifest.json")
1044
1044
  );
1045
1045
  }
1046
+ function collectPackManifestReferences(packManifest) {
1047
+ const references = [];
1048
+ if (packManifest.scaffold) {
1049
+ references.push({
1050
+ entryId: packManifest.scaffold.id || "scaffold",
1051
+ kind: "scaffold",
1052
+ markdown: packManifest.scaffold.markdown,
1053
+ json: packManifest.scaffold.json
1054
+ });
1055
+ }
1056
+ if (packManifest.review) {
1057
+ references.push({
1058
+ entryId: packManifest.review.id || "review",
1059
+ kind: "review",
1060
+ markdown: packManifest.review.markdown,
1061
+ json: packManifest.review.json
1062
+ });
1063
+ }
1064
+ for (const section of packManifest.sections ?? []) {
1065
+ references.push({
1066
+ entryId: section.id,
1067
+ kind: "section",
1068
+ markdown: section.markdown,
1069
+ json: section.json
1070
+ });
1071
+ }
1072
+ for (const page of packManifest.pages ?? []) {
1073
+ references.push({
1074
+ entryId: page.id,
1075
+ kind: "page",
1076
+ markdown: page.markdown,
1077
+ json: page.json
1078
+ });
1079
+ }
1080
+ for (const mutation of packManifest.mutations ?? []) {
1081
+ references.push({
1082
+ entryId: mutation.id,
1083
+ kind: "mutation",
1084
+ markdown: mutation.markdown,
1085
+ json: mutation.json
1086
+ });
1087
+ }
1088
+ return references;
1089
+ }
1090
+ function collectMissingPackManifestFiles(projectRoot, packManifest = loadPackManifest(projectRoot)) {
1091
+ if (!packManifest) return [];
1092
+ const contextDir = join2(projectRoot, ".decantr", "context");
1093
+ const missing = [];
1094
+ for (const reference of collectPackManifestReferences(packManifest)) {
1095
+ for (const field of ["markdown", "json"]) {
1096
+ const fileName = reference[field];
1097
+ if (!fileName) continue;
1098
+ const absolutePath = join2(contextDir, fileName);
1099
+ if (existsSync2(absolutePath)) continue;
1100
+ missing.push({
1101
+ entryId: reference.entryId,
1102
+ kind: reference.kind,
1103
+ field,
1104
+ relativePath: `.decantr/context/${fileName}`,
1105
+ absolutePath
1106
+ });
1107
+ }
1108
+ }
1109
+ return missing;
1110
+ }
1046
1111
  function readTextIfExists(path) {
1047
1112
  try {
1048
1113
  return existsSync2(path) ? readFileSync2(path, "utf-8") : "";
@@ -1050,6 +1115,11 @@ function readTextIfExists(path) {
1050
1115
  return "";
1051
1116
  }
1052
1117
  }
1118
+ function readProjectAdoptionMode(projectRoot) {
1119
+ const projectJson = readJsonIfExists(join2(projectRoot, ".decantr", "project.json"));
1120
+ const adoptionMode = projectJson?.initialized?.adoptionMode;
1121
+ return typeof adoptionMode === "string" ? adoptionMode : null;
1122
+ }
1053
1123
  function createSourceAuditBucket() {
1054
1124
  return { count: 0, files: [] };
1055
1125
  }
@@ -1115,6 +1185,12 @@ function isAuditableSourceFile(filePath) {
1115
1185
  if (/\.d\.ts$/i.test(filePath)) return false;
1116
1186
  return /\.(?:[cm]?[jt]sx?)$/i.test(filePath);
1117
1187
  }
1188
+ function isNonProductionSourceAuditFile(filePath) {
1189
+ const normalized = normalizeSourceAuditPath(filePath);
1190
+ return /(?:^|\/)(?:__tests__|__mocks__|tests?|specs?|fixtures?|mocks?|stories?)(?:\/|$)/i.test(
1191
+ normalized
1192
+ ) || /\.(?:test|spec|stories|story|fixture|mock)\.[cm]?[jt]sx?$/i.test(normalized);
1193
+ }
1118
1194
  function collectProjectSourceFiles(projectRoot) {
1119
1195
  const candidates = [
1120
1196
  "src",
@@ -1342,10 +1418,10 @@ function auditProjectSourceTree(projectRoot) {
1342
1418
  absolutePath: sourceFile,
1343
1419
  relativePath: relative(projectRoot, sourceFile) || sourceFile,
1344
1420
  code: readFileSync2(sourceFile, "utf-8")
1345
- }));
1421
+ })).filter((entry) => !isNonProductionSourceAuditFile(entry.relativePath));
1346
1422
  const clientReachableFiles = collectClientReachableSourceFiles(projectRoot, sourceEntries);
1347
1423
  const summary = {
1348
- filesChecked: sourceFiles.length,
1424
+ filesChecked: sourceEntries.length,
1349
1425
  inlineStyles: createSourceAuditBucket(),
1350
1426
  componentStyleTags: createSourceAuditBucket(),
1351
1427
  localCssRuntimeSignals: createSourceAuditBucket(),
@@ -2556,23 +2632,24 @@ function appendRuntimeAuditFindings(findings, runtimeAudit, projectRoot, topolog
2556
2632
  );
2557
2633
  }
2558
2634
  }
2559
- function appendSourceAuditFindings(findings, sourceAudit, essence, reviewPack) {
2635
+ function appendSourceAuditFindings(findings, sourceAudit, essence, reviewPack, adoptionMode) {
2560
2636
  if (sourceAudit.filesChecked === 0) {
2561
2637
  return;
2562
2638
  }
2639
+ const isContractOnly = adoptionMode === "contract-only";
2563
2640
  if (sourceAudit.inlineStyles.count > 0) {
2564
2641
  findings.push(
2565
2642
  makeFinding({
2566
2643
  id: "source-inline-styles-present",
2567
2644
  category: "Source Audit",
2568
2645
  severity: "warn",
2569
- message: "Source files still contain disallowed inline style attributes, which undermines the compiled treatment contract.",
2646
+ message: isContractOnly ? "Source files contain inline style attributes; contract-only projects should route static visual decisions through project-owned styling law." : "Source files still contain disallowed inline style attributes, which undermines the compiled treatment contract.",
2570
2647
  evidence: buildSourceAuditEvidence(
2571
2648
  sourceAudit,
2572
2649
  sourceAudit.inlineStyles,
2573
2650
  "Disallowed inline style attributes"
2574
2651
  ),
2575
- 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."
2652
+ suggestedFix: isContractOnly ? "Move static visual styling into the app design system, Tailwind/theme tokens, component variants, or accepted local rules. Keep inline style only for truly dynamic geometry." : "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."
2576
2653
  })
2577
2654
  );
2578
2655
  }
@@ -2582,17 +2659,17 @@ function appendSourceAuditFindings(findings, sourceAudit, essence, reviewPack) {
2582
2659
  id: "source-component-style-tags-present",
2583
2660
  category: "Source Audit",
2584
2661
  severity: "warn",
2585
- message: "Source files inject component-scoped style tags or dynamic style elements, which bypass the compiled Decantr layer contract.",
2662
+ message: isContractOnly ? "Source files inject component-scoped style tags or dynamic style elements, which makes project-owned styling rules harder to enforce." : "Source files inject component-scoped style tags or dynamic style elements, which bypass the compiled Decantr layer contract.",
2586
2663
  evidence: buildSourceAuditEvidence(
2587
2664
  sourceAudit,
2588
2665
  sourceAudit.componentStyleTags,
2589
2666
  "Component-level style tag signals"
2590
2667
  ),
2591
- suggestedFix: "Move shared keyframes, media queries, and visual rules into global.css or treatments.css so styling stays inside the reviewed Decantr layer stack."
2668
+ suggestedFix: isContractOnly ? "Move shared keyframes, media queries, and visual rules into the project stylesheet, component library, or accepted local pattern/rule manifest." : "Move shared keyframes, media queries, and visual rules into global.css or treatments.css so styling stays inside the reviewed Decantr layer stack."
2592
2669
  })
2593
2670
  );
2594
2671
  }
2595
- if (sourceAudit.localCssRuntimeSignals.count > 0) {
2672
+ if (!isContractOnly && sourceAudit.localCssRuntimeSignals.count > 0) {
2596
2673
  findings.push(
2597
2674
  makeFinding({
2598
2675
  id: "source-local-css-runtime-stub-present",
@@ -3695,6 +3772,7 @@ async function auditProject(projectRoot) {
3695
3772
  const findings = [];
3696
3773
  const reviewPack = loadReviewPack(projectRoot);
3697
3774
  const packManifest = loadPackManifest(projectRoot);
3775
+ const adoptionMode = readProjectAdoptionMode(projectRoot);
3698
3776
  const runtimeAudit = emptyRuntimeAudit();
3699
3777
  if (!existsSync2(essencePath)) {
3700
3778
  findings.push(
@@ -3775,10 +3853,25 @@ async function auditProject(projectRoot) {
3775
3853
  severity: "warn",
3776
3854
  message: "Compiled execution pack manifest is missing.",
3777
3855
  evidence: [join2(projectRoot, ".decantr", "context", "pack-manifest.json")],
3778
- suggestedFix: "Run `decantr refresh` to regenerate scaffold, review, mutation, section, and page packs."
3856
+ suggestedFix: "Run `decantr registry compile-packs decantr.essence.json --write-context` to hydrate scaffold, review, mutation, section, and page packs."
3779
3857
  })
3780
3858
  );
3781
3859
  } else {
3860
+ const missingPackFiles = collectMissingPackManifestFiles(projectRoot, packManifest);
3861
+ if (missingPackFiles.length > 0) {
3862
+ findings.push(
3863
+ makeFinding({
3864
+ id: "pack-manifest-referenced-files-missing",
3865
+ category: "Execution Packs",
3866
+ severity: "warn",
3867
+ message: "The compiled execution pack manifest references files that are missing.",
3868
+ evidence: missingPackFiles.slice(0, 12).map(
3869
+ (missing) => `${missing.kind}:${missing.entryId}:${missing.field} -> ${missing.relativePath}`
3870
+ ),
3871
+ suggestedFix: "Regenerate or hydrate the full execution pack bundle so every file named by pack-manifest.json exists beside it."
3872
+ })
3873
+ );
3874
+ }
3782
3875
  if (!packManifest.scaffold) {
3783
3876
  findings.push(
3784
3877
  makeFinding({
@@ -3811,7 +3904,7 @@ async function auditProject(projectRoot) {
3811
3904
  severity: "info",
3812
3905
  message: "No mutation packs were found in the manifest.",
3813
3906
  evidence: ["pack-manifest.json"],
3814
- suggestedFix: "Run `decantr refresh` to regenerate mutation task packs."
3907
+ suggestedFix: "Run `decantr registry compile-packs decantr.essence.json --write-context` to hydrate mutation task packs."
3815
3908
  })
3816
3909
  );
3817
3910
  }
@@ -3824,7 +3917,7 @@ async function auditProject(projectRoot) {
3824
3917
  severity: "warn",
3825
3918
  message: "The compiled review pack file is missing.",
3826
3919
  evidence: [join2(projectRoot, ".decantr", "context", "review-pack.json")],
3827
- suggestedFix: "Regenerate context with `decantr refresh`, or hydrate the hosted review contract with `decantr registry get-pack review --write-context`, so critique consumers can anchor findings to the compiled review contract."
3920
+ suggestedFix: "Hydrate the full hosted context bundle with `decantr registry compile-packs decantr.essence.json --write-context` so critique consumers can anchor findings to the compiled review contract."
3828
3921
  })
3829
3922
  );
3830
3923
  }
@@ -3838,7 +3931,7 @@ async function auditProject(projectRoot) {
3838
3931
  summarizeTopology(essence, reviewPack),
3839
3932
  sourceAudit
3840
3933
  );
3841
- appendSourceAuditFindings(findings, sourceAudit, essence, reviewPack);
3934
+ appendSourceAuditFindings(findings, sourceAudit, essence, reviewPack, adoptionMode);
3842
3935
  appendStyleContractFindings(findings, styleAudit, essence);
3843
3936
  const summary = {
3844
3937
  errorCount: findings.filter((finding) => finding.severity === "error").length,
@@ -4555,14 +4648,17 @@ function getObjectLiteralPropertyExpression(objectLiteral, propertyName, namedEx
4555
4648
  }
4556
4649
  return null;
4557
4650
  }
4558
- function resolveObjectLiteralExpressionAtPropertyPath(expression, sourceFile, propertyPath, namedExpressions, namedPropertyAliases, seenIdentifiers, seenFunctions) {
4651
+ var MAX_OPEN_REDIRECT_RESOLUTION_DEPTH = 40;
4652
+ function resolveObjectLiteralExpressionAtPropertyPath(expression, sourceFile, propertyPath, namedExpressions, namedPropertyAliases, seenIdentifiers, seenFunctions, depth = 0) {
4653
+ if (depth > MAX_OPEN_REDIRECT_RESOLUTION_DEPTH) return null;
4559
4654
  let resolvedObjectLiteral = resolveObjectLiteralExpression(
4560
4655
  expression,
4561
4656
  sourceFile,
4562
4657
  namedExpressions,
4563
4658
  namedPropertyAliases,
4564
4659
  seenIdentifiers,
4565
- seenFunctions
4660
+ seenFunctions,
4661
+ depth + 1
4566
4662
  );
4567
4663
  if (!resolvedObjectLiteral) return null;
4568
4664
  for (const propertyName of propertyPath) {
@@ -4578,13 +4674,15 @@ function resolveObjectLiteralExpressionAtPropertyPath(expression, sourceFile, pr
4578
4674
  resolvedObjectLiteral.namedExpressions,
4579
4675
  namedPropertyAliases,
4580
4676
  seenIdentifiers,
4581
- seenFunctions
4677
+ seenFunctions,
4678
+ depth + 1
4582
4679
  );
4583
4680
  if (!resolvedObjectLiteral) return null;
4584
4681
  }
4585
4682
  return resolvedObjectLiteral;
4586
4683
  }
4587
- function resolveReturnedObjectLiteralExpression(functionLike, args, sourceFile, namedExpressions, namedPropertyAliases, seenIdentifiers, seenFunctions) {
4684
+ function resolveReturnedObjectLiteralExpression(functionLike, args, sourceFile, namedExpressions, namedPropertyAliases, seenIdentifiers, seenFunctions, depth = 0) {
4685
+ if (depth > MAX_OPEN_REDIRECT_RESOLUTION_DEPTH) return null;
4588
4686
  const functionKey = getFunctionLikeCacheKey(functionLike);
4589
4687
  if (seenFunctions.has(functionKey)) return null;
4590
4688
  seenFunctions.add(functionKey);
@@ -4596,7 +4694,8 @@ function resolveReturnedObjectLiteralExpression(functionLike, args, sourceFile,
4596
4694
  boundExpressions,
4597
4695
  namedPropertyAliases,
4598
4696
  new Set(seenIdentifiers),
4599
- seenFunctions
4697
+ seenFunctions,
4698
+ depth + 1
4600
4699
  );
4601
4700
  if (objectLiteral) {
4602
4701
  seenFunctions.delete(functionKey);
@@ -4606,8 +4705,9 @@ function resolveReturnedObjectLiteralExpression(functionLike, args, sourceFile,
4606
4705
  seenFunctions.delete(functionKey);
4607
4706
  return null;
4608
4707
  }
4609
- function resolveObjectLiteralExpression(expression, sourceFile, namedExpressions, namedPropertyAliases, seenIdentifiers, seenFunctions = /* @__PURE__ */ new Set()) {
4708
+ function resolveObjectLiteralExpression(expression, sourceFile, namedExpressions, namedPropertyAliases, seenIdentifiers, seenFunctions = /* @__PURE__ */ new Set(), depth = 0) {
4610
4709
  if (!expression) return null;
4710
+ if (depth > MAX_OPEN_REDIRECT_RESOLUTION_DEPTH) return null;
4611
4711
  if (ts.isParenthesizedExpression(expression) || ts.isAsExpression(expression) || ts.isTypeAssertionExpression(expression) || ts.isNonNullExpression(expression)) {
4612
4712
  return resolveObjectLiteralExpression(
4613
4713
  expression.expression,
@@ -4615,7 +4715,8 @@ function resolveObjectLiteralExpression(expression, sourceFile, namedExpressions
4615
4715
  namedExpressions,
4616
4716
  namedPropertyAliases,
4617
4717
  seenIdentifiers,
4618
- seenFunctions
4718
+ seenFunctions,
4719
+ depth + 1
4619
4720
  );
4620
4721
  }
4621
4722
  if (ts.isObjectLiteralExpression(expression)) {
@@ -4632,7 +4733,8 @@ function resolveObjectLiteralExpression(expression, sourceFile, namedExpressions
4632
4733
  namedExpressions,
4633
4734
  namedPropertyAliases,
4634
4735
  seenIdentifiers,
4635
- seenFunctions
4736
+ seenFunctions,
4737
+ depth + 1
4636
4738
  );
4637
4739
  seenIdentifiers.delete(expression.text);
4638
4740
  return result2;
@@ -4647,7 +4749,8 @@ function resolveObjectLiteralExpression(expression, sourceFile, namedExpressions
4647
4749
  namedExpressions,
4648
4750
  namedPropertyAliases,
4649
4751
  seenIdentifiers,
4650
- seenFunctions
4752
+ seenFunctions,
4753
+ depth + 1
4651
4754
  );
4652
4755
  seenIdentifiers.delete(expression.text);
4653
4756
  return result;
@@ -4658,7 +4761,9 @@ function resolveObjectLiteralExpression(expression, sourceFile, namedExpressions
4658
4761
  sourceFile,
4659
4762
  namedExpressions,
4660
4763
  namedPropertyAliases,
4661
- /* @__PURE__ */ new Set()
4764
+ new Set(seenIdentifiers),
4765
+ seenFunctions,
4766
+ depth + 1
4662
4767
  );
4663
4768
  if (!functionResolution) return null;
4664
4769
  return resolveReturnedObjectLiteralExpression(
@@ -4668,7 +4773,8 @@ function resolveObjectLiteralExpression(expression, sourceFile, namedExpressions
4668
4773
  functionResolution.namedExpressions,
4669
4774
  namedPropertyAliases,
4670
4775
  seenIdentifiers,
4671
- seenFunctions
4776
+ seenFunctions,
4777
+ depth + 1
4672
4778
  );
4673
4779
  }
4674
4780
  if (isMemberAccessExpression(expression)) {
@@ -4681,7 +4787,8 @@ function resolveObjectLiteralExpression(expression, sourceFile, namedExpressions
4681
4787
  namedExpressions,
4682
4788
  namedPropertyAliases,
4683
4789
  seenIdentifiers,
4684
- seenFunctions
4790
+ seenFunctions,
4791
+ depth + 1
4685
4792
  );
4686
4793
  }
4687
4794
  return null;
@@ -4700,7 +4807,8 @@ function findFunctionLikeOnObjectLiteral(objectLiteral, propertyName, namedFunct
4700
4807
  }
4701
4808
  return null;
4702
4809
  }
4703
- function resolveReturnedTrackedFunctionLike(functionLike, args, sourceFile, namedExpressions, namedPropertyAliases, seenIdentifiers, seenFunctions) {
4810
+ function resolveReturnedTrackedFunctionLike(functionLike, args, sourceFile, namedExpressions, namedPropertyAliases, seenIdentifiers, seenFunctions, depth = 0) {
4811
+ if (depth > MAX_OPEN_REDIRECT_RESOLUTION_DEPTH) return null;
4704
4812
  const functionKey = getFunctionLikeCacheKey(functionLike);
4705
4813
  if (seenFunctions.has(functionKey)) return null;
4706
4814
  const nextSeenFunctions = new Set(seenFunctions);
@@ -4713,14 +4821,16 @@ function resolveReturnedTrackedFunctionLike(functionLike, args, sourceFile, name
4713
4821
  boundExpressions,
4714
4822
  namedPropertyAliases,
4715
4823
  new Set(seenIdentifiers),
4716
- nextSeenFunctions
4824
+ nextSeenFunctions,
4825
+ depth + 1
4717
4826
  );
4718
4827
  if (result) return result;
4719
4828
  }
4720
4829
  return null;
4721
4830
  }
4722
- function resolveTrackedOpenRedirectFunctionLike(expression, sourceFile, namedExpressions, namedPropertyAliases, seenIdentifiers, seenFunctions = /* @__PURE__ */ new Set()) {
4831
+ function resolveTrackedOpenRedirectFunctionLike(expression, sourceFile, namedExpressions, namedPropertyAliases, seenIdentifiers, seenFunctions = /* @__PURE__ */ new Set(), depth = 0) {
4723
4832
  if (!expression) return null;
4833
+ if (depth > MAX_OPEN_REDIRECT_RESOLUTION_DEPTH) return null;
4724
4834
  const namedFunctions = getCachedNamedFunctionLikeDeclarations(sourceFile);
4725
4835
  const directFunctionLike = resolveFunctionLikeHandler(expression, namedFunctions);
4726
4836
  if (directFunctionLike) {
@@ -4733,7 +4843,8 @@ function resolveTrackedOpenRedirectFunctionLike(expression, sourceFile, namedExp
4733
4843
  namedExpressions,
4734
4844
  namedPropertyAliases,
4735
4845
  seenIdentifiers,
4736
- seenFunctions
4846
+ seenFunctions,
4847
+ depth + 1
4737
4848
  );
4738
4849
  }
4739
4850
  if (isCallLikeExpression(expression)) {
@@ -4742,8 +4853,9 @@ function resolveTrackedOpenRedirectFunctionLike(expression, sourceFile, namedExp
4742
4853
  sourceFile,
4743
4854
  namedExpressions,
4744
4855
  namedPropertyAliases,
4745
- /* @__PURE__ */ new Set(),
4746
- seenFunctions
4856
+ new Set(seenIdentifiers),
4857
+ seenFunctions,
4858
+ depth + 1
4747
4859
  );
4748
4860
  if (!functionResolution) return null;
4749
4861
  return resolveReturnedTrackedFunctionLike(
@@ -4753,7 +4865,8 @@ function resolveTrackedOpenRedirectFunctionLike(expression, sourceFile, namedExp
4753
4865
  functionResolution.namedExpressions,
4754
4866
  namedPropertyAliases,
4755
4867
  seenIdentifiers,
4756
- seenFunctions
4868
+ seenFunctions,
4869
+ depth + 1
4757
4870
  );
4758
4871
  }
4759
4872
  if (ts.isIdentifier(expression)) {
@@ -4767,7 +4880,8 @@ function resolveTrackedOpenRedirectFunctionLike(expression, sourceFile, namedExp
4767
4880
  namedExpressions,
4768
4881
  namedPropertyAliases,
4769
4882
  seenIdentifiers,
4770
- seenFunctions
4883
+ seenFunctions,
4884
+ depth + 1
4771
4885
  );
4772
4886
  seenIdentifiers.delete(expression.text);
4773
4887
  if (result) return result;
@@ -4780,8 +4894,9 @@ function resolveTrackedOpenRedirectFunctionLike(expression, sourceFile, namedExp
4780
4894
  propertyAlias.propertyPath.slice(0, -1),
4781
4895
  namedExpressions,
4782
4896
  namedPropertyAliases,
4783
- /* @__PURE__ */ new Set(),
4784
- /* @__PURE__ */ new Set()
4897
+ new Set(seenIdentifiers),
4898
+ seenFunctions,
4899
+ depth + 1
4785
4900
  );
4786
4901
  if (!resolvedObjectLiteral2) return null;
4787
4902
  const functionLike2 = findFunctionLikeOnObjectLiteral(
@@ -4801,8 +4916,9 @@ function resolveTrackedOpenRedirectFunctionLike(expression, sourceFile, namedExp
4801
4916
  sourceFile,
4802
4917
  namedExpressions,
4803
4918
  namedPropertyAliases,
4804
- /* @__PURE__ */ new Set(),
4805
- /* @__PURE__ */ new Set()
4919
+ new Set(seenIdentifiers),
4920
+ seenFunctions,
4921
+ depth + 1
4806
4922
  );
4807
4923
  if (!resolvedObjectLiteral) return null;
4808
4924
  const functionLike = findFunctionLikeOnObjectLiteral(
@@ -10219,7 +10335,8 @@ function critiqueSource({
10219
10335
  code,
10220
10336
  reviewPack = null,
10221
10337
  packManifest = null,
10222
- treatmentsCss = ""
10338
+ treatmentsCss = "",
10339
+ adoptionMode = null
10223
10340
  }) {
10224
10341
  const codeLower = code.toLowerCase();
10225
10342
  const astSignals = analyzeAstSignals(filePath, code);
@@ -10227,67 +10344,92 @@ function critiqueSource({
10227
10344
  const findings = [];
10228
10345
  const scores = [];
10229
10346
  const antiPatternIds = new Set(reviewPack?.antiPatterns.map((entry) => entry.id) ?? []);
10347
+ const isContractOnly = adoptionMode === "contract-only";
10230
10348
  const usedTreatments = TREATMENT_CLASSES.filter((token) => code.includes(token));
10231
- const treatmentSuggestions = TREATMENT_CLASSES.filter((token) => !code.includes(token)).map(
10232
- (token) => `Consider using \`${token}\` where appropriate.`
10233
- );
10234
- scores.push({
10235
- category: "Treatment Usage",
10236
- focusArea: "treatment-usage",
10237
- score: scoreRatio(usedTreatments.length, TREATMENT_CLASSES.length),
10238
- details: `${usedTreatments.length}/${TREATMENT_CLASSES.length} base treatments used: ${usedTreatments.join(", ") || "none"}`,
10239
- suggestions: treatmentSuggestions
10240
- });
10241
- if (focusAreas.includes("treatment-usage") && usedTreatments.length === 0) {
10242
- findings.push(
10243
- makeFinding({
10244
- id: "treatment-usage-missing",
10245
- category: "Treatment Usage",
10246
- severity: resolveSeverityFromChecks(reviewPack, "warn", [
10247
- "page-pattern-contract",
10248
- "section-pattern-coverage"
10249
- ]),
10250
- message: "No Decantr treatment classes were detected in the reviewed file.",
10251
- evidence: [
10252
- filePath,
10253
- "Expected tokens include d-interactive, d-surface, d-data, d-control, d-section, d-annotation, d-label."
10254
- ],
10255
- file: filePath,
10256
- suggestedFix: "Apply the compiled treatment vocabulary instead of hand-rolled utility styling."
10257
- })
10258
- );
10349
+ if (isContractOnly) {
10350
+ scores.push({
10351
+ category: "Styling Authority",
10352
+ focusArea: "treatment-usage",
10353
+ score: 3,
10354
+ details: "Contract-only adoption does not require Decantr d-* treatment classes in source files.",
10355
+ suggestions: [
10356
+ "Codify project-owned component variants and local rules when button/card/surface drift needs mechanical enforcement."
10357
+ ]
10358
+ });
10359
+ } else {
10360
+ const treatmentSuggestions = TREATMENT_CLASSES.filter((token) => !code.includes(token)).map(
10361
+ (token) => `Consider using \`${token}\` where appropriate.`
10362
+ );
10363
+ scores.push({
10364
+ category: "Treatment Usage",
10365
+ focusArea: "treatment-usage",
10366
+ score: scoreRatio(usedTreatments.length, TREATMENT_CLASSES.length),
10367
+ details: `${usedTreatments.length}/${TREATMENT_CLASSES.length} base treatments used: ${usedTreatments.join(", ") || "none"}`,
10368
+ suggestions: treatmentSuggestions
10369
+ });
10370
+ if (focusAreas.includes("treatment-usage") && usedTreatments.length === 0) {
10371
+ findings.push(
10372
+ makeFinding({
10373
+ id: "treatment-usage-missing",
10374
+ category: "Treatment Usage",
10375
+ severity: resolveSeverityFromChecks(reviewPack, "warn", [
10376
+ "page-pattern-contract",
10377
+ "section-pattern-coverage"
10378
+ ]),
10379
+ message: "No Decantr treatment classes were detected in the reviewed file.",
10380
+ evidence: [
10381
+ filePath,
10382
+ "Expected tokens include d-interactive, d-surface, d-data, d-control, d-section, d-annotation, d-label."
10383
+ ],
10384
+ file: filePath,
10385
+ suggestedFix: "Apply the compiled treatment vocabulary instead of hand-rolled utility styling."
10386
+ })
10387
+ );
10388
+ }
10259
10389
  }
10260
10390
  const decoratorNames = buildDecoratorInventory(treatmentsCss);
10261
10391
  const usedDecorators = decoratorNames.filter((name) => code.includes(name));
10262
10392
  const usesCssVars = code.includes("var(--");
10263
- scores.push({
10264
- category: "Theme Consistency",
10265
- focusArea: "theme-consistency",
10266
- score: decoratorNames.length > 0 ? scoreRatio((usedDecorators.length > 0 ? 1 : 0) + (usesCssVars ? 1 : 0), 2) : usesCssVars ? 4 : 2,
10267
- details: `Decorators used: ${usedDecorators.join(", ") || "none"}; CSS vars: ${usesCssVars ? "yes" : "no"}`,
10268
- suggestions: [
10269
- ...!usesCssVars ? ["Prefer CSS variable references over hardcoded visual values."] : [],
10270
- ...decoratorNames.length > 0 && usedDecorators.length === 0 ? ["Use theme decorators from treatments.css when they fit the component intent."] : []
10271
- ]
10272
- });
10273
- if (focusAreas.includes("theme-consistency") && !usesCssVars && usedDecorators.length === 0) {
10274
- findings.push(
10275
- makeFinding({
10276
- id: "theme-consistency-weak",
10277
- category: "Theme Consistency",
10278
- severity: resolveSeverityFromChecks(reviewPack, "warn", [
10279
- "theme-consistency",
10280
- "mutation-theme-contract"
10281
- ]),
10282
- message: "The file does not appear to use theme decorators or CSS variables from the compiled contract.",
10283
- evidence: [
10284
- filePath,
10285
- `Decorators available: ${decoratorNames.slice(0, 5).join(", ") || "none"}`
10286
- ],
10287
- file: filePath,
10288
- suggestedFix: "Anchor styling to tokens.css and treatments.css instead of local hardcoded values."
10289
- })
10290
- );
10393
+ if (isContractOnly) {
10394
+ scores.push({
10395
+ category: "Theme Consistency",
10396
+ focusArea: "theme-consistency",
10397
+ score: usesCssVars ? 4 : 3,
10398
+ details: `Contract-only mode: Decantr decorators are not required; CSS vars: ${usesCssVars ? "yes" : "no"}.`,
10399
+ suggestions: [
10400
+ "Use the app design system, Tailwind theme, Sass variables, component variants, or accepted local rules as the styling authority."
10401
+ ]
10402
+ });
10403
+ } else {
10404
+ scores.push({
10405
+ category: "Theme Consistency",
10406
+ focusArea: "theme-consistency",
10407
+ score: decoratorNames.length > 0 ? scoreRatio((usedDecorators.length > 0 ? 1 : 0) + (usesCssVars ? 1 : 0), 2) : usesCssVars ? 4 : 2,
10408
+ details: `Decorators used: ${usedDecorators.join(", ") || "none"}; CSS vars: ${usesCssVars ? "yes" : "no"}`,
10409
+ suggestions: [
10410
+ ...!usesCssVars ? ["Prefer CSS variable references over hardcoded visual values."] : [],
10411
+ ...decoratorNames.length > 0 && usedDecorators.length === 0 ? ["Use theme decorators from treatments.css when they fit the component intent."] : []
10412
+ ]
10413
+ });
10414
+ if (focusAreas.includes("theme-consistency") && !usesCssVars && usedDecorators.length === 0) {
10415
+ findings.push(
10416
+ makeFinding({
10417
+ id: "theme-consistency-weak",
10418
+ category: "Theme Consistency",
10419
+ severity: resolveSeverityFromChecks(reviewPack, "warn", [
10420
+ "theme-consistency",
10421
+ "mutation-theme-contract"
10422
+ ]),
10423
+ message: "The file does not appear to use theme decorators or CSS variables from the compiled contract.",
10424
+ evidence: [
10425
+ filePath,
10426
+ `Decorators available: ${decoratorNames.slice(0, 5).join(", ") || "none"}`
10427
+ ],
10428
+ file: filePath,
10429
+ suggestedFix: "Anchor styling to tokens.css and treatments.css instead of local hardcoded values."
10430
+ })
10431
+ );
10432
+ }
10291
10433
  }
10292
10434
  const hasAria = codeLower.includes("aria-") || codeLower.includes("role=");
10293
10435
  const hasFocus = codeLower.includes("focus-visible") || codeLower.includes("focusvisible");
@@ -11217,7 +11359,9 @@ function critiqueSource({
11217
11359
  ),
11218
11360
  details: reviewPack ? `Compiled review contract covers ${knownRoutes} routes. Placeholder navigation targets: ${placeholderNavigationTargets}.` : packManifest ? `Pack manifest is available for ${knownRoutes} pages, but the review pack is missing. Placeholder navigation targets: ${placeholderNavigationTargets}.` : `No compiled route context was available during critique. Placeholder navigation targets: ${placeholderNavigationTargets}.`,
11219
11361
  suggestions: [
11220
- ...reviewPack ? [] : ["Run `decantr refresh` so critique starts from a compiled review contract."],
11362
+ ...reviewPack ? [] : [
11363
+ "Run `decantr registry compile-packs decantr.essence.json --write-context` so critique starts from a compiled review contract."
11364
+ ],
11221
11365
  ...placeholderNavigationTargets > 0 ? [
11222
11366
  "Replace placeholder href/to targets with real route destinations from the compiled contract."
11223
11367
  ] : []
@@ -11273,7 +11417,7 @@ function critiqueSource({
11273
11417
  `Disallowed inline style attributes: ${astSignals.inlineStyleAttributeCount}`
11274
11418
  ],
11275
11419
  file: filePath,
11276
- 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."
11420
+ suggestedFix: isContractOnly ? "Replace static inline visual values with the project design system, accepted component variants, or local style rules. Keep inline style only for truly dynamic geometry." : "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."
11277
11421
  })
11278
11422
  );
11279
11423
  }
@@ -11290,7 +11434,7 @@ function critiqueSource({
11290
11434
  message: "Component-scoped style tags or dynamic style elements were detected in the reviewed file.",
11291
11435
  evidence: [filePath, `Component style tag signals: ${componentStyleTagSignals}`],
11292
11436
  file: filePath,
11293
- suggestedFix: "Move shared keyframes, media queries, and visual rules into global.css or treatments.css so the file stays aligned with the Decantr layer contract."
11437
+ suggestedFix: isContractOnly ? "Move shared keyframes, media queries, and visual rules into the project stylesheet, component library, or accepted local pattern/rule manifest." : "Move shared keyframes, media queries, and visual rules into global.css or treatments.css so the file stays aligned with the Decantr layer contract."
11294
11438
  })
11295
11439
  );
11296
11440
  }
@@ -11943,12 +12087,14 @@ async function critiqueFile(filePath, projectRoot) {
11943
12087
  const treatmentsCss = readTextIfExists(join2(projectRoot, "src", "styles", "treatments.css"));
11944
12088
  const reviewPack = loadReviewPack(projectRoot);
11945
12089
  const packManifest = loadPackManifest(projectRoot);
12090
+ const adoptionMode = readProjectAdoptionMode(projectRoot);
11946
12091
  return critiqueSource({
11947
12092
  filePath: resolvedPath,
11948
12093
  code,
11949
12094
  reviewPack,
11950
12095
  packManifest,
11951
- treatmentsCss
12096
+ treatmentsCss,
12097
+ adoptionMode
11952
12098
  });
11953
12099
  }
11954
12100
  export {
@@ -11957,6 +12103,7 @@ export {
11957
12103
  VERIFICATION_SCHEMA_URLS,
11958
12104
  auditBuiltDist,
11959
12105
  auditProject,
12106
+ collectMissingPackManifestFiles,
11960
12107
  createContractAssertions,
11961
12108
  createEvidenceBundle,
11962
12109
  critiqueFile,