mason-context 0.16.2 → 0.17.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.
@@ -542,11 +542,24 @@ function decisionKnowledge(record, freshness = "unknown", proposalFreshness = "u
542
542
  import fs4 from "fs/promises";
543
543
  import path8 from "path";
544
544
  import { createHash } from "crypto";
545
+ import { z as z3 } from "zod";
545
546
 
546
547
  // src/context/lexical.ts
547
548
  import path7 from "path";
548
549
 
549
550
  // src/decisions/decisions.ts
551
+ var TITLE_MAX_CHARS = 80;
552
+ var BODY_RECOMMENDED_CHARS = 1500;
553
+ var BODY_MAX_CHARS = 2500;
554
+ var boundedText = (field, max, hint) => z3.string({
555
+ errorMap: (issue, ctx) => ({ message: issue.code === "too_big" ? `${field} has ${ctx.data.length} characters; maximum is ${max} (${ctx.data.length - max} over). ${hint}` : ctx.defaultError })
556
+ }).trim().min(1, `${field} must be non-empty`).max(max);
557
+ var decisionTitleSchema = boundedText("title", TITLE_MAX_CHARS, "Tighten it to a specific headline.");
558
+ var decisionBodySchema = boundedText(
559
+ "body",
560
+ BODY_MAX_CHARS,
561
+ `Aim for ${BODY_RECOMMENDED_CHARS} characters or fewer. Preserve the rule, reason, and exceptions; put supporting references in sources.`
562
+ );
550
563
  async function loadDecisionStore(rootDir) {
551
564
  const records = [];
552
565
  const diagnostics = [];
@@ -675,10 +688,10 @@ async function findMissingPartners(resolvedRoot, changedFiles, existsOnDisk, all
675
688
 
676
689
  // src/review/evidence.ts
677
690
  import path10 from "path";
678
- import { z as z5 } from "zod";
691
+ import { z as z7 } from "zod";
679
692
 
680
693
  // src/review/evidence/vitest.ts
681
- import { z as z3 } from "zod";
694
+ import { z as z4 } from "zod";
682
695
 
683
696
  // src/review/evidence/paths.ts
684
697
  function evidencePath(value, sourceRoot, uri = false) {
@@ -714,24 +727,24 @@ function messagePreview(message2) {
714
727
  }
715
728
 
716
729
  // src/review/evidence/vitest.ts
717
- var count = z3.number().int().nonnegative();
718
- var schema = z3.object({
719
- success: z3.boolean(),
730
+ var count = z4.number().int().nonnegative();
731
+ var schema = z4.object({
732
+ success: z4.boolean(),
720
733
  numTotalTests: count,
721
734
  numPassedTests: count,
722
735
  numFailedTests: count,
723
736
  numPendingTests: count,
724
737
  numTodoTests: count,
725
738
  numFailedTestSuites: count,
726
- testResults: z3.array(z3.object({
727
- name: z3.string().min(1),
728
- status: z3.enum(["passed", "failed"]),
729
- message: z3.string().optional(),
730
- assertionResults: z3.array(z3.object({
731
- fullName: z3.string(),
732
- status: z3.enum(["passed", "failed", "pending", "skipped", "todo", "disabled"]),
733
- failureMessages: z3.array(z3.string()).nullable().optional(),
734
- location: z3.object({ line: count, column: count }).nullable().optional()
739
+ testResults: z4.array(z4.object({
740
+ name: z4.string().min(1),
741
+ status: z4.enum(["passed", "failed"]),
742
+ message: z4.string().optional(),
743
+ assertionResults: z4.array(z4.object({
744
+ fullName: z4.string(),
745
+ status: z4.enum(["passed", "failed", "pending", "skipped", "todo", "disabled"]),
746
+ failureMessages: z4.array(z4.string()).nullable().optional(),
747
+ location: z4.object({ line: count, column: count }).nullable().optional()
735
748
  }))
736
749
  }))
737
750
  });
@@ -779,30 +792,30 @@ function parseVitest(raw, sourceRoot) {
779
792
  }
780
793
 
781
794
  // src/review/evidence/sarif.ts
782
- import { z as z4 } from "zod";
783
- var index = z4.number().int().nonnegative();
784
- var artifact = z4.object({ uri: z4.string().optional(), uriBaseId: z4.string().optional(), index: index.optional() });
785
- var message = z4.object({ text: z4.string().optional(), markdown: z4.string().optional(), id: z4.string().optional(), arguments: z4.array(z4.string()).optional() });
786
- var level = z4.enum(["error", "warning", "note", "none"]);
787
- var location = z4.object({ physicalLocation: z4.object({ artifactLocation: artifact.optional(), region: z4.object({ startLine: index.optional(), startColumn: index.optional() }).optional() }).optional() });
788
- var schema2 = z4.object({
789
- version: z4.literal("2.1.0"),
790
- runs: z4.array(z4.object({
791
- tool: z4.object({ driver: z4.object({ name: z4.string().min(1), rules: z4.array(z4.object({ id: z4.string(), defaultConfiguration: z4.object({ level: level.optional() }).optional(), messageStrings: z4.record(message).optional() })).optional(), globalMessageStrings: z4.record(message).optional() }) }),
792
- invocations: z4.array(z4.object({ executionSuccessful: z4.boolean(), commandLine: z4.string().optional(), toolExecutionNotifications: z4.array(z4.object({ level: level.optional(), message })).optional() })).optional(),
793
- versionControlProvenance: z4.array(z4.object({ revisionId: z4.string().optional() })).optional(),
794
- originalUriBaseIds: z4.record(artifact).optional(),
795
- artifacts: z4.array(z4.object({ location: artifact.optional() })).optional(),
796
- results: z4.array(z4.object({
797
- ruleId: z4.string().optional(),
795
+ import { z as z5 } from "zod";
796
+ var index = z5.number().int().nonnegative();
797
+ var artifact = z5.object({ uri: z5.string().optional(), uriBaseId: z5.string().optional(), index: index.optional() });
798
+ var message = z5.object({ text: z5.string().optional(), markdown: z5.string().optional(), id: z5.string().optional(), arguments: z5.array(z5.string()).optional() });
799
+ var level = z5.enum(["error", "warning", "note", "none"]);
800
+ var location = z5.object({ physicalLocation: z5.object({ artifactLocation: artifact.optional(), region: z5.object({ startLine: index.optional(), startColumn: index.optional() }).optional() }).optional() });
801
+ var schema2 = z5.object({
802
+ version: z5.literal("2.1.0"),
803
+ runs: z5.array(z5.object({
804
+ tool: z5.object({ driver: z5.object({ name: z5.string().min(1), rules: z5.array(z5.object({ id: z5.string(), defaultConfiguration: z5.object({ level: level.optional() }).optional(), messageStrings: z5.record(message).optional() })).optional(), globalMessageStrings: z5.record(message).optional() }) }),
805
+ invocations: z5.array(z5.object({ executionSuccessful: z5.boolean(), commandLine: z5.string().optional(), toolExecutionNotifications: z5.array(z5.object({ level: level.optional(), message })).optional() })).optional(),
806
+ versionControlProvenance: z5.array(z5.object({ revisionId: z5.string().optional() })).optional(),
807
+ originalUriBaseIds: z5.record(artifact).optional(),
808
+ artifacts: z5.array(z5.object({ location: artifact.optional() })).optional(),
809
+ results: z5.array(z5.object({
810
+ ruleId: z5.string().optional(),
798
811
  ruleIndex: index.optional(),
799
812
  message,
800
813
  level: level.optional(),
801
- kind: z4.enum(["fail", "pass", "open", "informational", "notApplicable", "review"]).optional(),
802
- baselineState: z4.enum(["new", "unchanged", "updated", "absent"]).optional(),
803
- suppressions: z4.array(z4.object({ kind: z4.enum(["inSource", "external"]), status: z4.enum(["accepted", "underReview", "rejected"]).optional() })).nullable().optional(),
804
- locations: z4.array(location).optional(),
805
- relatedLocations: z4.array(location).optional()
814
+ kind: z5.enum(["fail", "pass", "open", "informational", "notApplicable", "review"]).optional(),
815
+ baselineState: z5.enum(["new", "unchanged", "updated", "absent"]).optional(),
816
+ suppressions: z5.array(z5.object({ kind: z5.enum(["inSource", "external"]), status: z5.enum(["accepted", "underReview", "rejected"]).optional() })).nullable().optional(),
817
+ locations: z5.array(location).optional(),
818
+ relatedLocations: z5.array(location).optional()
806
819
  })).optional()
807
820
  }))
808
821
  });
@@ -891,20 +904,76 @@ function parseSarif(raw, sourceRoot) {
891
904
  };
892
905
  }
893
906
 
907
+ // src/review/evidence/check.ts
908
+ import { z as z6 } from "zod";
909
+ var schema3 = z6.object({
910
+ version: z6.literal(1),
911
+ commit: z6.string().regex(/^(?:[a-fA-F0-9]{40}|[a-fA-F0-9]{64})$/).optional(),
912
+ results: z6.array(z6.object({
913
+ name: z6.string().min(1).max(1e3),
914
+ status: z6.enum(["passed", "failed", "skipped"]),
915
+ message: z6.string().max(1e5).optional(),
916
+ locations: z6.array(z6.object({
917
+ file: z6.string().min(1).max(4e3),
918
+ line: z6.number().int().positive().optional(),
919
+ column: z6.number().int().positive().optional()
920
+ })).max(100).default([])
921
+ })).max(1e5)
922
+ });
923
+ function parseCheck(raw, sourceRoot) {
924
+ const report = schema3.parse(raw);
925
+ const diagnostics = [], findings = [];
926
+ const counts = { total: report.results.length, passed: 0, failed: 0, skipped: 0 };
927
+ for (const [index2, result] of report.results.entries()) {
928
+ counts[result.status]++;
929
+ const locations = result.locations.flatMap((location2) => {
930
+ const file = evidencePath(location2.file, sourceRoot);
931
+ if (!file) {
932
+ diagnostics.push(`Check location is outside the declared checkout or invalid: ${location2.file}`);
933
+ return [];
934
+ }
935
+ return [{ ...location2, file }];
936
+ });
937
+ if (result.status === "failed") findings.push({
938
+ id: String(index2),
939
+ ...messagePreview([result.name, result.message].filter(Boolean).join("\n")),
940
+ severity: "error",
941
+ state: "active",
942
+ locations
943
+ });
944
+ }
945
+ if (!counts.total) diagnostics.push("No check results were recorded; an empty report is not passing evidence.");
946
+ if (counts.skipped) diagnostics.push(`${counts.skipped} checks were skipped; verification is incomplete.`);
947
+ return {
948
+ outcome: counts.failed ? "failed" : !counts.total ? "unavailable" : !counts.passed ? "skipped" : "passed",
949
+ counts,
950
+ findings,
951
+ incomplete: diagnostics.length > 0,
952
+ diagnostics,
953
+ ...report.commit ? { reportedCommits: [report.commit] } : {}
954
+ };
955
+ }
956
+
894
957
  // src/review/evidence.ts
895
- var checkSchema = z5.object({
896
- id: z5.string().min(1).max(100),
897
- kind: z5.enum(["tests", "static-analysis", "security", "complexity", "duplication"]),
898
- tool: z5.string().min(1).max(200),
899
- command: z5.string().min(1).max(2e3),
900
- commit: z5.string().regex(/^(?:[a-fA-F0-9]{40}|[a-fA-F0-9]{64})$/).nullable().optional(),
901
- workingTreeClean: z5.boolean().optional(),
902
- source: z5.string().max(2e3).optional(),
903
- sourceRoot: z5.string().min(1).max(4e3).optional(),
904
- status: z5.enum(["completed", "skipped", "unavailable"]).default("completed"),
905
- reason: z5.string().min(1).max(2e3).optional(),
906
- exitCode: z5.number().int().nullable().optional(),
907
- report: z5.object({ format: z5.enum(["vitest-json", "sarif"]), path: z5.string().min(1).max(4e3) }).optional()
958
+ var checkSchema = z7.object({
959
+ id: z7.string().min(1).max(100),
960
+ kind: z7.enum(["tests", "static-analysis", "security", "complexity", "duplication", "documentation"]),
961
+ tool: z7.string().min(1).max(200),
962
+ command: z7.string().min(1).max(2e3),
963
+ commit: z7.string().regex(/^(?:[a-fA-F0-9]{40}|[a-fA-F0-9]{64})$/).nullable().optional(),
964
+ workingTreeClean: z7.boolean().optional(),
965
+ source: z7.string().max(2e3).optional(),
966
+ sourceRoot: z7.string().min(1).max(4e3).optional(),
967
+ status: z7.enum(["completed", "skipped", "unavailable"]).default("completed"),
968
+ reason: z7.string().min(1).max(2e3).optional(),
969
+ exitCode: z7.number().int().nullable().optional(),
970
+ environment: z7.object({
971
+ toolVersion: z7.string().min(1).max(200),
972
+ runtime: z7.string().min(1).max(200).optional(),
973
+ platform: z7.string().min(1).max(200).optional(),
974
+ configuration: z7.array(z7.string().min(1).max(4e3)).max(100).optional()
975
+ }).optional(),
976
+ report: z7.object({ format: z7.enum(["vitest-json", "sarif", "mason-check-json"]), path: z7.string().min(1).max(4e3) }).optional()
908
977
  });
909
978
  function relativeArtifact(root, file) {
910
979
  const relative = path10.isAbsolute(file) ? path10.relative(root, file) : file;
@@ -946,7 +1015,7 @@ async function collectReviewEvidence(root, manifests, changedFiles, decisions, d
946
1015
  let manifest, raw;
947
1016
  try {
948
1017
  manifest = relativeArtifact(root, supplied);
949
- raw = z5.object({ version: z5.literal(1), checks: z5.array(z5.unknown()) }).parse(await readStoreJson(root, manifest));
1018
+ raw = z7.object({ version: z7.literal(1), checks: z7.array(z7.unknown()) }).parse(await readStoreJson(root, manifest));
950
1019
  } catch (error) {
951
1020
  output.diagnostics.push(`${supplied}: ${String(error)}`);
952
1021
  continue;
@@ -985,7 +1054,8 @@ async function collectReviewEvidence(root, manifests, changedFiles, decisions, d
985
1054
  counts: {},
986
1055
  incomplete: false,
987
1056
  diagnostics: [],
988
- truncated: false
1057
+ truncated: false,
1058
+ ...check.environment ? { environment: check.environment } : {}
989
1059
  };
990
1060
  output.checks.push(result);
991
1061
  if (check.workingTreeClean !== true) result.diagnostics.push("The check's working tree was dirty or not recorded; its results cannot be attributed to the claimed commit alone.");
@@ -998,13 +1068,13 @@ async function collectReviewEvidence(root, manifests, changedFiles, decisions, d
998
1068
  }
999
1069
  try {
1000
1070
  if (!check.report) throw new Error("Completed check has no report artifact.");
1001
- if (check.kind === "tests" !== (check.report.format === "vitest-json")) throw new Error("Test checks require vitest-json; analysis checks require sarif.");
1071
+ if (check.report.format !== "mason-check-json" && check.kind === "tests" !== (check.report.format === "vitest-json")) throw new Error("Test checks require vitest-json or mason-check-json; analysis checks require sarif or mason-check-json.");
1002
1072
  const file = relativeArtifact(root, check.report.path);
1003
1073
  const report = await readStoreJson(root, file);
1004
1074
  if (report === null) throw new Error(`Report artifact is missing: ${file}`);
1005
1075
  const sourceRoot = check.sourceRoot ?? root;
1006
1076
  if (!path10.posix.isAbsolute(sourceRoot) && !/^[A-Za-z]:[\\/]/.test(sourceRoot)) throw new Error("sourceRoot must identify the absolute checkout root on the check runner.");
1007
- const parsed = check.report.format === "vitest-json" ? parseVitest(report, sourceRoot) : parseSarif(report, sourceRoot);
1077
+ const parsed = check.report.format === "vitest-json" ? parseVitest(report, sourceRoot) : check.report.format === "mason-check-json" ? parseCheck(report, sourceRoot) : parseSarif(report, sourceRoot);
1008
1078
  result.outcome = parsed.outcome;
1009
1079
  result.counts = parsed.counts;
1010
1080
  result.incomplete = parsed.incomplete;
@@ -1024,7 +1094,7 @@ async function collectReviewEvidence(root, manifests, changedFiles, decisions, d
1024
1094
  result.outcome = "failed";
1025
1095
  result.diagnostics.push(`Check command exited ${check.exitCode} despite a report with no active failures.`);
1026
1096
  }
1027
- if (check.report.format === "vitest-json" && pairs === void 0) {
1097
+ if (check.kind === "tests" && pairs === void 0) {
1028
1098
  try {
1029
1099
  pairs = (await buildTestMap(root)).paired;
1030
1100
  } catch (error) {
@@ -1246,6 +1316,10 @@ function evidenceSummary(report) {
1246
1316
  for (const check of evidence.checks) {
1247
1317
  lines.push(` [${check.kind}] ${display(check.id)}: ${check.outcome}, ${check.freshness} (${display(check.tool)})`);
1248
1318
  lines.push(` command: ${display(check.command)}; tested commit: ${check.commit ?? "unknown"}`);
1319
+ if (check.environment) {
1320
+ lines.push(` environment: ${display(check.tool)} ${display(check.environment.toolVersion)}` + (check.environment.runtime ? `; ${display(check.environment.runtime)}` : "") + (check.environment.platform ? `; ${display(check.environment.platform)}` : ""));
1321
+ if (check.environment.configuration?.length) lines.push(" configuration: " + check.environment.configuration.map((value) => display(value)).join(", "));
1322
+ }
1249
1323
  lines.push(` source: ${display(check.source ?? check.manifest)}; report: ${display(check.report?.path ?? "none")}`);
1250
1324
  if (Object.keys(check.counts).length) lines.push(` counts: ${JSON.stringify(check.counts)}`);
1251
1325
  for (const diagnostic of check.diagnostics.slice(0, 5)) lines.push(` note: ${display(diagnostic)}`);