strikethroo 3.17.2 → 3.18.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.
Files changed (59) hide show
  1. package/dist-web/assets/{arc-C3L1jkFY.js → arc-BlSNa2QY.js} +1 -1
  2. package/dist-web/assets/{architectureDiagram-3BPJPVTR-Dc5wdWPF.js → architectureDiagram-3BPJPVTR-CPcCF2a4.js} +1 -1
  3. package/dist-web/assets/{blockDiagram-GPEHLZMM-BCqaESV7.js → blockDiagram-GPEHLZMM-ECDR0edZ.js} +1 -1
  4. package/dist-web/assets/{c4Diagram-AAUBKEIU-DNecSAwM.js → c4Diagram-AAUBKEIU-oM3VnEAQ.js} +1 -1
  5. package/dist-web/assets/channel-CmU4mBgT.js +1 -0
  6. package/dist-web/assets/{chunk-2J33WTMH-3QS28848.js → chunk-2J33WTMH-DHk5SJLz.js} +1 -1
  7. package/dist-web/assets/{chunk-4BX2VUAB-B6R-x-SN.js → chunk-4BX2VUAB-CjBwgkMu.js} +1 -1
  8. package/dist-web/assets/{chunk-55IACEB6-BYY5vsbu.js → chunk-55IACEB6-D95zqyiV.js} +1 -1
  9. package/dist-web/assets/{chunk-727SXJPM-BZxIJIkN.js → chunk-727SXJPM-DCCPRUfv.js} +1 -1
  10. package/dist-web/assets/{chunk-AQP2D5EJ-Dwpt8x10.js → chunk-AQP2D5EJ-qur7KQyD.js} +1 -1
  11. package/dist-web/assets/{chunk-FMBD7UC4-BkIlqLqc.js → chunk-FMBD7UC4-D46Su7Fv.js} +1 -1
  12. package/dist-web/assets/{chunk-ND2GUHAM-BZAfIvWk.js → chunk-ND2GUHAM-BioGsqxt.js} +1 -1
  13. package/dist-web/assets/{chunk-QZHKN3VN-wAp9ruCQ.js → chunk-QZHKN3VN-C8LdDiqn.js} +1 -1
  14. package/dist-web/assets/classDiagram-4FO5ZUOK-D_FMKQsB.js +1 -0
  15. package/dist-web/assets/classDiagram-v2-Q7XG4LA2-D_FMKQsB.js +1 -0
  16. package/dist-web/assets/{cose-bilkent-S5V4N54A-BWhls-Li.js → cose-bilkent-S5V4N54A-ccY03BRh.js} +1 -1
  17. package/dist-web/assets/{dagre-BM42HDAG-D_rm5H8p.js → dagre-BM42HDAG-BYLmCViB.js} +1 -1
  18. package/dist-web/assets/{diagram-2AECGRRQ-CgrRz62Y.js → diagram-2AECGRRQ-DTvenvXR.js} +1 -1
  19. package/dist-web/assets/{diagram-5GNKFQAL-BRdPFx5y.js → diagram-5GNKFQAL-CCzsYDmr.js} +1 -1
  20. package/dist-web/assets/{diagram-KO2AKTUF-CRkShN2W.js → diagram-KO2AKTUF-QSff7J_V.js} +1 -1
  21. package/dist-web/assets/{diagram-LMA3HP47-Bxah1b5X.js → diagram-LMA3HP47-CVqlEmu2.js} +1 -1
  22. package/dist-web/assets/{diagram-OG6HWLK6-D9gsqeVI.js → diagram-OG6HWLK6-dBVz-I46.js} +1 -1
  23. package/dist-web/assets/{erDiagram-TEJ5UH35-BxuqpCGH.js → erDiagram-TEJ5UH35-B_y4ftNu.js} +1 -1
  24. package/dist-web/assets/{flowDiagram-I6XJVG4X-CVc346ab.js → flowDiagram-I6XJVG4X-M410hT_n.js} +1 -1
  25. package/dist-web/assets/{ganttDiagram-6RSMTGT7-CoV2q8mu.js → ganttDiagram-6RSMTGT7-qiri2OJa.js} +1 -1
  26. package/dist-web/assets/{gitGraphDiagram-PVQCEYII-DpBtbvUQ.js → gitGraphDiagram-PVQCEYII-FqGGBobG.js} +1 -1
  27. package/dist-web/assets/{index-qiGGVGhU.js → index-1SV04K_c.js} +1 -1
  28. package/dist-web/assets/{index-DXAUzXLU.js → index-BaFSLlgl.js} +1 -1
  29. package/dist-web/assets/{index-B5AkN942.js → index-DrUIu0u6.js} +4 -4
  30. package/dist-web/assets/{infoDiagram-5YYISTIA-Cs1LaltR.js → infoDiagram-5YYISTIA-CczHqRr1.js} +1 -1
  31. package/dist-web/assets/{ishikawaDiagram-YF4QCWOH-2dwQITAJ.js → ishikawaDiagram-YF4QCWOH-ty1DRy6n.js} +1 -1
  32. package/dist-web/assets/{journeyDiagram-JHISSGLW-C5NXwRcO.js → journeyDiagram-JHISSGLW-D_JYvt6i.js} +1 -1
  33. package/dist-web/assets/{kanban-definition-UN3LZRKU-D8L4UiFp.js → kanban-definition-UN3LZRKU-CT67nUTK.js} +1 -1
  34. package/dist-web/assets/{linear-BmwJXk2t.js → linear-B1HaE_t1.js} +1 -1
  35. package/dist-web/assets/{mermaid.core-CxfmFJso.js → mermaid.core-Dumx_iKV.js} +4 -4
  36. package/dist-web/assets/{mindmap-definition-RKZ34NQL-CCk10yMr.js → mindmap-definition-RKZ34NQL-CVulJo63.js} +1 -1
  37. package/dist-web/assets/{pieDiagram-4H26LBE5-CNaROrHh.js → pieDiagram-4H26LBE5-C1yD5ntZ.js} +1 -1
  38. package/dist-web/assets/{quadrantDiagram-W4KKPZXB-C9eBwT_4.js → quadrantDiagram-W4KKPZXB-B7PoiM8c.js} +1 -1
  39. package/dist-web/assets/{requirementDiagram-4Y6WPE33-CuWFUe-Z.js → requirementDiagram-4Y6WPE33-COvHb3Mv.js} +1 -1
  40. package/dist-web/assets/{sankeyDiagram-5OEKKPKP-xGYYGBDj.js → sankeyDiagram-5OEKKPKP-CAnwm5-J.js} +1 -1
  41. package/dist-web/assets/{sequenceDiagram-3UESZ5HK-Corn4mP3.js → sequenceDiagram-3UESZ5HK-C2KFLIUq.js} +1 -1
  42. package/dist-web/assets/{stateDiagram-AJRCARHV-DP1EwgDv.js → stateDiagram-AJRCARHV-CqmhS5P6.js} +1 -1
  43. package/dist-web/assets/stateDiagram-v2-BHNVJYJU-C3Kw8ZDR.js +1 -0
  44. package/dist-web/assets/{timeline-definition-PNZ67QCA-DgVY9v5q.js → timeline-definition-PNZ67QCA-DvPre9M_.js} +1 -1
  45. package/dist-web/assets/{vennDiagram-CIIHVFJN-B47n0Ab2.js → vennDiagram-CIIHVFJN-Top6i7KH.js} +1 -1
  46. package/dist-web/assets/{wardley-L42UT6IY-ubK4o7Fl.js → wardley-L42UT6IY-_jVLsWqf.js} +1 -1
  47. package/dist-web/assets/{wardleyDiagram-YWT4CUSO-ChirN_61.js → wardleyDiagram-YWT4CUSO-BV4e5mP6.js} +1 -1
  48. package/dist-web/assets/{xychartDiagram-2RQKCTM6-Cf8q2_P7.js → xychartDiagram-2RQKCTM6-Bn1hw4Jk.js} +1 -1
  49. package/dist-web/index.html +1 -1
  50. package/package.json +1 -1
  51. package/templates/harness/skills/st-code-review/SKILL.md +47 -281
  52. package/templates/harness/skills/st-code-review/scripts/code-review.cjs +70 -300
  53. package/templates/harness/skills/st-execute-blueprint/SKILL.md +15 -18
  54. package/templates/harness/skills/st-full-workflow/SKILL.md +15 -18
  55. package/templates/strikethroo/config/hooks/CODE_REVIEW.md +19 -29
  56. package/dist-web/assets/channel-DmY2Krrw.js +0 -1
  57. package/dist-web/assets/classDiagram-4FO5ZUOK-C91aiCZF.js +0 -1
  58. package/dist-web/assets/classDiagram-v2-Q7XG4LA2-C91aiCZF.js +0 -1
  59. package/dist-web/assets/stateDiagram-v2-BHNVJYJU-DQIao4rH.js +0 -1
@@ -34,20 +34,16 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
34
34
  // src/skill-scripts/code-review.ts
35
35
  var code_review_exports = {};
36
36
  __export(code_review_exports, {
37
- MAX_REVIEW_ROUNDS: () => MAX_REVIEW_ROUNDS,
38
- _decideRound: () => _decideRound,
39
37
  _exitCodeFor: () => _exitCodeFor,
40
38
  _extractReviewDocument: () => _extractReviewDocument,
41
39
  _makeDeliveryToken: () => _makeDeliveryToken,
42
40
  _readBaseCommit: () => _readBaseCommit,
43
41
  _readCumulativeDiff: () => _readCumulativeDiff,
44
- _readPriorAdjudicated: () => _readPriorAdjudicated,
42
+ _verdictFor: () => _verdictFor,
45
43
  buildReviewerPrompt: () => buildReviewerPrompt,
46
44
  createFindingsGate: () => createFindingsGate,
47
45
  main: () => main,
48
- parseReviewMandate: () => parseReviewMandate,
49
- runBoundedReviewRound: () => runBoundedReviewRound,
50
- runReviewRound: () => runReviewRound
46
+ runReview: () => runReview
51
47
  });
52
48
  module.exports = __toCommonJS(code_review_exports);
53
49
  var crypto = __toESM(require("crypto"));
@@ -730,61 +726,11 @@ var discoverHarnesses = async (request, overrides = {}) => {
730
726
 
731
727
  // src/skill-scripts/shared/review-findings.ts
732
728
  var import_child_process4 = require("child_process");
733
- var SEVERITY_RANK = { critical: 4, major: 3, minor: 2, info: 1 };
734
- var CONFIDENCE_RANK = { high: 3, medium: 2, low: 1 };
735
- var MAX_REVIEW_ROUNDS = 3;
736
- var DEFAULT_SEVERITY_FLOOR = "major";
737
- var DEFAULT_CONFIDENCE_FLOOR = "high";
729
+ var SEVERITIES = ["critical", "major", "minor", "info"];
730
+ var CONFIDENCES = ["high", "medium", "low"];
738
731
  var XMLLINT_TIMEOUT_MS = 3e4;
739
- var isSeverity = (value) => value in SEVERITY_RANK;
740
- var isConfidence = (value) => value in CONFIDENCE_RANK;
741
- var SEVERITY_FLOOR_RE = /^[ \t]*#{0,6}[ \t]*severity floor[ \t]*:[ \t]*`?([a-z]+)`?/im;
742
- var CONFIDENCE_FLOOR_RE = /^[ \t]*#{0,6}[ \t]*confidence floor[ \t]*:[ \t]*`?([a-z]+)`?/im;
743
- var ROUND_BUDGET_RE = /^[ \t]*#{0,6}[ \t]*round budget[ \t]*:[ \t]*`?(-?\d{1,9})`?/im;
744
- var parseReviewMandate = (hookContent) => {
745
- const notes = [];
746
- const severityMatch = SEVERITY_FLOOR_RE.exec(hookContent);
747
- const statedSeverity = (severityMatch?.[1] ?? "").toLowerCase();
748
- let severityFloor = DEFAULT_SEVERITY_FLOOR;
749
- if (isSeverity(statedSeverity)) {
750
- severityFloor = statedSeverity;
751
- } else {
752
- notes.push(
753
- `The hook states no recognised severity floor, so the compiled default \`${DEFAULT_SEVERITY_FLOOR}\` applies.`
754
- );
755
- }
756
- const confidenceMatch = CONFIDENCE_FLOOR_RE.exec(hookContent);
757
- const statedConfidence = (confidenceMatch?.[1] ?? "").toLowerCase();
758
- let confidenceFloor = DEFAULT_CONFIDENCE_FLOOR;
759
- if (isConfidence(statedConfidence)) {
760
- confidenceFloor = statedConfidence;
761
- } else {
762
- notes.push(
763
- `The hook states no recognised confidence floor, so the compiled default \`${DEFAULT_CONFIDENCE_FLOOR}\` applies.`
764
- );
765
- }
766
- const budgetMatch = ROUND_BUDGET_RE.exec(hookContent);
767
- let roundBudget = MAX_REVIEW_ROUNDS;
768
- if (budgetMatch === null) {
769
- notes.push(
770
- `The hook states no round budget, so the compiled ceiling of ${MAX_REVIEW_ROUNDS} rounds applies.`
771
- );
772
- } else {
773
- const stated = Number(budgetMatch[1]);
774
- if (!Number.isInteger(stated) || stated < 1) {
775
- notes.push(
776
- `The hook states a round budget of "${budgetMatch[1]}", which is not a positive whole number of rounds, so the compiled ceiling of ${MAX_REVIEW_ROUNDS} applies.`
777
- );
778
- } else if (stated > MAX_REVIEW_ROUNDS) {
779
- notes.push(
780
- `The hook states a round budget of ${stated}, above the compiled ceiling of ${MAX_REVIEW_ROUNDS}. The ceiling is enforced in code and cannot be raised by editing the hook, so ${MAX_REVIEW_ROUNDS} rounds apply.`
781
- );
782
- } else {
783
- roundBudget = stated;
784
- }
785
- }
786
- return { severityFloor, confidenceFloor, roundBudget, notes };
787
- };
732
+ var isSeverity = (value) => SEVERITIES.includes(value);
733
+ var isConfidence = (value) => CONFIDENCES.includes(value);
788
734
  var validateAgainstSchema = (xsdFile, xmlFile, timeoutMs = XMLLINT_TIMEOUT_MS) => new Promise((resolve3) => {
789
735
  let settled = false;
790
736
  let diagnostics = "";
@@ -943,12 +889,11 @@ var parseReviewFindings = (xml) => {
943
889
  comment = {
944
890
  file: file ?? "",
945
891
  location: lineRange(attributes),
946
- // Absent, empty, and unrecognised all become null, and null falls below
947
- // every floor. This is the one place the fail-safe default lives.
892
+ // Absent, empty, and unrecognised all become null. The label is
893
+ // advisory, so an unreadable one is dropped rather than guessed at.
948
894
  severity: isSeverity(severity) ? severity : null,
949
895
  confidence: isConfidence(confidence) ? confidence : null,
950
896
  category: null,
951
- hasSuggestion: false,
952
897
  summary: ""
953
898
  };
954
899
  capture = null;
@@ -965,55 +910,24 @@ var parseReviewFindings = (xml) => {
965
910
  } else if (name === "category") {
966
911
  capture = "category";
967
912
  buffer = "";
968
- } else if (name === "suggestion") {
969
- comment.hasSuggestion = true;
970
913
  }
971
914
  }
972
915
  return findings;
973
916
  };
974
- var partitionFindings = (findings, severityFloor, confidenceFloor) => {
975
- const actionable = [];
976
- const recorded = [];
977
- let aboveFloor = 0;
978
- let aboveFloorWithoutSuggestion = 0;
917
+ var countFindings = (findings) => {
918
+ const counts = {
919
+ total: findings.length,
920
+ critical: 0,
921
+ major: 0,
922
+ minor: 0,
923
+ info: 0,
924
+ unlabelled: 0
925
+ };
979
926
  for (const finding of findings) {
980
- const reasons = [];
981
- if (finding.severity === null) {
982
- reasons.push("severity-absent");
983
- } else if (SEVERITY_RANK[finding.severity] < SEVERITY_RANK[severityFloor]) {
984
- reasons.push("severity-below-floor");
985
- }
986
- if (finding.confidence === null) {
987
- reasons.push("confidence-absent");
988
- } else if (CONFIDENCE_RANK[finding.confidence] < CONFIDENCE_RANK[confidenceFloor]) {
989
- reasons.push("confidence-below-floor");
990
- }
991
- const clearsFloors = reasons.length === 0;
992
- if (clearsFloors) aboveFloor += 1;
993
- if (!finding.hasSuggestion) {
994
- reasons.push("no-suggestion");
995
- if (clearsFloors) aboveFloorWithoutSuggestion += 1;
996
- }
997
- if (reasons.length === 0) {
998
- actionable.push(finding);
999
- } else {
1000
- recorded.push({ ...finding, reasons });
1001
- }
927
+ if (finding.severity === null) counts.unlabelled += 1;
928
+ else counts[finding.severity] += 1;
1002
929
  }
1003
- return {
1004
- severityFloor,
1005
- confidenceFloor,
1006
- actionable,
1007
- recorded,
1008
- counts: {
1009
- total: findings.length,
1010
- aboveFloor,
1011
- belowFloor: findings.length - aboveFloor,
1012
- actionable: actionable.length,
1013
- recorded: recorded.length,
1014
- aboveFloorWithoutSuggestion
1015
- }
1016
- };
930
+ return counts;
1017
931
  };
1018
932
 
1019
933
  // src/skill-scripts/code-review.ts
@@ -1033,43 +947,37 @@ var readFileOrNull = (filePath) => {
1033
947
  return null;
1034
948
  }
1035
949
  };
1036
- var writeTranscript = (roundDir, transcript) => {
950
+ var writeTranscript = (reviewDir, transcript) => {
1037
951
  if (transcript === void 0 || transcript === "") return;
1038
952
  try {
1039
- fs6.mkdirSync(roundDir, { recursive: true });
1040
- fs6.writeFileSync(path6.join(roundDir, TRANSCRIPT_FILE_NAME), transcript, "utf8");
953
+ fs6.mkdirSync(reviewDir, { recursive: true });
954
+ fs6.writeFileSync(path6.join(reviewDir, TRANSCRIPT_FILE_NAME), transcript, "utf8");
1041
955
  } catch {
1042
956
  }
1043
957
  };
1044
- var createFindingsGate = (mandate) => async (context) => {
1045
- const roundDir = path6.dirname(context.reviewFile);
1046
- const findingsFile = path6.join(roundDir, FINDINGS_FILE_NAME);
958
+ var createFindingsGate = () => async (context) => {
959
+ const reviewDir = path6.dirname(context.reviewFile);
960
+ const findingsFile = path6.join(reviewDir, FINDINGS_FILE_NAME);
1047
961
  const record = (payload) => {
1048
962
  try {
1049
- fs6.mkdirSync(roundDir, { recursive: true });
963
+ fs6.mkdirSync(reviewDir, { recursive: true });
1050
964
  fs6.writeFileSync(findingsFile, `${JSON.stringify(payload, null, 2)}
1051
965
  `, "utf8");
1052
966
  } catch (error) {
1053
967
  throw new Error(
1054
- `The review round's findings partition could not be written to ${findingsFile}: ${errorMessage2(error)}`
968
+ `The review findings could not be written to ${findingsFile}: ${errorMessage2(error)}`
1055
969
  );
1056
970
  }
1057
971
  };
1058
- const base = {
1059
- round: context.round,
1060
- reviewFile: context.reviewFile,
1061
- xsdFile: context.xsdFile,
1062
- severityFloor: mandate.severityFloor,
1063
- confidenceFloor: mandate.confidenceFloor
1064
- };
972
+ const base = { reviewFile: context.reviewFile, xsdFile: context.xsdFile };
1065
973
  const delivered = _extractReviewDocument(context.reviewerStdout, context.deliveryToken);
1066
974
  if (delivered === null) {
1067
975
  const detail = "The reviewer printed no complete findings document between this dispatch's delimiters. A round with no findings document cannot be read as a round with no findings.";
1068
- record({ ...base, status: "findings-absent", detail, actionable: [], recorded: [] });
976
+ record({ ...base, status: "findings-absent", detail, findings: [] });
1069
977
  return { kind: "findings-absent", detail };
1070
978
  }
1071
979
  try {
1072
- fs6.mkdirSync(roundDir, { recursive: true });
980
+ fs6.mkdirSync(reviewDir, { recursive: true });
1073
981
  fs6.writeFileSync(
1074
982
  context.reviewFile,
1075
983
  delivered.endsWith("\n") ? delivered : `${delivered}
@@ -1087,46 +995,25 @@ var createFindingsGate = (mandate) => async (context) => {
1087
995
  ...base,
1088
996
  status: "validator-unavailable",
1089
997
  detail: validation.detail,
1090
- actionable: [],
1091
- recorded: []
998
+ findings: []
1092
999
  });
1093
1000
  return { kind: "validator-unavailable", detail: validation.detail };
1094
1001
  }
1095
1002
  if (validation.kind === "invalid") {
1096
- const detail = `${context.reviewFile} does not validate against ${context.xsdFile}, so its findings were not thresholded and none of them was applied. xmllint reported: ${validation.detail}`;
1097
- record({ ...base, status: "schema-invalid", detail, actionable: [], recorded: [] });
1003
+ const detail = `${context.reviewFile} does not validate against ${context.xsdFile}, so its findings could not be certified and none of them was recorded. xmllint reported: ${validation.detail}`;
1004
+ record({ ...base, status: "schema-invalid", detail, findings: [] });
1098
1005
  return { kind: "schema-invalid", detail };
1099
1006
  }
1100
1007
  const xml = readFileOrNull(context.reviewFile);
1101
1008
  if (xml === null) {
1102
1009
  const detail = `${context.reviewFile} validated but could not then be read.`;
1103
- record({ ...base, status: "findings-absent", detail, actionable: [], recorded: [] });
1010
+ record({ ...base, status: "findings-absent", detail, findings: [] });
1104
1011
  return { kind: "findings-absent", detail };
1105
1012
  }
1106
- const partition = partitionFindings(
1107
- parseReviewFindings(xml),
1108
- mandate.severityFloor,
1109
- mandate.confidenceFloor
1110
- );
1111
- record({
1112
- ...base,
1113
- status: "evaluated",
1114
- counts: partition.counts,
1115
- actionable: partition.actionable,
1116
- recorded: partition.recorded
1117
- });
1118
- return {
1119
- kind: "evaluated",
1120
- aboveFloor: partition.counts.aboveFloor,
1121
- belowFloor: partition.counts.belowFloor,
1122
- actionable: partition.counts.actionable,
1123
- recorded: partition.counts.recorded,
1124
- total: partition.counts.total,
1125
- aboveFloorWithoutSuggestion: partition.counts.aboveFloorWithoutSuggestion,
1126
- severityFloor: mandate.severityFloor,
1127
- confidenceFloor: mandate.confidenceFloor,
1128
- findingsFile
1129
- };
1013
+ const findings = parseReviewFindings(xml);
1014
+ const counts = countFindings(findings);
1015
+ record({ ...base, status: "evaluated", counts, findings });
1016
+ return { kind: "evaluated", counts, findingsFile };
1130
1017
  };
1131
1018
  var _readBaseCommit = (filePath) => {
1132
1019
  const raw = readFileOrNull(filePath);
@@ -1198,19 +1085,6 @@ var readReviewerSkill = () => {
1198
1085
  const content = readFileOrNull(skillFile);
1199
1086
  return content === null ? "Load the `st-code-review` skill and follow its Operating Procedure. If that skill is not installed on this harness, follow the mandate below exactly." : content;
1200
1087
  };
1201
- var renderAdjudicated = (findings) => {
1202
- if (findings.length === 0) {
1203
- return "None. This is the first round, or no earlier finding has been ruled on.";
1204
- }
1205
- return findings.map((finding) => {
1206
- const attributes = [
1207
- finding.severity === void 0 ? null : `severity=${finding.severity}`,
1208
- finding.confidence === void 0 ? null : `confidence=${finding.confidence}`
1209
- ].filter((part) => part !== null).join(" ");
1210
- const where = finding.location === void 0 ? finding.file : `${finding.file}:${finding.location}`;
1211
- return `- [${finding.disposition}] ${where}${attributes ? ` (${attributes})` : ""} \u2014 ${finding.summary}`;
1212
- }).join("\n");
1213
- };
1214
1088
  var _makeDeliveryToken = () => crypto.randomBytes(6).toString("hex");
1215
1089
  var beginMarker = (token) => `<<<BEGIN REVIEW XML ${token}>>>`;
1216
1090
  var endMarker = (token) => `<<<END REVIEW XML ${token}>>>`;
@@ -1232,7 +1106,7 @@ var _extractReviewDocument = (stdout, token) => {
1232
1106
  return null;
1233
1107
  };
1234
1108
  var buildReviewerPrompt = (input) => [
1235
- `Strikethroo code review gate \u2014 Plan ${input.planId}, review round ${input.round}.`,
1109
+ `Strikethroo code review gate \u2014 Plan ${input.planId}.`,
1236
1110
  "",
1237
1111
  "You are the independent reviewer, running on a different harness than the one",
1238
1112
  "that wrote this code. You detect; you never fix. Do not edit, create, or delete",
@@ -1245,7 +1119,6 @@ var buildReviewerPrompt = (input) => [
1245
1119
  `Review mandate hook: ${input.hookFile}`,
1246
1120
  `Findings schema to validate against: ${input.xsdFile}`,
1247
1121
  `Base commit anchoring this plan's scope: ${input.baseCommit}`,
1248
- `Round: ${input.round}`,
1249
1122
  "",
1250
1123
  "## How to deliver your findings",
1251
1124
  "",
@@ -1264,9 +1137,9 @@ var buildReviewerPrompt = (input) => [
1264
1137
  "Copy those two lines from this dispatch; never invent a token. Print nothing after",
1265
1138
  "the closing line. Do not write the document to a file \u2014 this channel is the only",
1266
1139
  "one that is read. The document is validated against the schema named above, so an",
1267
- "incomplete or invented document fails the round. Being unable to read the",
1140
+ "incomplete or invented document fails the review. Being unable to read the",
1268
1141
  "repository is not a reason to emit this block: a review you could not perform is a",
1269
- "failed round, and emitting well-formed XML instead of reporting that failure is a",
1142
+ "failed review, and emitting well-formed XML instead of reporting that failure is a",
1270
1143
  "worse outcome than the failure.",
1271
1144
  "",
1272
1145
  "## Review mandate (authoritative \u2014 it overrides the reviewer instructions below)",
@@ -1277,10 +1150,6 @@ var buildReviewerPrompt = (input) => [
1277
1150
  "",
1278
1151
  input.skillInstructions.trim(),
1279
1152
  "",
1280
- "## Prior adjudicated findings \u2014 do not re-litigate these",
1281
- "",
1282
- renderAdjudicated(input.adjudicatedFindings),
1283
- "",
1284
1153
  "## Cumulative diff",
1285
1154
  "",
1286
1155
  `Produced with \`git diff ${input.baseCommit} --\` in ${input.workspace}: the recorded`,
@@ -1292,48 +1161,6 @@ ${input.diff}
1292
1161
  <<<END CUMULATIVE DIFF>>>`,
1293
1162
  ""
1294
1163
  ].join("\n");
1295
- var asAdjudicated = (value, disposition) => {
1296
- if (!Array.isArray(value)) return [];
1297
- const adjudicated = [];
1298
- for (const entry of value) {
1299
- if (entry === null || typeof entry !== "object") continue;
1300
- const finding = entry;
1301
- if (typeof finding["file"] !== "string") continue;
1302
- adjudicated.push({
1303
- file: finding["file"],
1304
- ...typeof finding["location"] === "string" ? { location: finding["location"] } : {},
1305
- ...typeof finding["severity"] === "string" ? { severity: finding["severity"] } : {},
1306
- ...typeof finding["confidence"] === "string" ? { confidence: finding["confidence"] } : {},
1307
- summary: typeof finding["summary"] === "string" ? finding["summary"] : "",
1308
- disposition
1309
- });
1310
- }
1311
- return adjudicated;
1312
- };
1313
- var _readPriorAdjudicated = (planDir, round) => {
1314
- const carried = /* @__PURE__ */ new Map();
1315
- for (let prior = 1; prior < round; prior += 1) {
1316
- const raw = readFileOrNull(
1317
- path6.join(planDir, REVIEW_DIR_NAME, `round-${prior}`, FINDINGS_FILE_NAME)
1318
- );
1319
- if (raw === null) continue;
1320
- try {
1321
- const parsed = JSON.parse(raw);
1322
- if (parsed === null || typeof parsed !== "object") continue;
1323
- const partition = parsed;
1324
- const roundFindings = [
1325
- ...asAdjudicated(partition["actionable"], "applied"),
1326
- ...asAdjudicated(partition["recorded"], "recorded-below-floor")
1327
- ];
1328
- for (const finding of roundFindings) {
1329
- carried.set(`${finding.file}|${finding.location ?? ""}|${finding.summary}`, finding);
1330
- }
1331
- } catch {
1332
- continue;
1333
- }
1334
- }
1335
- return [...carried.values()];
1336
- };
1337
1164
  var skip = (reason, detail) => ({
1338
1165
  kind: "skipped",
1339
1166
  reason,
@@ -1396,17 +1223,16 @@ var resolveReviewContext = (startPath, validatorAvailable = () => executableOnPa
1396
1223
  workspace: path6.dirname(path6.dirname(strikethrooRoot)),
1397
1224
  hookFile,
1398
1225
  hookContent,
1399
- xsdFile,
1400
- mandate: parseReviewMandate(hookContent)
1226
+ xsdFile
1401
1227
  }
1402
1228
  };
1403
1229
  };
1404
- var runReviewRound = async (request, overrides = {}) => {
1230
+ var runReview = async (request, overrides = {}) => {
1405
1231
  const dependencies2 = { ...defaultDependencies2, ...overrides };
1406
1232
  const startPath = request.startPath ?? process.cwd();
1407
1233
  const resolution = resolveReviewContext(startPath, dependencies2.validatorAvailable);
1408
1234
  if (resolution.kind === "ended") return resolution.result;
1409
- const { strikethrooRoot, workspace, hookFile, hookContent, xsdFile, mandate } = resolution.context;
1235
+ const { strikethrooRoot, workspace, hookFile, hookContent, xsdFile } = resolution.context;
1410
1236
  const resolved = resolvePlan(request.plan, startPath);
1411
1237
  if (!resolved) {
1412
1238
  return {
@@ -1448,24 +1274,24 @@ var runReviewRound = async (request, overrides = {}) => {
1448
1274
  `The diff from ${baseCommit} to the working tree in ${workspace} is empty, so there was nothing to review. No reviewer was dispatched, and no round was certified.`
1449
1275
  );
1450
1276
  }
1451
- const roundDir = path6.join(planDir, REVIEW_DIR_NAME, `round-${request.round}`);
1452
- const reviewFile = path6.join(roundDir, REVIEW_FILE_NAME);
1277
+ const reviewDir = path6.join(planDir, REVIEW_DIR_NAME);
1278
+ const reviewFile = path6.join(reviewDir, REVIEW_FILE_NAME);
1453
1279
  try {
1454
- fs6.mkdirSync(roundDir, { recursive: true });
1280
+ fs6.mkdirSync(reviewDir, { recursive: true });
1455
1281
  } catch (error) {
1456
1282
  return {
1457
1283
  kind: "infrastructure-failure",
1458
- detail: `Could not create the review round directory ${roundDir}: ${errorMessage2(error)}`
1284
+ detail: `Could not create the review directory ${reviewDir}: ${errorMessage2(error)}`
1459
1285
  };
1460
1286
  }
1461
- const staleArtifacts = [reviewFile, path6.join(roundDir, TRANSCRIPT_FILE_NAME)];
1287
+ const staleArtifacts = [reviewFile, path6.join(reviewDir, TRANSCRIPT_FILE_NAME)];
1462
1288
  for (const stale of staleArtifacts) {
1463
1289
  try {
1464
1290
  fs6.rmSync(stale, { force: true });
1465
1291
  } catch (error) {
1466
1292
  return {
1467
1293
  kind: "infrastructure-failure",
1468
- detail: `Could not remove the stale round artifact ${stale}: ${errorMessage2(error)}`
1294
+ detail: `Could not remove the stale review artifact ${stale}: ${errorMessage2(error)}`
1469
1295
  };
1470
1296
  }
1471
1297
  }
@@ -1479,9 +1305,7 @@ var runReviewRound = async (request, overrides = {}) => {
1479
1305
  hookContent,
1480
1306
  xsdFile,
1481
1307
  baseCommit,
1482
- round: request.round,
1483
1308
  diff,
1484
- adjudicatedFindings: request.adjudicatedFindings ?? _readPriorAdjudicated(planDir, request.round),
1485
1309
  skillInstructions: readReviewerSkill(),
1486
1310
  deliveryToken
1487
1311
  });
@@ -1493,95 +1317,56 @@ var runReviewRound = async (request, overrides = {}) => {
1493
1317
  return {
1494
1318
  kind: "fallback",
1495
1319
  harness,
1496
- round: request.round,
1497
1320
  reason: dispatched.reason,
1498
1321
  detail: dispatched.detail
1499
1322
  };
1500
1323
  }
1501
1324
  if (dispatched.kind === "launched-failure") {
1502
- writeTranscript(roundDir, dispatched.stdout);
1325
+ writeTranscript(reviewDir, dispatched.stdout);
1503
1326
  return {
1504
1327
  kind: "launched-failure",
1505
1328
  harness,
1506
- round: request.round,
1507
1329
  reviewFile,
1508
1330
  exitCode: dispatched.exitCode,
1509
1331
  detail: `The ${harness} reviewer exited ${dispatched.exitCode}.`
1510
1332
  };
1511
1333
  }
1512
- const evaluate = dependencies2.evaluateFindings ?? createFindingsGate(mandate);
1334
+ const evaluate = dependencies2.evaluateFindings ?? createFindingsGate();
1513
1335
  const findingsGate = await evaluate({
1514
1336
  reviewFile,
1515
1337
  xsdFile,
1516
1338
  planDir,
1517
- round: request.round,
1518
1339
  reviewerStdout: dispatched.stdout ?? "",
1519
1340
  deliveryToken
1520
1341
  });
1521
1342
  if (findingsGate.kind !== "evaluated") {
1522
- writeTranscript(roundDir, dispatched.stdout);
1343
+ writeTranscript(reviewDir, dispatched.stdout);
1523
1344
  }
1524
1345
  return {
1525
1346
  kind: "reviewed",
1526
1347
  harness,
1527
- round: request.round,
1528
1348
  baseCommit,
1529
1349
  reviewFile,
1530
1350
  reviewFilePresent: fs6.existsSync(reviewFile),
1351
+ verdict: _verdictFor(findingsGate),
1531
1352
  findingsGate
1532
1353
  };
1533
1354
  };
1534
- var _decideRound = (outcome, round, roundBudget) => {
1355
+ var _verdictFor = (outcome) => {
1535
1356
  if (outcome.kind !== "evaluated") {
1536
- return { kind: "round-failed", detail: outcome.detail };
1537
- }
1538
- const recorded = `${outcome.recorded} finding(s) were recorded without being applied, of which ${outcome.aboveFloorWithoutSuggestion} cleared both floors but carried no local fix. See ${outcome.findingsFile}.`;
1539
- if (outcome.actionable === 0) {
1540
- return {
1541
- kind: "gate-passed",
1542
- detail: `No finding cleared the \`${outcome.severityFloor}\` severity floor and the \`${outcome.confidenceFloor}\` confidence floor with a local fix attached, so the review gate passed on round ${round}. ${recorded}`
1543
- };
1357
+ return { kind: "review-failed", detail: outcome.detail };
1544
1358
  }
1545
- if (round >= roundBudget) {
1359
+ const { counts, findingsFile } = outcome;
1360
+ if (counts.total === 0) {
1546
1361
  return {
1547
- kind: "budget-exhausted",
1548
- actionable: outcome.actionable,
1549
- detail: `Round ${round} of an enforced ${roundBudget}-round budget still reports ${outcome.actionable} actionable finding(s), so the review gate halts with the budget exhausted. The plan stays in \`plans/\` and every round's findings are recorded under its \`review/\` directory. ${recorded}`
1362
+ kind: "review-recorded",
1363
+ detail: `The reviewer raised no findings. See ${findingsFile}.`
1550
1364
  };
1551
1365
  }
1366
+ const byLabel = SEVERITIES.filter((label) => counts[label] > 0).map((label) => `${counts[label]} ${label}`).concat(counts.unlabelled > 0 ? [`${counts.unlabelled} unlabelled`] : []).join(", ");
1552
1367
  return {
1553
- kind: "fix-and-continue",
1554
- nextRound: round + 1,
1555
- actionable: outcome.actionable,
1556
- detail: `${outcome.actionable} finding(s) clear both floors and carry a local fix. Dispatch them on the implementer route, re-run POST_EXECUTION in full, then run round ${round + 1} of ${roundBudget}. ${recorded}`
1557
- };
1558
- };
1559
- var runBoundedReviewRound = async (request, overrides = {}) => {
1560
- const startPath = request.startPath ?? process.cwd();
1561
- const resolution = resolveReviewContext(
1562
- startPath,
1563
- { ...defaultDependencies2, ...overrides }.validatorAvailable
1564
- );
1565
- if (resolution.kind === "ended") return resolution.result;
1566
- const { mandate } = resolution.context;
1567
- if (request.round > mandate.roundBudget) {
1568
- return {
1569
- kind: "budget-exhausted",
1570
- round: request.round,
1571
- roundBudget: mandate.roundBudget,
1572
- roundCeiling: MAX_REVIEW_ROUNDS,
1573
- mandateNotes: mandate.notes,
1574
- detail: `Round ${request.round} was requested, but the review gate enforces a ${mandate.roundBudget}-round budget (compiled ceiling ${MAX_REVIEW_ROUNDS}). No reviewer was dispatched. The plan stays in \`plans/\` and the rounds already run are recorded under its \`review/\` directory.`
1575
- };
1576
- }
1577
- const result = await runReviewRound(request, overrides);
1578
- if (result.kind !== "reviewed") return result;
1579
- return {
1580
- ...result,
1581
- decision: _decideRound(result.findingsGate, request.round, mandate.roundBudget),
1582
- roundBudget: mandate.roundBudget,
1583
- roundCeiling: MAX_REVIEW_ROUNDS,
1584
- mandateNotes: mandate.notes
1368
+ kind: "review-recorded",
1369
+ detail: `The reviewer raised ${counts.total} finding(s) (${byLabel}). They are recorded, not applied: read them and decide which to act on. See ${findingsFile}.`
1585
1370
  };
1586
1371
  };
1587
1372
  var emit = (result, exitCode) => {
@@ -1592,34 +1377,23 @@ var emit = (result, exitCode) => {
1592
1377
  var _exitCodeFor = (result) => {
1593
1378
  if (result.kind === "infrastructure-failure") return 2;
1594
1379
  if (result.kind === "launched-failure") return 1;
1595
- if (result.kind === "budget-exhausted") return 1;
1596
- if (result.kind === "reviewed" && result.decision !== void 0) {
1597
- return result.decision.kind === "budget-exhausted" || result.decision.kind === "round-failed" ? 1 : 0;
1598
- }
1380
+ if (result.kind === "reviewed") return result.verdict.kind === "review-failed" ? 1 : 0;
1599
1381
  return 0;
1600
1382
  };
1601
1383
  var main = async (startPath = process.cwd()) => {
1602
- const [planArg, harnessArg, roundArg] = process.argv.slice(2);
1384
+ const [planArg, harnessArg] = process.argv.slice(2);
1603
1385
  if (!planArg || !harnessArg || !SUPPORTED_HARNESSES.includes(harnessArg)) {
1604
1386
  emit(
1605
1387
  {
1606
1388
  kind: "infrastructure-failure",
1607
- detail: `Usage: code-review.cjs <plan-id-or-path> <current-harness> [round]. <current-harness> is one of: ${SUPPORTED_HARNESSES.join(", ")}.`
1389
+ detail: `Usage: code-review.cjs <plan-id-or-path> <current-harness>. <current-harness> is one of: ${SUPPORTED_HARNESSES.join(", ")}.`
1608
1390
  },
1609
1391
  2
1610
1392
  );
1611
1393
  }
1612
- const round = roundArg === void 0 ? 1 : Number(roundArg);
1613
- if (!Number.isInteger(round) || round < 1) {
1614
- emit(
1615
- { kind: "infrastructure-failure", detail: `Round "${roundArg}" is not a positive integer.` },
1616
- 2
1617
- );
1618
- }
1619
- const result = await runBoundedReviewRound({
1394
+ const result = await runReview({
1620
1395
  plan: planArg,
1621
1396
  currentHarness: harnessArg,
1622
- round,
1623
1397
  startPath
1624
1398
  });
1625
1399
  emit(result, _exitCodeFor(result));
@@ -1637,18 +1411,14 @@ if (require.main === module) {
1637
1411
  }
1638
1412
  // Annotate the CommonJS export names for ESM import in node:
1639
1413
  0 && (module.exports = {
1640
- MAX_REVIEW_ROUNDS,
1641
- _decideRound,
1642
1414
  _exitCodeFor,
1643
1415
  _extractReviewDocument,
1644
1416
  _makeDeliveryToken,
1645
1417
  _readBaseCommit,
1646
1418
  _readCumulativeDiff,
1647
- _readPriorAdjudicated,
1419
+ _verdictFor,
1648
1420
  buildReviewerPrompt,
1649
1421
  createFindingsGate,
1650
1422
  main,
1651
- parseReviewMandate,
1652
- runBoundedReviewRound,
1653
- runReviewRound
1423
+ runReview
1654
1424
  });