@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/README.md +3 -0
- package/dist/index.d.ts +11 -2
- package/dist/index.js +246 -99
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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: "
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4784
|
-
|
|
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
|
-
|
|
4805
|
-
|
|
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
|
-
|
|
10232
|
-
(
|
|
10233
|
-
|
|
10234
|
-
|
|
10235
|
-
|
|
10236
|
-
|
|
10237
|
-
|
|
10238
|
-
|
|
10239
|
-
|
|
10240
|
-
|
|
10241
|
-
|
|
10242
|
-
|
|
10243
|
-
|
|
10244
|
-
|
|
10245
|
-
|
|
10246
|
-
|
|
10247
|
-
|
|
10248
|
-
|
|
10249
|
-
|
|
10250
|
-
|
|
10251
|
-
|
|
10252
|
-
|
|
10253
|
-
|
|
10254
|
-
|
|
10255
|
-
|
|
10256
|
-
|
|
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
|
-
|
|
10264
|
-
|
|
10265
|
-
|
|
10266
|
-
|
|
10267
|
-
|
|
10268
|
-
|
|
10269
|
-
|
|
10270
|
-
|
|
10271
|
-
|
|
10272
|
-
|
|
10273
|
-
|
|
10274
|
-
|
|
10275
|
-
|
|
10276
|
-
|
|
10277
|
-
|
|
10278
|
-
|
|
10279
|
-
|
|
10280
|
-
|
|
10281
|
-
]
|
|
10282
|
-
|
|
10283
|
-
|
|
10284
|
-
|
|
10285
|
-
|
|
10286
|
-
|
|
10287
|
-
|
|
10288
|
-
|
|
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 ? [] : [
|
|
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,
|