@decantr/verifier 2.3.0 → 2.3.1

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
  }
@@ -2556,23 +2626,24 @@ function appendRuntimeAuditFindings(findings, runtimeAudit, projectRoot, topolog
2556
2626
  );
2557
2627
  }
2558
2628
  }
2559
- function appendSourceAuditFindings(findings, sourceAudit, essence, reviewPack) {
2629
+ function appendSourceAuditFindings(findings, sourceAudit, essence, reviewPack, adoptionMode) {
2560
2630
  if (sourceAudit.filesChecked === 0) {
2561
2631
  return;
2562
2632
  }
2633
+ const isContractOnly = adoptionMode === "contract-only";
2563
2634
  if (sourceAudit.inlineStyles.count > 0) {
2564
2635
  findings.push(
2565
2636
  makeFinding({
2566
2637
  id: "source-inline-styles-present",
2567
2638
  category: "Source Audit",
2568
2639
  severity: "warn",
2569
- message: "Source files still contain disallowed inline style attributes, which undermines the compiled treatment contract.",
2640
+ 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
2641
  evidence: buildSourceAuditEvidence(
2571
2642
  sourceAudit,
2572
2643
  sourceAudit.inlineStyles,
2573
2644
  "Disallowed inline style attributes"
2574
2645
  ),
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."
2646
+ 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
2647
  })
2577
2648
  );
2578
2649
  }
@@ -2582,17 +2653,17 @@ function appendSourceAuditFindings(findings, sourceAudit, essence, reviewPack) {
2582
2653
  id: "source-component-style-tags-present",
2583
2654
  category: "Source Audit",
2584
2655
  severity: "warn",
2585
- message: "Source files inject component-scoped style tags or dynamic style elements, which bypass the compiled Decantr layer contract.",
2656
+ 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
2657
  evidence: buildSourceAuditEvidence(
2587
2658
  sourceAudit,
2588
2659
  sourceAudit.componentStyleTags,
2589
2660
  "Component-level style tag signals"
2590
2661
  ),
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."
2662
+ 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
2663
  })
2593
2664
  );
2594
2665
  }
2595
- if (sourceAudit.localCssRuntimeSignals.count > 0) {
2666
+ if (!isContractOnly && sourceAudit.localCssRuntimeSignals.count > 0) {
2596
2667
  findings.push(
2597
2668
  makeFinding({
2598
2669
  id: "source-local-css-runtime-stub-present",
@@ -3695,6 +3766,7 @@ async function auditProject(projectRoot) {
3695
3766
  const findings = [];
3696
3767
  const reviewPack = loadReviewPack(projectRoot);
3697
3768
  const packManifest = loadPackManifest(projectRoot);
3769
+ const adoptionMode = readProjectAdoptionMode(projectRoot);
3698
3770
  const runtimeAudit = emptyRuntimeAudit();
3699
3771
  if (!existsSync2(essencePath)) {
3700
3772
  findings.push(
@@ -3775,10 +3847,25 @@ async function auditProject(projectRoot) {
3775
3847
  severity: "warn",
3776
3848
  message: "Compiled execution pack manifest is missing.",
3777
3849
  evidence: [join2(projectRoot, ".decantr", "context", "pack-manifest.json")],
3778
- suggestedFix: "Run `decantr refresh` to regenerate scaffold, review, mutation, section, and page packs."
3850
+ suggestedFix: "Run `decantr registry compile-packs decantr.essence.json --write-context` to hydrate scaffold, review, mutation, section, and page packs."
3779
3851
  })
3780
3852
  );
3781
3853
  } else {
3854
+ const missingPackFiles = collectMissingPackManifestFiles(projectRoot, packManifest);
3855
+ if (missingPackFiles.length > 0) {
3856
+ findings.push(
3857
+ makeFinding({
3858
+ id: "pack-manifest-referenced-files-missing",
3859
+ category: "Execution Packs",
3860
+ severity: "warn",
3861
+ message: "The compiled execution pack manifest references files that are missing.",
3862
+ evidence: missingPackFiles.slice(0, 12).map(
3863
+ (missing) => `${missing.kind}:${missing.entryId}:${missing.field} -> ${missing.relativePath}`
3864
+ ),
3865
+ suggestedFix: "Regenerate or hydrate the full execution pack bundle so every file named by pack-manifest.json exists beside it."
3866
+ })
3867
+ );
3868
+ }
3782
3869
  if (!packManifest.scaffold) {
3783
3870
  findings.push(
3784
3871
  makeFinding({
@@ -3811,7 +3898,7 @@ async function auditProject(projectRoot) {
3811
3898
  severity: "info",
3812
3899
  message: "No mutation packs were found in the manifest.",
3813
3900
  evidence: ["pack-manifest.json"],
3814
- suggestedFix: "Run `decantr refresh` to regenerate mutation task packs."
3901
+ suggestedFix: "Run `decantr registry compile-packs decantr.essence.json --write-context` to hydrate mutation task packs."
3815
3902
  })
3816
3903
  );
3817
3904
  }
@@ -3824,7 +3911,7 @@ async function auditProject(projectRoot) {
3824
3911
  severity: "warn",
3825
3912
  message: "The compiled review pack file is missing.",
3826
3913
  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."
3914
+ 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
3915
  })
3829
3916
  );
3830
3917
  }
@@ -3838,7 +3925,7 @@ async function auditProject(projectRoot) {
3838
3925
  summarizeTopology(essence, reviewPack),
3839
3926
  sourceAudit
3840
3927
  );
3841
- appendSourceAuditFindings(findings, sourceAudit, essence, reviewPack);
3928
+ appendSourceAuditFindings(findings, sourceAudit, essence, reviewPack, adoptionMode);
3842
3929
  appendStyleContractFindings(findings, styleAudit, essence);
3843
3930
  const summary = {
3844
3931
  errorCount: findings.filter((finding) => finding.severity === "error").length,
@@ -4555,14 +4642,17 @@ function getObjectLiteralPropertyExpression(objectLiteral, propertyName, namedEx
4555
4642
  }
4556
4643
  return null;
4557
4644
  }
4558
- function resolveObjectLiteralExpressionAtPropertyPath(expression, sourceFile, propertyPath, namedExpressions, namedPropertyAliases, seenIdentifiers, seenFunctions) {
4645
+ var MAX_OPEN_REDIRECT_RESOLUTION_DEPTH = 40;
4646
+ function resolveObjectLiteralExpressionAtPropertyPath(expression, sourceFile, propertyPath, namedExpressions, namedPropertyAliases, seenIdentifiers, seenFunctions, depth = 0) {
4647
+ if (depth > MAX_OPEN_REDIRECT_RESOLUTION_DEPTH) return null;
4559
4648
  let resolvedObjectLiteral = resolveObjectLiteralExpression(
4560
4649
  expression,
4561
4650
  sourceFile,
4562
4651
  namedExpressions,
4563
4652
  namedPropertyAliases,
4564
4653
  seenIdentifiers,
4565
- seenFunctions
4654
+ seenFunctions,
4655
+ depth + 1
4566
4656
  );
4567
4657
  if (!resolvedObjectLiteral) return null;
4568
4658
  for (const propertyName of propertyPath) {
@@ -4578,13 +4668,15 @@ function resolveObjectLiteralExpressionAtPropertyPath(expression, sourceFile, pr
4578
4668
  resolvedObjectLiteral.namedExpressions,
4579
4669
  namedPropertyAliases,
4580
4670
  seenIdentifiers,
4581
- seenFunctions
4671
+ seenFunctions,
4672
+ depth + 1
4582
4673
  );
4583
4674
  if (!resolvedObjectLiteral) return null;
4584
4675
  }
4585
4676
  return resolvedObjectLiteral;
4586
4677
  }
4587
- function resolveReturnedObjectLiteralExpression(functionLike, args, sourceFile, namedExpressions, namedPropertyAliases, seenIdentifiers, seenFunctions) {
4678
+ function resolveReturnedObjectLiteralExpression(functionLike, args, sourceFile, namedExpressions, namedPropertyAliases, seenIdentifiers, seenFunctions, depth = 0) {
4679
+ if (depth > MAX_OPEN_REDIRECT_RESOLUTION_DEPTH) return null;
4588
4680
  const functionKey = getFunctionLikeCacheKey(functionLike);
4589
4681
  if (seenFunctions.has(functionKey)) return null;
4590
4682
  seenFunctions.add(functionKey);
@@ -4596,7 +4688,8 @@ function resolveReturnedObjectLiteralExpression(functionLike, args, sourceFile,
4596
4688
  boundExpressions,
4597
4689
  namedPropertyAliases,
4598
4690
  new Set(seenIdentifiers),
4599
- seenFunctions
4691
+ seenFunctions,
4692
+ depth + 1
4600
4693
  );
4601
4694
  if (objectLiteral) {
4602
4695
  seenFunctions.delete(functionKey);
@@ -4606,8 +4699,9 @@ function resolveReturnedObjectLiteralExpression(functionLike, args, sourceFile,
4606
4699
  seenFunctions.delete(functionKey);
4607
4700
  return null;
4608
4701
  }
4609
- function resolveObjectLiteralExpression(expression, sourceFile, namedExpressions, namedPropertyAliases, seenIdentifiers, seenFunctions = /* @__PURE__ */ new Set()) {
4702
+ function resolveObjectLiteralExpression(expression, sourceFile, namedExpressions, namedPropertyAliases, seenIdentifiers, seenFunctions = /* @__PURE__ */ new Set(), depth = 0) {
4610
4703
  if (!expression) return null;
4704
+ if (depth > MAX_OPEN_REDIRECT_RESOLUTION_DEPTH) return null;
4611
4705
  if (ts.isParenthesizedExpression(expression) || ts.isAsExpression(expression) || ts.isTypeAssertionExpression(expression) || ts.isNonNullExpression(expression)) {
4612
4706
  return resolveObjectLiteralExpression(
4613
4707
  expression.expression,
@@ -4615,7 +4709,8 @@ function resolveObjectLiteralExpression(expression, sourceFile, namedExpressions
4615
4709
  namedExpressions,
4616
4710
  namedPropertyAliases,
4617
4711
  seenIdentifiers,
4618
- seenFunctions
4712
+ seenFunctions,
4713
+ depth + 1
4619
4714
  );
4620
4715
  }
4621
4716
  if (ts.isObjectLiteralExpression(expression)) {
@@ -4632,7 +4727,8 @@ function resolveObjectLiteralExpression(expression, sourceFile, namedExpressions
4632
4727
  namedExpressions,
4633
4728
  namedPropertyAliases,
4634
4729
  seenIdentifiers,
4635
- seenFunctions
4730
+ seenFunctions,
4731
+ depth + 1
4636
4732
  );
4637
4733
  seenIdentifiers.delete(expression.text);
4638
4734
  return result2;
@@ -4647,7 +4743,8 @@ function resolveObjectLiteralExpression(expression, sourceFile, namedExpressions
4647
4743
  namedExpressions,
4648
4744
  namedPropertyAliases,
4649
4745
  seenIdentifiers,
4650
- seenFunctions
4746
+ seenFunctions,
4747
+ depth + 1
4651
4748
  );
4652
4749
  seenIdentifiers.delete(expression.text);
4653
4750
  return result;
@@ -4658,7 +4755,9 @@ function resolveObjectLiteralExpression(expression, sourceFile, namedExpressions
4658
4755
  sourceFile,
4659
4756
  namedExpressions,
4660
4757
  namedPropertyAliases,
4661
- /* @__PURE__ */ new Set()
4758
+ new Set(seenIdentifiers),
4759
+ seenFunctions,
4760
+ depth + 1
4662
4761
  );
4663
4762
  if (!functionResolution) return null;
4664
4763
  return resolveReturnedObjectLiteralExpression(
@@ -4668,7 +4767,8 @@ function resolveObjectLiteralExpression(expression, sourceFile, namedExpressions
4668
4767
  functionResolution.namedExpressions,
4669
4768
  namedPropertyAliases,
4670
4769
  seenIdentifiers,
4671
- seenFunctions
4770
+ seenFunctions,
4771
+ depth + 1
4672
4772
  );
4673
4773
  }
4674
4774
  if (isMemberAccessExpression(expression)) {
@@ -4681,7 +4781,8 @@ function resolveObjectLiteralExpression(expression, sourceFile, namedExpressions
4681
4781
  namedExpressions,
4682
4782
  namedPropertyAliases,
4683
4783
  seenIdentifiers,
4684
- seenFunctions
4784
+ seenFunctions,
4785
+ depth + 1
4685
4786
  );
4686
4787
  }
4687
4788
  return null;
@@ -4700,7 +4801,8 @@ function findFunctionLikeOnObjectLiteral(objectLiteral, propertyName, namedFunct
4700
4801
  }
4701
4802
  return null;
4702
4803
  }
4703
- function resolveReturnedTrackedFunctionLike(functionLike, args, sourceFile, namedExpressions, namedPropertyAliases, seenIdentifiers, seenFunctions) {
4804
+ function resolveReturnedTrackedFunctionLike(functionLike, args, sourceFile, namedExpressions, namedPropertyAliases, seenIdentifiers, seenFunctions, depth = 0) {
4805
+ if (depth > MAX_OPEN_REDIRECT_RESOLUTION_DEPTH) return null;
4704
4806
  const functionKey = getFunctionLikeCacheKey(functionLike);
4705
4807
  if (seenFunctions.has(functionKey)) return null;
4706
4808
  const nextSeenFunctions = new Set(seenFunctions);
@@ -4713,14 +4815,16 @@ function resolveReturnedTrackedFunctionLike(functionLike, args, sourceFile, name
4713
4815
  boundExpressions,
4714
4816
  namedPropertyAliases,
4715
4817
  new Set(seenIdentifiers),
4716
- nextSeenFunctions
4818
+ nextSeenFunctions,
4819
+ depth + 1
4717
4820
  );
4718
4821
  if (result) return result;
4719
4822
  }
4720
4823
  return null;
4721
4824
  }
4722
- function resolveTrackedOpenRedirectFunctionLike(expression, sourceFile, namedExpressions, namedPropertyAliases, seenIdentifiers, seenFunctions = /* @__PURE__ */ new Set()) {
4825
+ function resolveTrackedOpenRedirectFunctionLike(expression, sourceFile, namedExpressions, namedPropertyAliases, seenIdentifiers, seenFunctions = /* @__PURE__ */ new Set(), depth = 0) {
4723
4826
  if (!expression) return null;
4827
+ if (depth > MAX_OPEN_REDIRECT_RESOLUTION_DEPTH) return null;
4724
4828
  const namedFunctions = getCachedNamedFunctionLikeDeclarations(sourceFile);
4725
4829
  const directFunctionLike = resolveFunctionLikeHandler(expression, namedFunctions);
4726
4830
  if (directFunctionLike) {
@@ -4733,7 +4837,8 @@ function resolveTrackedOpenRedirectFunctionLike(expression, sourceFile, namedExp
4733
4837
  namedExpressions,
4734
4838
  namedPropertyAliases,
4735
4839
  seenIdentifiers,
4736
- seenFunctions
4840
+ seenFunctions,
4841
+ depth + 1
4737
4842
  );
4738
4843
  }
4739
4844
  if (isCallLikeExpression(expression)) {
@@ -4742,8 +4847,9 @@ function resolveTrackedOpenRedirectFunctionLike(expression, sourceFile, namedExp
4742
4847
  sourceFile,
4743
4848
  namedExpressions,
4744
4849
  namedPropertyAliases,
4745
- /* @__PURE__ */ new Set(),
4746
- seenFunctions
4850
+ new Set(seenIdentifiers),
4851
+ seenFunctions,
4852
+ depth + 1
4747
4853
  );
4748
4854
  if (!functionResolution) return null;
4749
4855
  return resolveReturnedTrackedFunctionLike(
@@ -4753,7 +4859,8 @@ function resolveTrackedOpenRedirectFunctionLike(expression, sourceFile, namedExp
4753
4859
  functionResolution.namedExpressions,
4754
4860
  namedPropertyAliases,
4755
4861
  seenIdentifiers,
4756
- seenFunctions
4862
+ seenFunctions,
4863
+ depth + 1
4757
4864
  );
4758
4865
  }
4759
4866
  if (ts.isIdentifier(expression)) {
@@ -4767,7 +4874,8 @@ function resolveTrackedOpenRedirectFunctionLike(expression, sourceFile, namedExp
4767
4874
  namedExpressions,
4768
4875
  namedPropertyAliases,
4769
4876
  seenIdentifiers,
4770
- seenFunctions
4877
+ seenFunctions,
4878
+ depth + 1
4771
4879
  );
4772
4880
  seenIdentifiers.delete(expression.text);
4773
4881
  if (result) return result;
@@ -4780,8 +4888,9 @@ function resolveTrackedOpenRedirectFunctionLike(expression, sourceFile, namedExp
4780
4888
  propertyAlias.propertyPath.slice(0, -1),
4781
4889
  namedExpressions,
4782
4890
  namedPropertyAliases,
4783
- /* @__PURE__ */ new Set(),
4784
- /* @__PURE__ */ new Set()
4891
+ new Set(seenIdentifiers),
4892
+ seenFunctions,
4893
+ depth + 1
4785
4894
  );
4786
4895
  if (!resolvedObjectLiteral2) return null;
4787
4896
  const functionLike2 = findFunctionLikeOnObjectLiteral(
@@ -4801,8 +4910,9 @@ function resolveTrackedOpenRedirectFunctionLike(expression, sourceFile, namedExp
4801
4910
  sourceFile,
4802
4911
  namedExpressions,
4803
4912
  namedPropertyAliases,
4804
- /* @__PURE__ */ new Set(),
4805
- /* @__PURE__ */ new Set()
4913
+ new Set(seenIdentifiers),
4914
+ seenFunctions,
4915
+ depth + 1
4806
4916
  );
4807
4917
  if (!resolvedObjectLiteral) return null;
4808
4918
  const functionLike = findFunctionLikeOnObjectLiteral(
@@ -10219,7 +10329,8 @@ function critiqueSource({
10219
10329
  code,
10220
10330
  reviewPack = null,
10221
10331
  packManifest = null,
10222
- treatmentsCss = ""
10332
+ treatmentsCss = "",
10333
+ adoptionMode = null
10223
10334
  }) {
10224
10335
  const codeLower = code.toLowerCase();
10225
10336
  const astSignals = analyzeAstSignals(filePath, code);
@@ -10227,67 +10338,92 @@ function critiqueSource({
10227
10338
  const findings = [];
10228
10339
  const scores = [];
10229
10340
  const antiPatternIds = new Set(reviewPack?.antiPatterns.map((entry) => entry.id) ?? []);
10341
+ const isContractOnly = adoptionMode === "contract-only";
10230
10342
  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
- );
10343
+ if (isContractOnly) {
10344
+ scores.push({
10345
+ category: "Styling Authority",
10346
+ focusArea: "treatment-usage",
10347
+ score: 3,
10348
+ details: "Contract-only adoption does not require Decantr d-* treatment classes in source files.",
10349
+ suggestions: [
10350
+ "Codify project-owned component variants and local rules when button/card/surface drift needs mechanical enforcement."
10351
+ ]
10352
+ });
10353
+ } else {
10354
+ const treatmentSuggestions = TREATMENT_CLASSES.filter((token) => !code.includes(token)).map(
10355
+ (token) => `Consider using \`${token}\` where appropriate.`
10356
+ );
10357
+ scores.push({
10358
+ category: "Treatment Usage",
10359
+ focusArea: "treatment-usage",
10360
+ score: scoreRatio(usedTreatments.length, TREATMENT_CLASSES.length),
10361
+ details: `${usedTreatments.length}/${TREATMENT_CLASSES.length} base treatments used: ${usedTreatments.join(", ") || "none"}`,
10362
+ suggestions: treatmentSuggestions
10363
+ });
10364
+ if (focusAreas.includes("treatment-usage") && usedTreatments.length === 0) {
10365
+ findings.push(
10366
+ makeFinding({
10367
+ id: "treatment-usage-missing",
10368
+ category: "Treatment Usage",
10369
+ severity: resolveSeverityFromChecks(reviewPack, "warn", [
10370
+ "page-pattern-contract",
10371
+ "section-pattern-coverage"
10372
+ ]),
10373
+ message: "No Decantr treatment classes were detected in the reviewed file.",
10374
+ evidence: [
10375
+ filePath,
10376
+ "Expected tokens include d-interactive, d-surface, d-data, d-control, d-section, d-annotation, d-label."
10377
+ ],
10378
+ file: filePath,
10379
+ suggestedFix: "Apply the compiled treatment vocabulary instead of hand-rolled utility styling."
10380
+ })
10381
+ );
10382
+ }
10259
10383
  }
10260
10384
  const decoratorNames = buildDecoratorInventory(treatmentsCss);
10261
10385
  const usedDecorators = decoratorNames.filter((name) => code.includes(name));
10262
10386
  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
- );
10387
+ if (isContractOnly) {
10388
+ scores.push({
10389
+ category: "Theme Consistency",
10390
+ focusArea: "theme-consistency",
10391
+ score: usesCssVars ? 4 : 3,
10392
+ details: `Contract-only mode: Decantr decorators are not required; CSS vars: ${usesCssVars ? "yes" : "no"}.`,
10393
+ suggestions: [
10394
+ "Use the app design system, Tailwind theme, Sass variables, component variants, or accepted local rules as the styling authority."
10395
+ ]
10396
+ });
10397
+ } else {
10398
+ scores.push({
10399
+ category: "Theme Consistency",
10400
+ focusArea: "theme-consistency",
10401
+ score: decoratorNames.length > 0 ? scoreRatio((usedDecorators.length > 0 ? 1 : 0) + (usesCssVars ? 1 : 0), 2) : usesCssVars ? 4 : 2,
10402
+ details: `Decorators used: ${usedDecorators.join(", ") || "none"}; CSS vars: ${usesCssVars ? "yes" : "no"}`,
10403
+ suggestions: [
10404
+ ...!usesCssVars ? ["Prefer CSS variable references over hardcoded visual values."] : [],
10405
+ ...decoratorNames.length > 0 && usedDecorators.length === 0 ? ["Use theme decorators from treatments.css when they fit the component intent."] : []
10406
+ ]
10407
+ });
10408
+ if (focusAreas.includes("theme-consistency") && !usesCssVars && usedDecorators.length === 0) {
10409
+ findings.push(
10410
+ makeFinding({
10411
+ id: "theme-consistency-weak",
10412
+ category: "Theme Consistency",
10413
+ severity: resolveSeverityFromChecks(reviewPack, "warn", [
10414
+ "theme-consistency",
10415
+ "mutation-theme-contract"
10416
+ ]),
10417
+ message: "The file does not appear to use theme decorators or CSS variables from the compiled contract.",
10418
+ evidence: [
10419
+ filePath,
10420
+ `Decorators available: ${decoratorNames.slice(0, 5).join(", ") || "none"}`
10421
+ ],
10422
+ file: filePath,
10423
+ suggestedFix: "Anchor styling to tokens.css and treatments.css instead of local hardcoded values."
10424
+ })
10425
+ );
10426
+ }
10291
10427
  }
10292
10428
  const hasAria = codeLower.includes("aria-") || codeLower.includes("role=");
10293
10429
  const hasFocus = codeLower.includes("focus-visible") || codeLower.includes("focusvisible");
@@ -11217,7 +11353,9 @@ function critiqueSource({
11217
11353
  ),
11218
11354
  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
11355
  suggestions: [
11220
- ...reviewPack ? [] : ["Run `decantr refresh` so critique starts from a compiled review contract."],
11356
+ ...reviewPack ? [] : [
11357
+ "Run `decantr registry compile-packs decantr.essence.json --write-context` so critique starts from a compiled review contract."
11358
+ ],
11221
11359
  ...placeholderNavigationTargets > 0 ? [
11222
11360
  "Replace placeholder href/to targets with real route destinations from the compiled contract."
11223
11361
  ] : []
@@ -11273,7 +11411,7 @@ function critiqueSource({
11273
11411
  `Disallowed inline style attributes: ${astSignals.inlineStyleAttributeCount}`
11274
11412
  ],
11275
11413
  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."
11414
+ 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
11415
  })
11278
11416
  );
11279
11417
  }
@@ -11290,7 +11428,7 @@ function critiqueSource({
11290
11428
  message: "Component-scoped style tags or dynamic style elements were detected in the reviewed file.",
11291
11429
  evidence: [filePath, `Component style tag signals: ${componentStyleTagSignals}`],
11292
11430
  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."
11431
+ 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
11432
  })
11295
11433
  );
11296
11434
  }
@@ -11943,12 +12081,14 @@ async function critiqueFile(filePath, projectRoot) {
11943
12081
  const treatmentsCss = readTextIfExists(join2(projectRoot, "src", "styles", "treatments.css"));
11944
12082
  const reviewPack = loadReviewPack(projectRoot);
11945
12083
  const packManifest = loadPackManifest(projectRoot);
12084
+ const adoptionMode = readProjectAdoptionMode(projectRoot);
11946
12085
  return critiqueSource({
11947
12086
  filePath: resolvedPath,
11948
12087
  code,
11949
12088
  reviewPack,
11950
12089
  packManifest,
11951
- treatmentsCss
12090
+ treatmentsCss,
12091
+ adoptionMode
11952
12092
  });
11953
12093
  }
11954
12094
  export {
@@ -11957,6 +12097,7 @@ export {
11957
12097
  VERIFICATION_SCHEMA_URLS,
11958
12098
  auditBuiltDist,
11959
12099
  auditProject,
12100
+ collectMissingPackManifestFiles,
11960
12101
  createContractAssertions,
11961
12102
  createEvidenceBundle,
11962
12103
  critiqueFile,