@decantr/verifier 3.4.0 → 3.5.0
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 +13 -7
- package/dist/index.d.ts +262 -50
- package/dist/index.js +429 -28
- package/dist/index.js.map +1 -1
- package/package.json +9 -2
- package/schema/authority-resolution.v2.json +116 -0
- package/schema/decantr-ci-report.v2.json +55 -0
- package/schema/evidence-bundle.v2.json +35 -4
- package/schema/loop-readiness.v2.json +87 -0
- package/schema/project-health-report.v2.json +119 -0
- package/schema/proof-field-report.v2.json +51 -0
- package/schema/verification-report.common.v2.json +91 -0
- package/schema/workspace-health-report.v2.json +51 -0
package/dist/index.js
CHANGED
|
@@ -344,6 +344,300 @@ function auditComponentReuse(projectRoot, sourceFiles) {
|
|
|
344
344
|
};
|
|
345
345
|
}
|
|
346
346
|
|
|
347
|
+
// src/contracts-v2.ts
|
|
348
|
+
var VERIFICATION_COMMON_V2_SCHEMA_URL = "https://decantr.ai/schemas/verification-report.common.v2.json";
|
|
349
|
+
var PROJECT_HEALTH_REPORT_V2_SCHEMA_URL = "https://decantr.ai/schemas/project-health-report.v2.json";
|
|
350
|
+
var DECANTR_CI_REPORT_V2_SCHEMA_URL = "https://decantr.ai/schemas/decantr-ci-report.v2.json";
|
|
351
|
+
var WORKSPACE_HEALTH_REPORT_V2_SCHEMA_URL = "https://decantr.ai/schemas/workspace-health-report.v2.json";
|
|
352
|
+
var EVIDENCE_BUNDLE_V2_SCHEMA_URL = "https://decantr.ai/schemas/evidence-bundle.v2.json";
|
|
353
|
+
var RUNTIME_PROBE_PAYLOAD_V2_SCHEMA_URL = "https://decantr.ai/schemas/runtime-probe-payload.v2.json";
|
|
354
|
+
var LOOP_READINESS_V2_SCHEMA_URL = "https://decantr.ai/schemas/loop-readiness.v2.json";
|
|
355
|
+
var AUTHORITY_RESOLUTION_V2_SCHEMA_URL = "https://decantr.ai/schemas/authority-resolution.v2.json";
|
|
356
|
+
var PROOF_FIELD_REPORT_V2_SCHEMA_URL = "https://decantr.ai/schemas/proof-field-report.v2.json";
|
|
357
|
+
function unique(values) {
|
|
358
|
+
return [...new Set(values)];
|
|
359
|
+
}
|
|
360
|
+
function rounded(value) {
|
|
361
|
+
return Number(Math.max(0, Math.min(1, value)).toFixed(2));
|
|
362
|
+
}
|
|
363
|
+
function createEvidenceTier(report, options = {}) {
|
|
364
|
+
const runtimeProbeCount = options.runtimeProbeCount ?? (report.summary.runtimeAuditChecked ? 1 : 0);
|
|
365
|
+
const visualArtifactCount = options.visualArtifactCount ?? 0;
|
|
366
|
+
const findingsAnchored = report.findings.filter((finding) => finding.graph).length;
|
|
367
|
+
const findingsWithRepairPlan = report.findings.filter((finding) => finding.repairPlan).length;
|
|
368
|
+
const capabilities = ["static-audit", "project-health"];
|
|
369
|
+
if (report.graph.ready) capabilities.push("typed-graph");
|
|
370
|
+
if (runtimeProbeCount > 0) capabilities.push("runtime-probe");
|
|
371
|
+
if (report.summary.runtimeAuditChecked || visualArtifactCount > 0)
|
|
372
|
+
capabilities.push("browser-evidence");
|
|
373
|
+
if (visualArtifactCount > 0) capabilities.push("visual-baseline");
|
|
374
|
+
if (findingsWithRepairPlan > 0) capabilities.push("repair-plan");
|
|
375
|
+
if (options.benchmarkReplay) capabilities.push("benchmark-replay");
|
|
376
|
+
let stage = "static";
|
|
377
|
+
if (options.benchmarkReplay) stage = "proof";
|
|
378
|
+
else if (findingsWithRepairPlan > 0) stage = "repair";
|
|
379
|
+
else if (visualArtifactCount > 0) stage = "visual";
|
|
380
|
+
else if (runtimeProbeCount > 0 || report.summary.runtimeAuditChecked) stage = "runtime";
|
|
381
|
+
else if (report.graph.ready) stage = "graph";
|
|
382
|
+
const routeCoverage = report.routes.declared.length > 0 ? Math.min(1, report.routes.runtimeChecked.length / report.routes.declared.length) : report.summary.runtimeAuditChecked ? 1 : 0;
|
|
383
|
+
const graphCoverage = report.findings.length > 0 ? findingsAnchored / report.findings.length : report.graph.ready ? 1 : 0;
|
|
384
|
+
const repairCoverage = report.findings.length > 0 ? findingsWithRepairPlan / report.findings.length : 1;
|
|
385
|
+
const runtimeCoverage = runtimeProbeCount > 0 || report.summary.runtimeAuditChecked ? 1 : 0;
|
|
386
|
+
const score = rounded(
|
|
387
|
+
0.22 + (report.graph.ready ? 0.22 : 0) + routeCoverage * 0.16 + graphCoverage * 0.16 + repairCoverage * 0.14 + runtimeCoverage * 0.1
|
|
388
|
+
);
|
|
389
|
+
const level = score >= 0.78 ? "high" : score >= 0.48 ? "moderate" : "low";
|
|
390
|
+
const reasons = [];
|
|
391
|
+
reasons.push(report.graph.ready ? "typed graph is ready" : "typed graph is missing or stale");
|
|
392
|
+
reasons.push(
|
|
393
|
+
runtimeProbeCount > 0 || report.summary.runtimeAuditChecked ? "runtime evidence is present" : "runtime evidence has not run"
|
|
394
|
+
);
|
|
395
|
+
reasons.push(
|
|
396
|
+
report.findings.length === 0 ? "no open findings" : `${findingsAnchored}/${report.findings.length} finding(s) have graph anchors`
|
|
397
|
+
);
|
|
398
|
+
reasons.push(
|
|
399
|
+
report.findings.length === 0 ? "repair context is not needed" : `${findingsWithRepairPlan}/${report.findings.length} finding(s) include repair plans`
|
|
400
|
+
);
|
|
401
|
+
return {
|
|
402
|
+
schemaVersion: 2,
|
|
403
|
+
stage,
|
|
404
|
+
status: report.status === "healthy" ? "healthy" : report.status,
|
|
405
|
+
capabilities: unique(capabilities),
|
|
406
|
+
coverage: {
|
|
407
|
+
declaredRoutes: report.routes.declared.length,
|
|
408
|
+
runtimeRoutesChecked: report.routes.runtimeChecked.length,
|
|
409
|
+
findingsAnchored,
|
|
410
|
+
findingsWithRepairPlan,
|
|
411
|
+
runtimeProbeCount,
|
|
412
|
+
visualArtifactCount
|
|
413
|
+
},
|
|
414
|
+
confidence: {
|
|
415
|
+
level,
|
|
416
|
+
score,
|
|
417
|
+
reasons
|
|
418
|
+
}
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
function createAuthorityOrder() {
|
|
422
|
+
return [
|
|
423
|
+
{
|
|
424
|
+
id: "production-source",
|
|
425
|
+
label: "Production source",
|
|
426
|
+
role: "Existing runtime/source wins first in Brownfield unless a human explicitly accepts a contract change.",
|
|
427
|
+
rank: 1
|
|
428
|
+
},
|
|
429
|
+
{
|
|
430
|
+
id: "local-law",
|
|
431
|
+
label: "Accepted local law",
|
|
432
|
+
role: "Project-owned rules and codified patterns constrain edits after they are accepted locally.",
|
|
433
|
+
rank: 2
|
|
434
|
+
},
|
|
435
|
+
{
|
|
436
|
+
id: "style-bridge",
|
|
437
|
+
label: "Accepted style bridge",
|
|
438
|
+
role: "Mapped native style tokens/components govern Hybrid styling once accepted locally.",
|
|
439
|
+
rank: 3
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
id: "essence-contract",
|
|
443
|
+
label: "Essence V4 contract",
|
|
444
|
+
role: "Structural route, section, page, guard, and DNA contract for Decantr context.",
|
|
445
|
+
rank: 4
|
|
446
|
+
},
|
|
447
|
+
{
|
|
448
|
+
id: "registry-guidance",
|
|
449
|
+
label: "Hosted packs and registry",
|
|
450
|
+
role: "Advisory guidance unless mapped into accepted local law or the local style bridge.",
|
|
451
|
+
rank: 5
|
|
452
|
+
}
|
|
453
|
+
];
|
|
454
|
+
}
|
|
455
|
+
function laneForFinding(finding) {
|
|
456
|
+
if (finding.source === "style-bridge") return "style-bridge";
|
|
457
|
+
if (finding.source === "brownfield" || finding.category.toLowerCase().includes("drift")) {
|
|
458
|
+
return "production-source";
|
|
459
|
+
}
|
|
460
|
+
if (finding.source === "check" || finding.source === "assertion") return "essence-contract";
|
|
461
|
+
if (finding.source === "pack") return "registry-guidance";
|
|
462
|
+
return "local-law";
|
|
463
|
+
}
|
|
464
|
+
function actionsForFinding(finding) {
|
|
465
|
+
const commands = finding.repairPlan?.commands ?? finding.remediation?.commands ?? [];
|
|
466
|
+
const repairCommand = commands[0] ?? null;
|
|
467
|
+
const source = finding.source;
|
|
468
|
+
const actions = [];
|
|
469
|
+
if (source === "graph") {
|
|
470
|
+
actions.push({
|
|
471
|
+
kind: "regenerate_graph",
|
|
472
|
+
label: "Regenerate graph/context evidence",
|
|
473
|
+
command: "decantr graph --check || decantr graph",
|
|
474
|
+
writes: true,
|
|
475
|
+
rationale: "Graph drift should be resolved by regenerating derived graph artifacts from current source."
|
|
476
|
+
});
|
|
477
|
+
} else if (source === "style-bridge") {
|
|
478
|
+
actions.push({
|
|
479
|
+
kind: "update_style_bridge",
|
|
480
|
+
label: "Update accepted style bridge",
|
|
481
|
+
command: "decantr codify --style-bridge",
|
|
482
|
+
writes: true,
|
|
483
|
+
rationale: "Style conflicts should be accepted through the explicit Hybrid style bridge workflow."
|
|
484
|
+
});
|
|
485
|
+
} else if (source === "brownfield") {
|
|
486
|
+
actions.push({
|
|
487
|
+
kind: "accept_observed_source",
|
|
488
|
+
label: "Accept observed source into contract",
|
|
489
|
+
command: "decantr init --existing --merge-proposal",
|
|
490
|
+
writes: true,
|
|
491
|
+
rationale: "Brownfield source is first authority; accept it explicitly before treating contract drift as repaired."
|
|
492
|
+
});
|
|
493
|
+
} else if (source === "pack") {
|
|
494
|
+
actions.push({
|
|
495
|
+
kind: "regenerate_context",
|
|
496
|
+
label: "Regenerate Decantr context",
|
|
497
|
+
command: "decantr refresh",
|
|
498
|
+
writes: true,
|
|
499
|
+
rationale: "Generated context and pack artifacts are derived from the local contract."
|
|
500
|
+
});
|
|
501
|
+
} else {
|
|
502
|
+
actions.push({
|
|
503
|
+
kind: "repair_source",
|
|
504
|
+
label: "Repair source to satisfy accepted authority",
|
|
505
|
+
command: repairCommand,
|
|
506
|
+
writes: false,
|
|
507
|
+
rationale: "The finding can be repaired by editing the relevant application source under human/agent control."
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
actions.push({
|
|
511
|
+
kind: "defer_to_drift_log",
|
|
512
|
+
label: "Defer to drift log",
|
|
513
|
+
command: `decantr resolve --defer ${finding.id}`,
|
|
514
|
+
writes: true,
|
|
515
|
+
rationale: "Record an explicit human deferral when this conflict should not block the current task."
|
|
516
|
+
});
|
|
517
|
+
if (finding.severity !== "error") {
|
|
518
|
+
actions.push({
|
|
519
|
+
kind: "mark_advisory",
|
|
520
|
+
label: "Mark advisory",
|
|
521
|
+
command: `decantr resolve --mark-advisory ${finding.id}`,
|
|
522
|
+
writes: true,
|
|
523
|
+
rationale: "Warnings can be documented as advisory when the observed source is intentionally different."
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
return actions;
|
|
527
|
+
}
|
|
528
|
+
function createAuthorityResolution(report) {
|
|
529
|
+
const conflicts = report.findings.filter((finding) => finding.severity !== "info").map((finding) => ({
|
|
530
|
+
id: finding.id,
|
|
531
|
+
source: finding.source,
|
|
532
|
+
category: finding.category,
|
|
533
|
+
severity: finding.severity,
|
|
534
|
+
message: finding.message,
|
|
535
|
+
...finding.graph ? { graphAnchor: finding.graph } : {},
|
|
536
|
+
lane: laneForFinding(finding),
|
|
537
|
+
status: finding.severity === "error" ? "blocking" : finding.source === "brownfield" || finding.source === "style-bridge" ? "repairable" : "advisory",
|
|
538
|
+
recommendedActions: actionsForFinding(finding)
|
|
539
|
+
}));
|
|
540
|
+
const activeLane = report.summary.workflowMode === "brownfield-attach" ? "production-source" : "essence-contract";
|
|
541
|
+
return {
|
|
542
|
+
schemaVersion: 2,
|
|
543
|
+
order: createAuthorityOrder(),
|
|
544
|
+
activeLane,
|
|
545
|
+
summary: activeLane === "production-source" ? "Brownfield authority: preserve observed production source first, then accepted local law/style bridge, then Essence V4 structure, with hosted packs advisory." : "Contract authority: Essence V4 and accepted local project law guide source changes; hosted packs remain advisory until accepted locally.",
|
|
546
|
+
conflicts,
|
|
547
|
+
stopRule: "If runtime source and Decantr context disagree, stop and report drift instead of guessing which truth to overwrite."
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
function readTargetsForFindings(report) {
|
|
551
|
+
const targets = /* @__PURE__ */ new Set(["DECANTR.md", "decantr.essence.json"]);
|
|
552
|
+
for (const finding of report.findings) {
|
|
553
|
+
for (const target of finding.repairPlan?.readTargets ?? []) targets.add(target);
|
|
554
|
+
if (finding.file) targets.add(finding.file);
|
|
555
|
+
if (finding.source === "graph") targets.add(".decantr/graph/graph.manifest.json");
|
|
556
|
+
if (finding.source === "style-bridge") targets.add(".decantr/style-bridge.json");
|
|
557
|
+
}
|
|
558
|
+
return [...targets].sort();
|
|
559
|
+
}
|
|
560
|
+
function createLoopReadiness(report, authority = createAuthorityResolution(report), evidenceTier = createEvidenceTier(report)) {
|
|
561
|
+
const hasEssence = Boolean(report.summary.essenceVersion);
|
|
562
|
+
const graphMissing = !report.graph.ready;
|
|
563
|
+
const blockingConflicts = authority.conflicts.filter(
|
|
564
|
+
(conflict) => conflict.status === "blocking"
|
|
565
|
+
);
|
|
566
|
+
const repairableConflicts = authority.conflicts.filter(
|
|
567
|
+
(conflict) => conflict.status === "repairable"
|
|
568
|
+
);
|
|
569
|
+
let state = "verified";
|
|
570
|
+
const blockingReasons = [];
|
|
571
|
+
if (!hasEssence) {
|
|
572
|
+
state = "blocked_missing_context";
|
|
573
|
+
blockingReasons.push("Essence V4 context is missing.");
|
|
574
|
+
} else if (graphMissing) {
|
|
575
|
+
state = "blocked_missing_graph";
|
|
576
|
+
blockingReasons.push("Typed Contract graph is missing, stale, or incomplete.");
|
|
577
|
+
} else if (blockingConflicts.length > 0 && repairableConflicts.length > 0) {
|
|
578
|
+
state = "human_resolution_required";
|
|
579
|
+
blockingReasons.push("Blocking findings conflict with Brownfield or Hybrid authority.");
|
|
580
|
+
} else if (blockingConflicts.length > 0 || report.summary.errorCount > 0) {
|
|
581
|
+
state = "repair_required";
|
|
582
|
+
blockingReasons.push("Project Health has blocking errors.");
|
|
583
|
+
} else if (repairableConflicts.length > 0 || report.summary.warnCount > 0) {
|
|
584
|
+
state = "repair_required";
|
|
585
|
+
blockingReasons.push("Project Health has repairable warnings or drift.");
|
|
586
|
+
} else if (report.status === "healthy") {
|
|
587
|
+
state = "verified";
|
|
588
|
+
} else {
|
|
589
|
+
state = "verify_required";
|
|
590
|
+
blockingReasons.push("Verification needs to be rerun before the loop is complete.");
|
|
591
|
+
}
|
|
592
|
+
const nextActions = state === "verified" ? ["Continue normal route/task workflow."] : state === "blocked_missing_context" ? ["Run `decantr scan` for a read-only preview, then `decantr adopt` when ready."] : state === "blocked_missing_graph" ? ["Run `decantr graph` to regenerate typed graph artifacts, then rerun verification."] : state === "human_resolution_required" ? ["Run `decantr resolve` and choose an explicit resolution action before editing."] : state === "repair_required" ? ["Repair the highest-severity finding, then rerun the verify command."] : ["Run the verify command before treating the task as complete."];
|
|
593
|
+
const status = state.startsWith("blocked") || state === "human_resolution_required" ? "blocked" : report.status;
|
|
594
|
+
return {
|
|
595
|
+
schemaVersion: 2,
|
|
596
|
+
state,
|
|
597
|
+
status,
|
|
598
|
+
verdict: state === "verified" ? "Loop verified." : "Loop needs attention before it should be considered complete.",
|
|
599
|
+
summary: `${report.status} Project Health, ${evidenceTier.confidence.level} evidence confidence, ${authority.conflicts.length} authority conflict(s).`,
|
|
600
|
+
authority: {
|
|
601
|
+
activeLane: authority.activeLane,
|
|
602
|
+
summary: authority.summary,
|
|
603
|
+
stopRule: authority.stopRule
|
|
604
|
+
},
|
|
605
|
+
evidenceTier,
|
|
606
|
+
blockingReasons,
|
|
607
|
+
nextActions,
|
|
608
|
+
maker: {
|
|
609
|
+
title: "Maker instructions",
|
|
610
|
+
instructions: [
|
|
611
|
+
"Read the route/task context before editing source.",
|
|
612
|
+
"Preserve production source behavior and accepted local law unless a human chooses a resolution action.",
|
|
613
|
+
"Stop and report drift when runtime source and Decantr context disagree."
|
|
614
|
+
]
|
|
615
|
+
},
|
|
616
|
+
checker: {
|
|
617
|
+
title: "Checker instructions",
|
|
618
|
+
instructions: [
|
|
619
|
+
"Rerun the verify command after edits.",
|
|
620
|
+
"Check graph anchors and repair plans before accepting a fix.",
|
|
621
|
+
"Treat critique-only or advisory evidence as warning-level unless runtime evidence proves a failure."
|
|
622
|
+
]
|
|
623
|
+
},
|
|
624
|
+
readTargets: readTargetsForFindings(report),
|
|
625
|
+
graphImpact: {
|
|
626
|
+
status: !report.graph.present ? "missing" : report.graph.ready && report.graph.current !== false ? "ready" : "stale",
|
|
627
|
+
snapshotId: report.graph.snapshotId,
|
|
628
|
+
sourceHash: report.graph.sourceHash,
|
|
629
|
+
sourceArtifactCount: report.graph.sourceArtifactCount,
|
|
630
|
+
staleArtifacts: report.graph.staleArtifacts
|
|
631
|
+
},
|
|
632
|
+
stopConditions: [
|
|
633
|
+
"Runtime source and Decantr context disagree.",
|
|
634
|
+
"Typed graph is missing for the route or changed file.",
|
|
635
|
+
"A finding requires contract/source/local-law mutation outside the current explicit workflow."
|
|
636
|
+
],
|
|
637
|
+
verifyCommand: report.ci.recommendedCommand
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
|
|
347
641
|
// src/runtime.ts
|
|
348
642
|
import { existsSync, readdirSync, readFileSync as readFileSync2, statSync } from "fs";
|
|
349
643
|
import { createServer } from "http";
|
|
@@ -1160,13 +1454,16 @@ function isAcceptedTokenValue(value, tokenHints) {
|
|
|
1160
1454
|
return value.includes(normalizedHint) || value.includes(`var(${normalizedHint})`);
|
|
1161
1455
|
});
|
|
1162
1456
|
}
|
|
1163
|
-
function arbitraryInlineStyleValue(property, value, tokenHints) {
|
|
1457
|
+
function arbitraryInlineStyleValue(property, value, tokenHints, options = {}) {
|
|
1164
1458
|
const normalizedProperty = property.trim();
|
|
1165
1459
|
const normalizedValue = value.trim();
|
|
1166
1460
|
if (!normalizedProperty || !normalizedValue) return null;
|
|
1167
1461
|
const isVisualProperty = INLINE_STYLE_PROPERTIES.has(normalizedProperty) || STYLESHEET_VISUAL_PROPERTIES.has(normalizedProperty) || normalizedProperty.startsWith("--");
|
|
1168
1462
|
if (!isVisualProperty) return null;
|
|
1169
1463
|
if (isAcceptedTokenValue(normalizedValue, tokenHints)) return null;
|
|
1464
|
+
if (options.allowCssVariableValues && /\bvar\(\s*--[A-Za-z0-9_-]+/i.test(normalizedValue)) {
|
|
1465
|
+
return null;
|
|
1466
|
+
}
|
|
1170
1467
|
if (/(?:^|[\s,(])#[0-9a-f]{3,8}\b|(?:rgba?|hsla?|oklch|oklab|lch|lab|color-mix)\(/i.test(
|
|
1171
1468
|
normalizedValue
|
|
1172
1469
|
)) {
|
|
@@ -1286,7 +1583,9 @@ function collectStylesheetBridgeDriftFindings(input) {
|
|
|
1286
1583
|
const property = match.groups?.property;
|
|
1287
1584
|
const rawValue = match.groups?.value;
|
|
1288
1585
|
if (!property || !rawValue) continue;
|
|
1289
|
-
const value = arbitraryInlineStyleValue(property, rawValue, input.bridge.tokenHints
|
|
1586
|
+
const value = arbitraryInlineStyleValue(property, rawValue, input.bridge.tokenHints, {
|
|
1587
|
+
allowCssVariableValues: true
|
|
1588
|
+
});
|
|
1290
1589
|
if (!value) continue;
|
|
1291
1590
|
const lineNumber = lineIndex + 1;
|
|
1292
1591
|
const key = `${file}:${lineNumber}:${value}`;
|
|
@@ -2005,9 +2304,9 @@ function sourceLineCount(source) {
|
|
|
2005
2304
|
}
|
|
2006
2305
|
function verifyInteractionsInSource(interactions, sources) {
|
|
2007
2306
|
if (interactions.length === 0 || sources.size === 0) return [];
|
|
2008
|
-
const
|
|
2307
|
+
const unique2 = Array.from(new Set(interactions));
|
|
2009
2308
|
const missing = [];
|
|
2010
|
-
for (const interaction of
|
|
2309
|
+
for (const interaction of unique2) {
|
|
2011
2310
|
const requirement = INTERACTION_SIGNALS[interaction];
|
|
2012
2311
|
if (!requirement) {
|
|
2013
2312
|
continue;
|
|
@@ -3729,17 +4028,17 @@ function resolveSourceSymbolOrigin(context, pathOrSourceFile, localName, options
|
|
|
3729
4028
|
|
|
3730
4029
|
// src/index.ts
|
|
3731
4030
|
var VERIFICATION_SCHEMA_URLS = {
|
|
3732
|
-
common:
|
|
4031
|
+
common: VERIFICATION_COMMON_V2_SCHEMA_URL,
|
|
3733
4032
|
projectAudit: "https://decantr.ai/schemas/project-audit-report.v1.json",
|
|
3734
|
-
projectHealth:
|
|
3735
|
-
decantrCi:
|
|
3736
|
-
evidenceBundle:
|
|
4033
|
+
projectHealth: PROJECT_HEALTH_REPORT_V2_SCHEMA_URL,
|
|
4034
|
+
decantrCi: DECANTR_CI_REPORT_V2_SCHEMA_URL,
|
|
4035
|
+
evidenceBundle: EVIDENCE_BUNDLE_V2_SCHEMA_URL,
|
|
3737
4036
|
scanReport: "https://decantr.ai/schemas/scan-report.v1.json",
|
|
3738
|
-
workspaceHealth:
|
|
4037
|
+
workspaceHealth: WORKSPACE_HEALTH_REPORT_V2_SCHEMA_URL,
|
|
3739
4038
|
fileCritique: "https://decantr.ai/schemas/file-critique-report.v1.json",
|
|
3740
4039
|
showcaseShortlist: "https://decantr.ai/schemas/showcase-shortlist-report.v1.json"
|
|
3741
4040
|
};
|
|
3742
|
-
var EVIDENCE_BUNDLE_SCHEMA_URL =
|
|
4041
|
+
var EVIDENCE_BUNDLE_SCHEMA_URL = EVIDENCE_BUNDLE_V2_SCHEMA_URL;
|
|
3743
4042
|
function hashString(value) {
|
|
3744
4043
|
return createHash("sha256").update(value).digest("hex").slice(0, 16);
|
|
3745
4044
|
}
|
|
@@ -3941,7 +4240,8 @@ function createContractAssertions(projectRoot, audit) {
|
|
|
3941
4240
|
if (v4) {
|
|
3942
4241
|
const declaredRoutes = Object.keys(v4.blueprint.routes ?? {}).sort();
|
|
3943
4242
|
for (const route of declaredRoutes) {
|
|
3944
|
-
const routeTarget =
|
|
4243
|
+
const routeTarget = v4.blueprint.routes?.[route];
|
|
4244
|
+
if (!routeTarget) continue;
|
|
3945
4245
|
const section = v4.blueprint.sections.find((entry) => entry.id === routeTarget.section);
|
|
3946
4246
|
const page = section?.pages.find((entry) => entry.id === routeTarget.page);
|
|
3947
4247
|
assertions.push(
|
|
@@ -4027,6 +4327,8 @@ function createContractAssertions(projectRoot, audit) {
|
|
|
4027
4327
|
}
|
|
4028
4328
|
function createEvidenceBundle(input) {
|
|
4029
4329
|
const assertions = input.assertions ?? createContractAssertions(input.projectRoot, input.audit);
|
|
4330
|
+
const evidenceTier = input.report.evidenceTier ?? createEvidenceTier(input.report);
|
|
4331
|
+
const authority = input.report.authority ?? createAuthorityResolution(input.report);
|
|
4030
4332
|
let resolvedProjectRoot = input.projectRoot;
|
|
4031
4333
|
try {
|
|
4032
4334
|
resolvedProjectRoot = realpathSync2(input.projectRoot);
|
|
@@ -4039,13 +4341,18 @@ function createEvidenceBundle(input) {
|
|
|
4039
4341
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4040
4342
|
project: {
|
|
4041
4343
|
id: projectId,
|
|
4042
|
-
rootLabel: basename2(input.projectRoot) || "project"
|
|
4344
|
+
rootLabel: basename2(input.projectRoot) || "project",
|
|
4345
|
+
workflowMode: input.report.summary.workflowMode,
|
|
4346
|
+
adoptionMode: input.report.summary.adoptionMode
|
|
4043
4347
|
},
|
|
4044
4348
|
toolchain: {
|
|
4045
|
-
verifierVersion: input.verifierVersion ?? null
|
|
4349
|
+
verifierVersion: input.verifierVersion ?? null,
|
|
4350
|
+
cliVersion: input.cliVersion ?? null,
|
|
4351
|
+
mcpServerVersion: input.mcpServerVersion ?? null
|
|
4046
4352
|
},
|
|
4047
4353
|
privacy: {
|
|
4048
4354
|
localOnly: true,
|
|
4355
|
+
sourceIncluded: false,
|
|
4049
4356
|
redactedFields: [
|
|
4050
4357
|
"source",
|
|
4051
4358
|
"prompt",
|
|
@@ -4054,8 +4361,10 @@ function createEvidenceBundle(input) {
|
|
|
4054
4361
|
"absolute_path",
|
|
4055
4362
|
"repository_name"
|
|
4056
4363
|
],
|
|
4057
|
-
screenshotsLocalOnly: true
|
|
4364
|
+
screenshotsLocalOnly: true,
|
|
4365
|
+
hostedUploadAllowed: false
|
|
4058
4366
|
},
|
|
4367
|
+
evidenceTier,
|
|
4059
4368
|
health: {
|
|
4060
4369
|
status: input.report.status,
|
|
4061
4370
|
score: input.report.score,
|
|
@@ -4073,7 +4382,9 @@ function createEvidenceBundle(input) {
|
|
|
4073
4382
|
graphDiff: provenanceEntry(input.projectRoot, ".decantr/graph/graph.diff.json"),
|
|
4074
4383
|
contractCapsule: provenanceEntry(input.projectRoot, ".decantr/graph/contract-capsule.json"),
|
|
4075
4384
|
...input.workspaceConfigPath ? { workspaceConfig: provenanceForPath(input.projectRoot, input.workspaceConfigPath) } : {},
|
|
4076
|
-
...input.designTokensPath ? { designTokens: provenanceForPath(input.projectRoot, input.designTokensPath) } : {}
|
|
4385
|
+
...input.designTokensPath ? { designTokens: provenanceForPath(input.projectRoot, input.designTokensPath) } : {},
|
|
4386
|
+
...input.runtimeProbePath ? { runtimeProbe: provenanceForPath(input.projectRoot, input.runtimeProbePath) } : {},
|
|
4387
|
+
...input.visualManifestPath ? { visualManifest: provenanceForPath(input.projectRoot, input.visualManifestPath) } : {}
|
|
4077
4388
|
},
|
|
4078
4389
|
assertions,
|
|
4079
4390
|
findings: input.report.findings.map((finding) => ({
|
|
@@ -4090,10 +4401,37 @@ function createEvidenceBundle(input) {
|
|
|
4090
4401
|
graph: finding.graph,
|
|
4091
4402
|
repair: redactRepairAction(input.projectRoot, finding.repair),
|
|
4092
4403
|
repairPlan: buildProjectHealthRepairPlan(input.projectRoot, finding),
|
|
4404
|
+
evidenceTier: finding.evidenceTier ?? evidenceTier,
|
|
4405
|
+
authorityLane: finding.authorityLane ?? authority.conflicts.find((conflict) => conflict.id === finding.id)?.lane ?? authority.activeLane,
|
|
4406
|
+
resolutionActions: finding.resolutionActions ?? authority.conflicts.find((conflict) => conflict.id === finding.id)?.recommendedActions,
|
|
4407
|
+
privacy: finding.privacy ?? {
|
|
4408
|
+
sourceIncluded: false,
|
|
4409
|
+
redacted: true,
|
|
4410
|
+
localOnly: true
|
|
4411
|
+
},
|
|
4412
|
+
loopVerdict: finding.loopVerdict ?? input.report.loop?.state,
|
|
4093
4413
|
remediationSummary: finding.remediation.summary,
|
|
4094
4414
|
commands: finding.remediation.commands,
|
|
4095
4415
|
promptCommand: `decantr health --prompt ${finding.id}`
|
|
4096
4416
|
})),
|
|
4417
|
+
runtimeProbe: input.runtimeProbe ?? null,
|
|
4418
|
+
artifacts: input.artifacts ?? [
|
|
4419
|
+
{
|
|
4420
|
+
id: "artifact:project-health",
|
|
4421
|
+
kind: "project-health",
|
|
4422
|
+
path: "decantr-health.json",
|
|
4423
|
+
hash: null,
|
|
4424
|
+
localOnly: true,
|
|
4425
|
+
redacted: true
|
|
4426
|
+
}
|
|
4427
|
+
],
|
|
4428
|
+
dashboard: {
|
|
4429
|
+
safeToUpload: false,
|
|
4430
|
+
retentionClass: "local-only",
|
|
4431
|
+
redactionNotes: [
|
|
4432
|
+
"Evidence Bundle v2 does not include raw source, prompts, secrets, absolute paths, or uploaded screenshots by default."
|
|
4433
|
+
]
|
|
4434
|
+
},
|
|
4097
4435
|
...input.browser ? { browser: input.browser } : {},
|
|
4098
4436
|
...input.designTokens ? { designTokens: input.designTokens } : {}
|
|
4099
4437
|
};
|
|
@@ -4848,6 +5186,10 @@ function buildRegistryContext(projectRoot) {
|
|
|
4848
5186
|
const patternRegistry = /* @__PURE__ */ new Map();
|
|
4849
5187
|
const cacheDir = join6(projectRoot, ".decantr", "cache");
|
|
4850
5188
|
const customDir = join6(projectRoot, ".decantr", "custom");
|
|
5189
|
+
const addPattern = (id, source, data = null) => {
|
|
5190
|
+
if (typeof id !== "string" || !id.trim() || patternRegistry.has(id)) return;
|
|
5191
|
+
patternRegistry.set(id, data ?? { id, source });
|
|
5192
|
+
};
|
|
4851
5193
|
const cachedThemesDir = join6(cacheDir, "@official", "themes");
|
|
4852
5194
|
try {
|
|
4853
5195
|
if (existsSync6(cachedThemesDir)) {
|
|
@@ -4881,17 +5223,27 @@ function buildRegistryContext(projectRoot) {
|
|
|
4881
5223
|
(name) => name.endsWith(".json") && name !== "index.json"
|
|
4882
5224
|
)) {
|
|
4883
5225
|
const data = JSON.parse(readFileSync6(join6(cachedPatternsDir, file), "utf-8"));
|
|
4884
|
-
|
|
4885
|
-
patternRegistry.set(data.id, data);
|
|
4886
|
-
}
|
|
5226
|
+
addPattern(data.id, "cache", data);
|
|
4887
5227
|
}
|
|
4888
5228
|
}
|
|
4889
5229
|
} catch {
|
|
4890
5230
|
}
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
5231
|
+
const customPatternsDir = join6(customDir, "patterns");
|
|
5232
|
+
try {
|
|
5233
|
+
if (existsSync6(customPatternsDir)) {
|
|
5234
|
+
for (const file of readdirSync5(customPatternsDir).filter((name) => name.endsWith(".json"))) {
|
|
5235
|
+
const data = JSON.parse(readFileSync6(join6(customPatternsDir, file), "utf-8"));
|
|
5236
|
+
addPattern(data.id, "custom", data);
|
|
5237
|
+
}
|
|
4894
5238
|
}
|
|
5239
|
+
} catch {
|
|
5240
|
+
}
|
|
5241
|
+
const localPatterns = readJsonIfExists(join6(projectRoot, ".decantr", "local-patterns.json"));
|
|
5242
|
+
for (const pattern of localPatterns?.patterns ?? []) {
|
|
5243
|
+
addPattern(pattern.id, "local-law", pattern);
|
|
5244
|
+
}
|
|
5245
|
+
for (const id of ["content-section", "footer", "form-basic", "hero", "nav-header"]) {
|
|
5246
|
+
addPattern(id, "bundled");
|
|
4895
5247
|
}
|
|
4896
5248
|
return { themeRegistry, patternRegistry };
|
|
4897
5249
|
}
|
|
@@ -7666,9 +8018,15 @@ function getJsxTextContent(node) {
|
|
|
7666
8018
|
});
|
|
7667
8019
|
return text;
|
|
7668
8020
|
}
|
|
8021
|
+
function hasNonEmptyJsxAttribute(attributes, ...names) {
|
|
8022
|
+
const attribute = getJsxAttribute(attributes, ...names);
|
|
8023
|
+
if (!attribute?.initializer) return false;
|
|
8024
|
+
const literalValue = getJsxAttributeLiteralValue(attribute);
|
|
8025
|
+
return literalValue === null || literalValue.trim().length > 0;
|
|
8026
|
+
}
|
|
7669
8027
|
function hasAccessibleLabel(attributes, textContent) {
|
|
7670
8028
|
return Boolean(
|
|
7671
|
-
|
|
8029
|
+
hasNonEmptyJsxAttribute(attributes, "aria-label", "aria-labelledby", "title") || textContent.trim().length > 0
|
|
7672
8030
|
);
|
|
7673
8031
|
}
|
|
7674
8032
|
function isNonSemanticInteractiveTag(tagName) {
|
|
@@ -7693,6 +8051,18 @@ function collectLabelForIds(root) {
|
|
|
7693
8051
|
walk(root);
|
|
7694
8052
|
return ids;
|
|
7695
8053
|
}
|
|
8054
|
+
function collectElementIds(root) {
|
|
8055
|
+
const ids = /* @__PURE__ */ new Set();
|
|
8056
|
+
const walk = (node) => {
|
|
8057
|
+
if (ts6.isJsxSelfClosingElement(node) || ts6.isJsxOpeningElement(node)) {
|
|
8058
|
+
const idValue = getJsxAttributeLiteralValue(getJsxAttribute(node.attributes, "id"));
|
|
8059
|
+
if (idValue?.trim()) ids.add(idValue.trim());
|
|
8060
|
+
}
|
|
8061
|
+
ts6.forEachChild(node, walk);
|
|
8062
|
+
};
|
|
8063
|
+
walk(root);
|
|
8064
|
+
return ids;
|
|
8065
|
+
}
|
|
7696
8066
|
function isWrappedInJsxLabel(node) {
|
|
7697
8067
|
let current = node.parent;
|
|
7698
8068
|
while (current) {
|
|
@@ -7715,21 +8085,37 @@ function hasAncestorJsxTag(node, tagName) {
|
|
|
7715
8085
|
}
|
|
7716
8086
|
function isLabelableFormControl(tagName, attributes) {
|
|
7717
8087
|
if (!tagName) return false;
|
|
7718
|
-
|
|
7719
|
-
if (
|
|
8088
|
+
const normalizedTagName = tagName.toLowerCase();
|
|
8089
|
+
if (normalizedTagName === "select" || normalizedTagName === "textarea") return true;
|
|
8090
|
+
if (normalizedTagName !== "input" && !isLikelyProjectFormControlTag(tagName)) return false;
|
|
7720
8091
|
const inputType = (getJsxAttributeLiteralValue(getJsxAttribute(attributes, "type")) ?? "text").toLowerCase();
|
|
7721
8092
|
return !["hidden", "submit", "reset", "button"].includes(inputType);
|
|
7722
8093
|
}
|
|
8094
|
+
function isLikelyProjectFormControlTag(tagName) {
|
|
8095
|
+
if (tagName === tagName.toLowerCase()) return false;
|
|
8096
|
+
const normalizedTagName = tagName.toLowerCase();
|
|
8097
|
+
return /(?:^|(?:text|email|password|search|number|tel|url|date))input$/.test(normalizedTagName) || /^(?:textarea|textArea|select|combobox|comboBox|checkbox|radio|switch)$/i.test(tagName);
|
|
8098
|
+
}
|
|
7723
8099
|
function getNormalizedInputType(attributes) {
|
|
7724
8100
|
return (getJsxAttributeLiteralValue(getJsxAttribute(attributes, "type")) ?? "text").trim().toLowerCase();
|
|
7725
8101
|
}
|
|
7726
|
-
function hasFormControlLabel(node, tagName, attributes, labelForIds, textContent) {
|
|
8102
|
+
function hasFormControlLabel(node, tagName, attributes, labelForIds, elementIds, textContent) {
|
|
7727
8103
|
if (!isLabelableFormControl(tagName, attributes)) return true;
|
|
7728
|
-
if (
|
|
8104
|
+
if (hasNonEmptyJsxAttribute(attributes, "aria-label", "title")) return true;
|
|
8105
|
+
if (hasAriaLabelledByReference(attributes, elementIds)) return true;
|
|
8106
|
+
if (textContent.trim().length > 0) return true;
|
|
7729
8107
|
if (isWrappedInJsxLabel(node)) return true;
|
|
7730
8108
|
const idValue = getJsxAttributeLiteralValue(getJsxAttribute(attributes, "id"));
|
|
7731
8109
|
return Boolean(idValue && labelForIds.has(idValue));
|
|
7732
8110
|
}
|
|
8111
|
+
function hasAriaLabelledByReference(attributes, elementIds) {
|
|
8112
|
+
const attribute = getJsxAttribute(attributes, "aria-labelledby");
|
|
8113
|
+
if (!attribute?.initializer) return false;
|
|
8114
|
+
const literalValue = getJsxAttributeLiteralValue(attribute);
|
|
8115
|
+
if (literalValue === null) return true;
|
|
8116
|
+
const references = literalValue.trim().split(/\s+/).filter(Boolean);
|
|
8117
|
+
return references.some((reference) => elementIds.has(reference));
|
|
8118
|
+
}
|
|
7733
8119
|
function isExternalLinkTargetBlankWithoutRel(attributes) {
|
|
7734
8120
|
const targetValue = getJsxAttributeLiteralValue(getJsxAttribute(attributes, "target"));
|
|
7735
8121
|
if (targetValue !== "_blank") return false;
|
|
@@ -9414,7 +9800,7 @@ function countAuthAnonymousRedirectSignals(code) {
|
|
|
9414
9800
|
];
|
|
9415
9801
|
return patterns.reduce((count, pattern) => count + (pattern.test(code) ? 1 : 0), 0);
|
|
9416
9802
|
}
|
|
9417
|
-
var OPEN_REDIRECT_QUERY_KEY_PATTERN =
|
|
9803
|
+
var OPEN_REDIRECT_QUERY_KEY_PATTERN = "next|redirect(?:To|[_-]to)?|return(?:To|[_-]to)?|callback(?:Url|[_-]url)?|continue(?:Url|[_-]url)?|from";
|
|
9418
9804
|
var OPEN_REDIRECT_SOURCE_PATTERN = String.raw`\b(?:searchParams|(?:request|req)\.nextUrl\.searchParams|url\.searchParams)\.get\s*\(\s*['"\`](?:${OPEN_REDIRECT_QUERY_KEY_PATTERN})['"\`]\s*\)|\b(?:router\.query|route\.query|query)\.(?:${OPEN_REDIRECT_QUERY_KEY_PATTERN})\b|\b(?:new\s+)?URLSearchParams\s*\(\s*(?:(?:window|globalThis|document|self|parent|top)\.)?location\.(?:search|hash)(?:\.slice\(\s*1\s*\)|\.replace\([^)]*\))?\s*\)\.get\s*\(\s*['"\`](?:${OPEN_REDIRECT_QUERY_KEY_PATTERN})['"\`]\s*\)|\bnew\s+URL\(\s*(?:request|req)\.url\s*\)\.searchParams\.get\s*\(\s*['"\`](?:${OPEN_REDIRECT_QUERY_KEY_PATTERN})['"\`]\s*\)`;
|
|
9419
9805
|
var OPEN_REDIRECT_SOURCE_REGEX = new RegExp(OPEN_REDIRECT_SOURCE_PATTERN, "i");
|
|
9420
9806
|
var OPEN_REDIRECT_QUERY_KEY_REGEX = new RegExp(`^(?:${OPEN_REDIRECT_QUERY_KEY_PATTERN})$`, "i");
|
|
@@ -13330,6 +13716,7 @@ function analyzeAstSignals(filePath, code) {
|
|
|
13330
13716
|
const namedExpressionInitializers = collectNamedExpressionInitializers(sourceFile);
|
|
13331
13717
|
const namedPropertyAliases = collectNamedPropertyAliases(sourceFile);
|
|
13332
13718
|
const labelForIds = collectLabelForIds(sourceFile);
|
|
13719
|
+
const elementIds = collectElementIds(sourceFile);
|
|
13333
13720
|
let navigationLandmarkCount = 0;
|
|
13334
13721
|
let unlabeledNavigationLandmarkCount = 0;
|
|
13335
13722
|
const walk = (node) => {
|
|
@@ -13736,7 +14123,7 @@ function analyzeAstSignals(filePath, code) {
|
|
|
13736
14123
|
signals.authInputWithoutNameCount += 1;
|
|
13737
14124
|
}
|
|
13738
14125
|
}
|
|
13739
|
-
if (!hasFormControlLabel(node, tagName, node.attributes, labelForIds, "")) {
|
|
14126
|
+
if (!hasFormControlLabel(node, tagName, node.attributes, labelForIds, elementIds, "")) {
|
|
13740
14127
|
signals.formControlWithoutLabelCount += 1;
|
|
13741
14128
|
}
|
|
13742
14129
|
}
|
|
@@ -13851,6 +14238,7 @@ function analyzeAstSignals(filePath, code) {
|
|
|
13851
14238
|
tagName,
|
|
13852
14239
|
node.openingElement.attributes,
|
|
13853
14240
|
labelForIds,
|
|
14241
|
+
elementIds,
|
|
13854
14242
|
textContent
|
|
13855
14243
|
)) {
|
|
13856
14244
|
signals.formControlWithoutLabelCount += 1;
|
|
@@ -15636,14 +16024,23 @@ async function critiqueFile(filePath, projectRoot) {
|
|
|
15636
16024
|
});
|
|
15637
16025
|
}
|
|
15638
16026
|
export {
|
|
16027
|
+
AUTHORITY_RESOLUTION_V2_SCHEMA_URL,
|
|
15639
16028
|
COMPONENT_REUSE_RULE_ID,
|
|
16029
|
+
DECANTR_CI_REPORT_V2_SCHEMA_URL,
|
|
15640
16030
|
EVIDENCE_BUNDLE_SCHEMA_URL,
|
|
16031
|
+
EVIDENCE_BUNDLE_V2_SCHEMA_URL,
|
|
15641
16032
|
INTERACTION_SIGNALS,
|
|
15642
16033
|
KNOWN_VERIFICATION_DIAGNOSTICS,
|
|
16034
|
+
LOOP_READINESS_V2_SCHEMA_URL,
|
|
16035
|
+
PROJECT_HEALTH_REPORT_V2_SCHEMA_URL,
|
|
16036
|
+
PROOF_FIELD_REPORT_V2_SCHEMA_URL,
|
|
15643
16037
|
RAW_CONTROL_REUSE_RULE_ID,
|
|
16038
|
+
RUNTIME_PROBE_PAYLOAD_V2_SCHEMA_URL,
|
|
15644
16039
|
SCAN_REPORT_SCHEMA_URL,
|
|
15645
16040
|
STYLE_BRIDGE_ARBITRARY_VALUE_RULE_ID,
|
|
16041
|
+
VERIFICATION_COMMON_V2_SCHEMA_URL,
|
|
15646
16042
|
VERIFICATION_SCHEMA_URLS,
|
|
16043
|
+
WORKSPACE_HEALTH_REPORT_V2_SCHEMA_URL,
|
|
15647
16044
|
anchorFindingsToGraph,
|
|
15648
16045
|
auditBuiltDist,
|
|
15649
16046
|
auditComponentReuse,
|
|
@@ -15653,8 +16050,12 @@ export {
|
|
|
15653
16050
|
collectMissingPackManifestFiles,
|
|
15654
16051
|
collectProjectSourceFiles,
|
|
15655
16052
|
collectSourceImports,
|
|
16053
|
+
createAuthorityOrder,
|
|
16054
|
+
createAuthorityResolution,
|
|
15656
16055
|
createContractAssertions,
|
|
15657
16056
|
createEvidenceBundle,
|
|
16057
|
+
createEvidenceTier,
|
|
16058
|
+
createLoopReadiness,
|
|
15658
16059
|
createProjectSourceProgram,
|
|
15659
16060
|
createSourceInventory,
|
|
15660
16061
|
createUnavailableScanReport,
|