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.
- package/dist-web/assets/{arc-C3L1jkFY.js → arc-BlSNa2QY.js} +1 -1
- package/dist-web/assets/{architectureDiagram-3BPJPVTR-Dc5wdWPF.js → architectureDiagram-3BPJPVTR-CPcCF2a4.js} +1 -1
- package/dist-web/assets/{blockDiagram-GPEHLZMM-BCqaESV7.js → blockDiagram-GPEHLZMM-ECDR0edZ.js} +1 -1
- package/dist-web/assets/{c4Diagram-AAUBKEIU-DNecSAwM.js → c4Diagram-AAUBKEIU-oM3VnEAQ.js} +1 -1
- package/dist-web/assets/channel-CmU4mBgT.js +1 -0
- package/dist-web/assets/{chunk-2J33WTMH-3QS28848.js → chunk-2J33WTMH-DHk5SJLz.js} +1 -1
- package/dist-web/assets/{chunk-4BX2VUAB-B6R-x-SN.js → chunk-4BX2VUAB-CjBwgkMu.js} +1 -1
- package/dist-web/assets/{chunk-55IACEB6-BYY5vsbu.js → chunk-55IACEB6-D95zqyiV.js} +1 -1
- package/dist-web/assets/{chunk-727SXJPM-BZxIJIkN.js → chunk-727SXJPM-DCCPRUfv.js} +1 -1
- package/dist-web/assets/{chunk-AQP2D5EJ-Dwpt8x10.js → chunk-AQP2D5EJ-qur7KQyD.js} +1 -1
- package/dist-web/assets/{chunk-FMBD7UC4-BkIlqLqc.js → chunk-FMBD7UC4-D46Su7Fv.js} +1 -1
- package/dist-web/assets/{chunk-ND2GUHAM-BZAfIvWk.js → chunk-ND2GUHAM-BioGsqxt.js} +1 -1
- package/dist-web/assets/{chunk-QZHKN3VN-wAp9ruCQ.js → chunk-QZHKN3VN-C8LdDiqn.js} +1 -1
- package/dist-web/assets/classDiagram-4FO5ZUOK-D_FMKQsB.js +1 -0
- package/dist-web/assets/classDiagram-v2-Q7XG4LA2-D_FMKQsB.js +1 -0
- package/dist-web/assets/{cose-bilkent-S5V4N54A-BWhls-Li.js → cose-bilkent-S5V4N54A-ccY03BRh.js} +1 -1
- package/dist-web/assets/{dagre-BM42HDAG-D_rm5H8p.js → dagre-BM42HDAG-BYLmCViB.js} +1 -1
- package/dist-web/assets/{diagram-2AECGRRQ-CgrRz62Y.js → diagram-2AECGRRQ-DTvenvXR.js} +1 -1
- package/dist-web/assets/{diagram-5GNKFQAL-BRdPFx5y.js → diagram-5GNKFQAL-CCzsYDmr.js} +1 -1
- package/dist-web/assets/{diagram-KO2AKTUF-CRkShN2W.js → diagram-KO2AKTUF-QSff7J_V.js} +1 -1
- package/dist-web/assets/{diagram-LMA3HP47-Bxah1b5X.js → diagram-LMA3HP47-CVqlEmu2.js} +1 -1
- package/dist-web/assets/{diagram-OG6HWLK6-D9gsqeVI.js → diagram-OG6HWLK6-dBVz-I46.js} +1 -1
- package/dist-web/assets/{erDiagram-TEJ5UH35-BxuqpCGH.js → erDiagram-TEJ5UH35-B_y4ftNu.js} +1 -1
- package/dist-web/assets/{flowDiagram-I6XJVG4X-CVc346ab.js → flowDiagram-I6XJVG4X-M410hT_n.js} +1 -1
- package/dist-web/assets/{ganttDiagram-6RSMTGT7-CoV2q8mu.js → ganttDiagram-6RSMTGT7-qiri2OJa.js} +1 -1
- package/dist-web/assets/{gitGraphDiagram-PVQCEYII-DpBtbvUQ.js → gitGraphDiagram-PVQCEYII-FqGGBobG.js} +1 -1
- package/dist-web/assets/{index-qiGGVGhU.js → index-1SV04K_c.js} +1 -1
- package/dist-web/assets/{index-DXAUzXLU.js → index-BaFSLlgl.js} +1 -1
- package/dist-web/assets/{index-B5AkN942.js → index-DrUIu0u6.js} +4 -4
- package/dist-web/assets/{infoDiagram-5YYISTIA-Cs1LaltR.js → infoDiagram-5YYISTIA-CczHqRr1.js} +1 -1
- package/dist-web/assets/{ishikawaDiagram-YF4QCWOH-2dwQITAJ.js → ishikawaDiagram-YF4QCWOH-ty1DRy6n.js} +1 -1
- package/dist-web/assets/{journeyDiagram-JHISSGLW-C5NXwRcO.js → journeyDiagram-JHISSGLW-D_JYvt6i.js} +1 -1
- package/dist-web/assets/{kanban-definition-UN3LZRKU-D8L4UiFp.js → kanban-definition-UN3LZRKU-CT67nUTK.js} +1 -1
- package/dist-web/assets/{linear-BmwJXk2t.js → linear-B1HaE_t1.js} +1 -1
- package/dist-web/assets/{mermaid.core-CxfmFJso.js → mermaid.core-Dumx_iKV.js} +4 -4
- package/dist-web/assets/{mindmap-definition-RKZ34NQL-CCk10yMr.js → mindmap-definition-RKZ34NQL-CVulJo63.js} +1 -1
- package/dist-web/assets/{pieDiagram-4H26LBE5-CNaROrHh.js → pieDiagram-4H26LBE5-C1yD5ntZ.js} +1 -1
- package/dist-web/assets/{quadrantDiagram-W4KKPZXB-C9eBwT_4.js → quadrantDiagram-W4KKPZXB-B7PoiM8c.js} +1 -1
- package/dist-web/assets/{requirementDiagram-4Y6WPE33-CuWFUe-Z.js → requirementDiagram-4Y6WPE33-COvHb3Mv.js} +1 -1
- package/dist-web/assets/{sankeyDiagram-5OEKKPKP-xGYYGBDj.js → sankeyDiagram-5OEKKPKP-CAnwm5-J.js} +1 -1
- package/dist-web/assets/{sequenceDiagram-3UESZ5HK-Corn4mP3.js → sequenceDiagram-3UESZ5HK-C2KFLIUq.js} +1 -1
- package/dist-web/assets/{stateDiagram-AJRCARHV-DP1EwgDv.js → stateDiagram-AJRCARHV-CqmhS5P6.js} +1 -1
- package/dist-web/assets/stateDiagram-v2-BHNVJYJU-C3Kw8ZDR.js +1 -0
- package/dist-web/assets/{timeline-definition-PNZ67QCA-DgVY9v5q.js → timeline-definition-PNZ67QCA-DvPre9M_.js} +1 -1
- package/dist-web/assets/{vennDiagram-CIIHVFJN-B47n0Ab2.js → vennDiagram-CIIHVFJN-Top6i7KH.js} +1 -1
- package/dist-web/assets/{wardley-L42UT6IY-ubK4o7Fl.js → wardley-L42UT6IY-_jVLsWqf.js} +1 -1
- package/dist-web/assets/{wardleyDiagram-YWT4CUSO-ChirN_61.js → wardleyDiagram-YWT4CUSO-BV4e5mP6.js} +1 -1
- package/dist-web/assets/{xychartDiagram-2RQKCTM6-Cf8q2_P7.js → xychartDiagram-2RQKCTM6-Bn1hw4Jk.js} +1 -1
- package/dist-web/index.html +1 -1
- package/package.json +1 -1
- package/templates/harness/skills/st-code-review/SKILL.md +47 -281
- package/templates/harness/skills/st-code-review/scripts/code-review.cjs +70 -300
- package/templates/harness/skills/st-execute-blueprint/SKILL.md +15 -18
- package/templates/harness/skills/st-full-workflow/SKILL.md +15 -18
- package/templates/strikethroo/config/hooks/CODE_REVIEW.md +19 -29
- package/dist-web/assets/channel-DmY2Krrw.js +0 -1
- package/dist-web/assets/classDiagram-4FO5ZUOK-C91aiCZF.js +0 -1
- package/dist-web/assets/classDiagram-v2-Q7XG4LA2-C91aiCZF.js +0 -1
- 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
|
-
|
|
42
|
+
_verdictFor: () => _verdictFor,
|
|
45
43
|
buildReviewerPrompt: () => buildReviewerPrompt,
|
|
46
44
|
createFindingsGate: () => createFindingsGate,
|
|
47
45
|
main: () => main,
|
|
48
|
-
|
|
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
|
|
734
|
-
var
|
|
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
|
|
740
|
-
var isConfidence = (value) => value
|
|
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
|
|
947
|
-
//
|
|
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
|
|
975
|
-
const
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
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
|
-
|
|
981
|
-
|
|
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 = (
|
|
950
|
+
var writeTranscript = (reviewDir, transcript) => {
|
|
1037
951
|
if (transcript === void 0 || transcript === "") return;
|
|
1038
952
|
try {
|
|
1039
|
-
fs6.mkdirSync(
|
|
1040
|
-
fs6.writeFileSync(path6.join(
|
|
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 = (
|
|
1045
|
-
const
|
|
1046
|
-
const findingsFile = path6.join(
|
|
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(
|
|
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
|
|
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,
|
|
976
|
+
record({ ...base, status: "findings-absent", detail, findings: [] });
|
|
1069
977
|
return { kind: "findings-absent", detail };
|
|
1070
978
|
}
|
|
1071
979
|
try {
|
|
1072
|
-
fs6.mkdirSync(
|
|
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
|
-
|
|
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
|
|
1097
|
-
record({ ...base, status: "schema-invalid", detail,
|
|
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,
|
|
1010
|
+
record({ ...base, status: "findings-absent", detail, findings: [] });
|
|
1104
1011
|
return { kind: "findings-absent", detail };
|
|
1105
1012
|
}
|
|
1106
|
-
const
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
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}
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1452
|
-
const reviewFile = path6.join(
|
|
1277
|
+
const reviewDir = path6.join(planDir, REVIEW_DIR_NAME);
|
|
1278
|
+
const reviewFile = path6.join(reviewDir, REVIEW_FILE_NAME);
|
|
1453
1279
|
try {
|
|
1454
|
-
fs6.mkdirSync(
|
|
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
|
|
1284
|
+
detail: `Could not create the review directory ${reviewDir}: ${errorMessage2(error)}`
|
|
1459
1285
|
};
|
|
1460
1286
|
}
|
|
1461
|
-
const staleArtifacts = [reviewFile, path6.join(
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
1355
|
+
var _verdictFor = (outcome) => {
|
|
1535
1356
|
if (outcome.kind !== "evaluated") {
|
|
1536
|
-
return { kind: "
|
|
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
|
-
|
|
1359
|
+
const { counts, findingsFile } = outcome;
|
|
1360
|
+
if (counts.total === 0) {
|
|
1546
1361
|
return {
|
|
1547
|
-
kind: "
|
|
1548
|
-
|
|
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: "
|
|
1554
|
-
|
|
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 === "
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
1419
|
+
_verdictFor,
|
|
1648
1420
|
buildReviewerPrompt,
|
|
1649
1421
|
createFindingsGate,
|
|
1650
1422
|
main,
|
|
1651
|
-
|
|
1652
|
-
runBoundedReviewRound,
|
|
1653
|
-
runReviewRound
|
|
1423
|
+
runReview
|
|
1654
1424
|
});
|