prizmkit 1.1.124 → 1.1.125
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/bundled/VERSION.json +3 -3
- package/bundled/adapters/claude/command-adapter.js +4 -6
- package/bundled/dev-pipeline/scripts/generate-recovery-prompt.py +3 -4
- package/bundled/dev-pipeline/templates/bootstrap-prompt.md +1 -1
- package/bundled/dev-pipeline/templates/bootstrap-tier3.md +4 -3
- package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +4 -3
- package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +7 -2
- package/bundled/dev-pipeline/templates/sections/bugfix-phase-review.md +5 -4
- package/bundled/dev-pipeline/templates/sections/phase-review-agent.md +4 -4
- package/bundled/dev-pipeline/templates/sections/phase-review-full.md +5 -4
- package/bundled/dev-pipeline/templates/sections/refactor-phase-review.md +6 -5
- package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +2 -1
- package/bundled/dev-pipeline/tests/test_unified_cli.py +16 -10
- package/bundled/skills/_metadata.json +4 -4
- package/bundled/skills/prizmkit-code-review/SKILL.md +90 -96
- package/bundled/skills/prizmkit-code-review/assets/reviewer-role.json +3 -5
- package/bundled/skills/prizmkit-code-review/references/review-report-template.md +18 -31
- package/bundled/skills/prizmkit-code-review/references/reviewer-agent-prompt.md +44 -62
- package/bundled/skills/prizmkit-code-review/scripts/render_review_report.py +86 -112
- package/package.json +1 -1
- package/bundled/skills/prizmkit-code-review/references/reviewer-execution-protocol.md +0 -179
- package/bundled/skills/prizmkit-code-review/references/reviewer-workspace-protocol.md +0 -118
- package/bundled/skills/prizmkit-code-review/scripts/check_loop.py +0 -380
- package/bundled/skills/prizmkit-code-review/scripts/workspace_snapshot.py +0 -222
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
-
"protocol_version":
|
|
2
|
+
"protocol_version": 2,
|
|
3
3
|
"role_name": "prizmkit-code-reviewer",
|
|
4
|
-
"description": "Use only as the
|
|
4
|
+
"description": "Use only as the one fresh independent read-only Reviewer for a completed /prizmkit-code-review round.",
|
|
5
5
|
"observational_capabilities": [
|
|
6
6
|
"read-files",
|
|
7
7
|
"search-content",
|
|
@@ -15,7 +15,5 @@
|
|
|
15
15
|
"orchestration-reentry",
|
|
16
16
|
"skill-invocation"
|
|
17
17
|
],
|
|
18
|
-
"execution_mode": "foreground-
|
|
19
|
-
"max_descendant_execution_units": 0,
|
|
20
|
-
"max_delegation_depth": 0
|
|
18
|
+
"execution_mode": "foreground-independent-review"
|
|
21
19
|
}
|
|
@@ -1,53 +1,40 @@
|
|
|
1
1
|
# Review Report Output Template
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Write the final result to `{artifact_dir}/review-report.md`. This is the only required persisted review artifact.
|
|
4
4
|
|
|
5
5
|
```markdown
|
|
6
6
|
# Review Report
|
|
7
7
|
|
|
8
|
-
## Verdict: <PASS|NEEDS_FIXES>
|
|
8
|
+
## Verdict: <PASS|NEEDS_FIXES|BLOCKED>
|
|
9
9
|
## Rounds Completed: <number>
|
|
10
|
-
## Reviewers Started: <number>
|
|
11
|
-
## Review Mode: <independent-reviewer-verified-snapshot|not-run-no-changes>
|
|
12
|
-
## Reviewer Strategy: <semantic strategy summary|not-applicable>
|
|
13
|
-
## Execution Protocol: <COMPLIANT|BLOCKED|VIOLATION|NOT_APPLICABLE>
|
|
14
|
-
## Capability Attestation: <VERIFIED|FAILED|NOT_APPLICABLE>
|
|
15
|
-
## Descendant Execution Units Observed: <number>
|
|
16
|
-
## Delegation Depth Observed: <number>
|
|
17
|
-
## Snapshot Verification: <VERIFIED|FAILED|NOT_APPLICABLE>
|
|
18
|
-
## Snapshot Identity: <identity|failure reason|not-applicable>
|
|
19
|
-
## Blocker Code: <REVIEW_INFRASTRUCTURE_BLOCKED|REVIEW_ORCHESTRATION_VIOLATION|WORKSPACE_MISMATCH|REVIEW_SAFETY_LIMIT_REACHED|INVALID_REVIEW_RESULT|NONE>
|
|
20
10
|
## Total Findings: <total> → Fixed: <fixed>, Rejected: <rejected>, Unresolved: <unresolved>
|
|
21
11
|
|
|
22
|
-
##
|
|
23
|
-
|
|
24
|
-
- Snapshot Identity: <identity>
|
|
25
|
-
- Reviewer Execution Identity: <identity>
|
|
26
|
-
- Result: <PASS|NEEDS_FIXES>
|
|
27
|
-
- Findings: <count>
|
|
28
|
-
- Accepted: <count>
|
|
29
|
-
- Rejected: <count>
|
|
12
|
+
## Findings
|
|
30
13
|
|
|
31
14
|
### Finding <N>: <short title>
|
|
32
15
|
|
|
33
16
|
- Severity: <high|medium|low>
|
|
34
|
-
- Dimension: <dimension>
|
|
35
17
|
- Location: <filepath:line|project-level>
|
|
36
18
|
- Problem: <what was wrong and why it mattered>
|
|
37
19
|
- Failure Scenario: <concrete input or state>
|
|
38
|
-
-
|
|
20
|
+
- Disposition: <fixed in round N|rejected — concrete evidence|unresolved — reason>
|
|
21
|
+
|
|
22
|
+
## Blocker
|
|
23
|
+
|
|
24
|
+
<Include only for BLOCKED: concise infrastructure failure and required action.>
|
|
39
25
|
|
|
40
|
-
##
|
|
26
|
+
## Summary
|
|
41
27
|
|
|
42
|
-
<One to three sentences covering the
|
|
28
|
+
<One to three sentences covering the final outcome. Do not expose private reasoning traces.>
|
|
43
29
|
```
|
|
44
30
|
|
|
45
31
|
## Report Rules
|
|
46
32
|
|
|
47
|
-
-
|
|
48
|
-
-
|
|
49
|
-
-
|
|
50
|
-
- `PASS` requires no
|
|
51
|
-
- The
|
|
52
|
-
-
|
|
53
|
-
-
|
|
33
|
+
- A no-change review uses zero completed rounds and returns `PASS`.
|
|
34
|
+
- A usable Reviewer result counts as one completed round.
|
|
35
|
+
- Reviewer launch failure, abnormal exit, or unusable output returns `BLOCKED` and does not add a completed round.
|
|
36
|
+
- `PASS` requires no accepted or unresolved finding. This includes a Reviewer `PASS` and a findings result where the Main Agent rejects every finding with concrete evidence.
|
|
37
|
+
- The tenth completed round with accepted findings returns `NEEDS_FIXES`.
|
|
38
|
+
- `BLOCKED` describes review infrastructure or missing essential context, not a code defect.
|
|
39
|
+
- Overwrite the same report on each execution; keep raw round output only in the session log.
|
|
40
|
+
- Do not persist a separate review state file.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Reviewer Prompt Contract
|
|
2
2
|
|
|
3
|
-
Use this complete template for every Reviewer execution
|
|
3
|
+
Use this complete template for every Reviewer execution. Fill the documented placeholders with current-round context. Do not include previous-round findings, verdicts, filtering decisions, rejection reasons, or repair explanations.
|
|
4
4
|
|
|
5
5
|
```text
|
|
6
6
|
You are the sole independent read-only Reviewer for this round of `/prizmkit-code-review`.
|
|
@@ -9,61 +9,60 @@ You are the sole independent read-only Reviewer for this round of `/prizmkit-cod
|
|
|
9
9
|
|
|
10
10
|
Perform the complete review yourself.
|
|
11
11
|
|
|
12
|
-
Do not create, schedule, resume, continue, request, or coordinate any separate downstream execution unit. Do not re-enter orchestration,
|
|
12
|
+
Do not create, schedule, resume, continue, request, or coordinate any separate downstream execution unit. Do not re-enter orchestration, invoke a skill, delegate review work, or ask the Main Agent to create a helper.
|
|
13
13
|
|
|
14
|
-
Do not modify, create, delete, rename, stage, or commit workspace files.
|
|
14
|
+
Do not modify, create, delete, rename, stage, or commit workspace files. Use only read-only inspection capabilities.
|
|
15
15
|
|
|
16
|
-
If you cannot complete the review
|
|
16
|
+
If you cannot complete the review in this execution context, return `BLOCKED` with the missing capability or context. Do not delegate and do not return a partial `PASS`.
|
|
17
17
|
|
|
18
18
|
## Independent Input Boundary
|
|
19
19
|
|
|
20
|
-
Review only the current state supplied below. No
|
|
20
|
+
Review only the current state supplied below. No previous-round findings, verdicts, filtering decisions, rejection reasons, repair descriptions, or Reviewer reasoning are part of your input.
|
|
21
21
|
|
|
22
|
-
##
|
|
22
|
+
## Goals and Acceptance Criteria
|
|
23
23
|
|
|
24
|
-
{goals and acceptance criteria from spec.md}
|
|
24
|
+
{goals and acceptance criteria from spec.md, or "No spec artifact found; infer the requested behavior from the user goal and current change."}
|
|
25
25
|
|
|
26
|
-
## Plan
|
|
26
|
+
## Plan and Intended Change
|
|
27
27
|
|
|
28
|
-
{architecture decisions and
|
|
28
|
+
{relevant architecture decisions and completed tasks from plan.md, or a concise current task description}
|
|
29
29
|
|
|
30
30
|
## Applicable Rules and Contracts
|
|
31
31
|
|
|
32
32
|
{relevant Prizm docs, project rules, public and internal contracts, or "No additional project rules identified."}
|
|
33
33
|
|
|
34
|
-
##
|
|
34
|
+
## Test Evidence
|
|
35
35
|
|
|
36
|
-
{current test
|
|
36
|
+
{current scoped test evidence, or "No scoped test report found."}
|
|
37
37
|
|
|
38
|
-
##
|
|
38
|
+
## Current Change
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
- Repository and Reviewer-visible checkout: {path or platform-visible location}
|
|
41
|
+
- Base revision: {base revision when known}
|
|
42
|
+
- Changed paths and states: {staged, unstaged, untracked, deleted, and renamed inventory}
|
|
43
|
+
- Review access: {active checkout, or forced worktree used only for baseline and surrounding context}
|
|
41
44
|
|
|
42
|
-
##
|
|
45
|
+
## Supplied Git Diff
|
|
43
46
|
|
|
44
|
-
|
|
47
|
+
### Staged
|
|
45
48
|
|
|
46
|
-
|
|
49
|
+
{complete staged diff, or "No staged changes."}
|
|
47
50
|
|
|
48
|
-
|
|
51
|
+
### Unstaged
|
|
49
52
|
|
|
50
|
-
|
|
53
|
+
{complete unstaged diff, or "No unstaged tracked changes."}
|
|
51
54
|
|
|
52
|
-
|
|
55
|
+
## Supplied Untracked Files
|
|
53
56
|
|
|
54
|
-
|
|
55
|
-
2. Staged, unstaged, untracked, deleted, and renamed changes are preserved.
|
|
56
|
-
3. Content and snapshot identities match.
|
|
57
|
-
4. Supporting caller or dependent context is distinguishable from changed content.
|
|
58
|
-
5. No unexplained changed path, stale content, mixed-round content, or unverifiable entry exists.
|
|
57
|
+
{path and full content of every untracked file, or "No untracked files."}
|
|
59
58
|
|
|
60
|
-
|
|
59
|
+
Treat the supplied diffs, path states, and untracked file contents as the authoritative current change. Use read-only file inspection for targeted surrounding context, callers, dependents, contracts, and tests. Do not depend on shell or Git execution being available.
|
|
61
60
|
|
|
62
|
-
If
|
|
61
|
+
If a platform-forced worktree does not contain the active checkout's dirty or untracked changes, use the worktree only for baseline and surrounding-context reads. You are not required to calculate hashes or prove equivalence between physical checkouts. If an essential changed file or diff is missing from the supplied input, return `BLOCKED` and identify exactly what is missing.
|
|
63
62
|
|
|
64
|
-
## Complete Current-
|
|
63
|
+
## Complete Current-Change Review
|
|
65
64
|
|
|
66
|
-
Review the complete current change
|
|
65
|
+
Review the complete current change independently. Do not limit inspection to one file or one suspected issue.
|
|
67
66
|
|
|
68
67
|
Evaluate applicable dimensions:
|
|
69
68
|
|
|
@@ -79,60 +78,43 @@ Evaluate applicable dimensions:
|
|
|
79
78
|
10. Test adequacy and evidence compatibility
|
|
80
79
|
11. Project-rule compliance
|
|
81
80
|
|
|
82
|
-
Use both
|
|
81
|
+
Use both directions in this review:
|
|
83
82
|
|
|
84
83
|
- Code-forward: changed code → affected symbols → callers and dependents → concrete failure scenarios
|
|
85
84
|
- Requirement-backward: requirements and contracts → required behavior → implementation path → side effects → test evidence
|
|
86
85
|
|
|
87
|
-
Read unchanged callers, dependents,
|
|
86
|
+
Read unchanged callers, dependents, shared patterns, or tests when needed. Prefer targeted read-only inspection. Do not perform a broad repository scan without a concrete cross-cutting reason.
|
|
88
87
|
|
|
89
|
-
##
|
|
88
|
+
## Finding Standard
|
|
90
89
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
Use existing valid test evidence when it answers the question. Prefer the narrowest verification that can establish or refute a concrete issue. Do not rerun an equivalent check when relevant code and inputs have not changed.
|
|
94
|
-
|
|
95
|
-
Avoid broad builds, tests, lint, or type checks as exploratory defaults. A broad command remains valid when a concrete cross-cutting risk genuinely requires it; state why narrower evidence is insufficient.
|
|
96
|
-
|
|
97
|
-
Non-normative examples:
|
|
98
|
-
|
|
99
|
-
- A repository-wide test or build without a concrete reason is often redundant with the formal Test Gate.
|
|
100
|
-
- A focused module or test-case invocation is usually better when investigating one failure scenario.
|
|
101
|
-
|
|
102
|
-
These examples do not prohibit command names. The governing rule is targeted, non-duplicative evidence gathering.
|
|
103
|
-
|
|
104
|
-
## Finding Coverage
|
|
105
|
-
|
|
106
|
-
Report every concrete issue you find, including uncertain or low-severity issues. Do not filter for importance or confidence; the Main Agent performs downstream filtering. Exclude pure style or naming preferences that cannot cause incorrect behavior, contract drift, regression, security weakness, or misleading evidence.
|
|
90
|
+
Report every concrete defect you find, including low-severity issues. Exclude pure style preferences and speculative concerns that lack a concrete wrong behavior, contract violation, regression path, security weakness, data-integrity risk, or misleading test result.
|
|
107
91
|
|
|
108
92
|
## Output Contract
|
|
109
93
|
|
|
110
94
|
Respond with exactly this structure:
|
|
111
95
|
|
|
112
|
-
### Result: PASS |
|
|
96
|
+
### Result: PASS | FINDINGS | BLOCKED
|
|
113
97
|
|
|
114
|
-
###
|
|
115
|
-
- Status: COMPLIANT | BLOCKED
|
|
116
|
-
- Descendant execution units created: 0
|
|
117
|
-
- Delegation depth: 0
|
|
98
|
+
### Findings
|
|
118
99
|
|
|
119
|
-
|
|
120
|
-
- Status: VERIFIED | FAILED
|
|
121
|
-
- Identity: <snapshot identity or mismatch summary>
|
|
100
|
+
If the result is `PASS`, write:
|
|
122
101
|
|
|
123
|
-
|
|
124
|
-
(If PASS, write "No issues found." If WORKSPACE_MISMATCH or REVIEW_INFRASTRUCTURE_BLOCKED, include only blocker evidence and required action.)
|
|
102
|
+
No issues found.
|
|
125
103
|
|
|
126
|
-
|
|
104
|
+
If the result is `BLOCKED`, write only the missing capability or essential context and the required action.
|
|
105
|
+
|
|
106
|
+
If the result is `FINDINGS`, repeat this block for every concrete issue:
|
|
107
|
+
|
|
108
|
+
#### Finding N: <short title>
|
|
127
109
|
- Severity: high | medium | low
|
|
128
|
-
- Confidence: high | medium | low
|
|
129
|
-
- Dimension: requirement-alignment | defect | completeness | error-handling | security | data-integrity | concurrency | contract | compatibility | test-quality | rules-compliance
|
|
130
110
|
- Location: filepath:line (or "project-level")
|
|
131
111
|
- Problem: concrete wrong behavior and why it matters
|
|
132
112
|
- Failure scenario: inputs or state that produce the wrong result
|
|
133
|
-
-
|
|
113
|
+
- Reasoning: concise evidence connecting the current code to the failure
|
|
114
|
+
- Suggested repair: smallest safe repair approach
|
|
134
115
|
- Verification: how to prove the repair
|
|
135
116
|
|
|
136
117
|
### Summary
|
|
137
|
-
|
|
118
|
+
|
|
119
|
+
One or two sentences describing the current change and result. Do not expose private reasoning traces.
|
|
138
120
|
```
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""Render review-report.md from
|
|
2
|
+
"""Render the final review-report.md from minimal review data."""
|
|
3
3
|
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
@@ -9,102 +9,94 @@ import sys
|
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
from typing import Any
|
|
11
11
|
|
|
12
|
-
VERDICTS = {"PASS", "NEEDS_FIXES"}
|
|
13
|
-
|
|
14
|
-
ATTESTATIONS = {"FAILED", "NOT_APPLICABLE", "VERIFIED"}
|
|
15
|
-
SNAPSHOT_STATUSES = {"FAILED", "NOT_APPLICABLE", "VERIFIED"}
|
|
16
|
-
BLOCKERS = {
|
|
17
|
-
"INVALID_REVIEW_RESULT",
|
|
18
|
-
"NONE",
|
|
19
|
-
"REVIEW_INFRASTRUCTURE_BLOCKED",
|
|
20
|
-
"REVIEW_ORCHESTRATION_VIOLATION",
|
|
21
|
-
"REVIEW_SAFETY_LIMIT_REACHED",
|
|
22
|
-
"WORKSPACE_MISMATCH",
|
|
23
|
-
}
|
|
24
|
-
REVIEW_MODES = {
|
|
25
|
-
"independent-reviewer-verified-snapshot",
|
|
26
|
-
"not-run-no-changes",
|
|
27
|
-
}
|
|
12
|
+
VERDICTS = {"PASS", "NEEDS_FIXES", "BLOCKED"}
|
|
13
|
+
DISPOSITIONS = ("fixed", "rejected", "unresolved")
|
|
28
14
|
|
|
29
15
|
|
|
30
16
|
class ReportStateError(ValueError):
|
|
31
|
-
"""Raised when
|
|
17
|
+
"""Raised when review data cannot produce a valid final report."""
|
|
32
18
|
|
|
33
19
|
|
|
34
|
-
def
|
|
35
|
-
value =
|
|
36
|
-
if value not in allowed:
|
|
37
|
-
raise ReportStateError(f"{name} has invalid value: {value!r}")
|
|
38
|
-
return value
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def _require_count(state: dict[str, Any], name: str) -> int:
|
|
42
|
-
value = state.get(name)
|
|
20
|
+
def _require_count(data: dict[str, Any], name: str) -> int:
|
|
21
|
+
value = data.get(name)
|
|
43
22
|
if not isinstance(value, int) or isinstance(value, bool) or value < 0:
|
|
44
23
|
raise ReportStateError(f"{name} must be a non-negative integer")
|
|
45
24
|
return value
|
|
46
25
|
|
|
47
26
|
|
|
48
|
-
def
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
raise ReportStateError("rounds and findings must be arrays")
|
|
68
|
-
if rounds_completed != len(rounds):
|
|
69
|
-
raise ReportStateError("rounds_completed must equal the rounds array length")
|
|
70
|
-
if reviewers_started < rounds_completed:
|
|
71
|
-
raise ReportStateError("reviewers_started cannot be below rounds_completed")
|
|
72
|
-
|
|
73
|
-
if state["review_mode"] == "not-run-no-changes":
|
|
74
|
-
if any((rounds_completed, reviewers_started, descendants, delegation_depth)):
|
|
75
|
-
raise ReportStateError("no-change review cannot contain execution activity")
|
|
76
|
-
if state["verdict"] != "PASS":
|
|
77
|
-
raise ReportStateError("no-change review must have PASS verdict")
|
|
78
|
-
|
|
79
|
-
if state["verdict"] == "PASS" and state["blocker_code"] != "NONE":
|
|
80
|
-
raise ReportStateError("PASS cannot include a blocker code")
|
|
81
|
-
if state["execution_protocol"] == "COMPLIANT":
|
|
82
|
-
if descendants != 0 or delegation_depth != 0:
|
|
83
|
-
raise ReportStateError("COMPLIANT execution requires zero descendants and depth")
|
|
27
|
+
def validate_data(data: Any) -> dict[str, Any]:
|
|
28
|
+
if not isinstance(data, dict):
|
|
29
|
+
raise ReportStateError("review data must be a JSON object")
|
|
30
|
+
|
|
31
|
+
verdict = data.get("verdict")
|
|
32
|
+
if verdict not in VERDICTS:
|
|
33
|
+
raise ReportStateError(f"verdict has invalid value: {verdict!r}")
|
|
34
|
+
|
|
35
|
+
rounds_completed = _require_count(data, "rounds_completed")
|
|
36
|
+
if rounds_completed > 10:
|
|
37
|
+
raise ReportStateError("rounds_completed cannot exceed ten")
|
|
38
|
+
|
|
39
|
+
findings = data.get("findings")
|
|
40
|
+
if not isinstance(findings, list):
|
|
41
|
+
raise ReportStateError("findings must be an array")
|
|
42
|
+
|
|
43
|
+
summary = data.get("summary")
|
|
44
|
+
if not isinstance(summary, str) or not summary.strip():
|
|
45
|
+
raise ReportStateError("summary must be a non-empty string")
|
|
84
46
|
|
|
85
47
|
for index, finding in enumerate(findings, start=1):
|
|
86
48
|
if not isinstance(finding, dict):
|
|
87
49
|
raise ReportStateError(f"finding {index} must be an object")
|
|
88
|
-
required = {
|
|
50
|
+
required = {
|
|
51
|
+
"title",
|
|
52
|
+
"severity",
|
|
53
|
+
"location",
|
|
54
|
+
"problem",
|
|
55
|
+
"failure_scenario",
|
|
56
|
+
"disposition",
|
|
57
|
+
}
|
|
89
58
|
missing = sorted(required - finding.keys())
|
|
90
59
|
if missing:
|
|
91
60
|
raise ReportStateError(
|
|
92
61
|
f"finding {index} missing fields: {', '.join(missing)}"
|
|
93
62
|
)
|
|
94
|
-
|
|
95
|
-
|
|
63
|
+
disposition = finding["disposition"]
|
|
64
|
+
if not isinstance(disposition, str) or not disposition.startswith(DISPOSITIONS):
|
|
65
|
+
raise ReportStateError(f"finding {index} has invalid disposition")
|
|
66
|
+
|
|
67
|
+
unresolved = sum(
|
|
68
|
+
finding["disposition"].startswith("unresolved") for finding in findings
|
|
69
|
+
)
|
|
70
|
+
blocker = data.get("blocker")
|
|
71
|
+
|
|
72
|
+
if verdict == "PASS" and unresolved:
|
|
73
|
+
raise ReportStateError("PASS cannot contain unresolved findings")
|
|
74
|
+
if verdict == "NEEDS_FIXES" and not unresolved:
|
|
75
|
+
raise ReportStateError("NEEDS_FIXES requires an unresolved finding")
|
|
76
|
+
if verdict == "BLOCKED":
|
|
77
|
+
if not isinstance(blocker, str) or not blocker.strip():
|
|
78
|
+
raise ReportStateError("BLOCKED requires a non-empty blocker")
|
|
79
|
+
if unresolved:
|
|
80
|
+
raise ReportStateError("BLOCKED cannot classify code findings as unresolved")
|
|
81
|
+
elif blocker is not None:
|
|
82
|
+
raise ReportStateError("only BLOCKED may include blocker")
|
|
83
|
+
|
|
84
|
+
return data
|
|
96
85
|
|
|
97
86
|
|
|
98
87
|
def _finding_totals(findings: list[dict[str, Any]]) -> tuple[int, int, int]:
|
|
99
|
-
fixed = sum(finding["
|
|
100
|
-
rejected = sum(
|
|
101
|
-
|
|
88
|
+
fixed = sum(finding["disposition"].startswith("fixed") for finding in findings)
|
|
89
|
+
rejected = sum(
|
|
90
|
+
finding["disposition"].startswith("rejected") for finding in findings
|
|
91
|
+
)
|
|
92
|
+
unresolved = sum(
|
|
93
|
+
finding["disposition"].startswith("unresolved") for finding in findings
|
|
94
|
+
)
|
|
102
95
|
return fixed, rejected, unresolved
|
|
103
96
|
|
|
104
97
|
|
|
105
|
-
def render_report(
|
|
106
|
-
|
|
107
|
-
valid = validate_state(state)
|
|
98
|
+
def render_report(data: Any) -> str:
|
|
99
|
+
valid = validate_data(data)
|
|
108
100
|
findings = valid["findings"]
|
|
109
101
|
fixed, rejected, unresolved = _finding_totals(findings)
|
|
110
102
|
|
|
@@ -113,69 +105,51 @@ def render_report(state: Any) -> str:
|
|
|
113
105
|
"",
|
|
114
106
|
f"## Verdict: {valid['verdict']}",
|
|
115
107
|
f"## Rounds Completed: {valid['rounds_completed']}",
|
|
116
|
-
f"## Reviewers Started: {valid['reviewers_started']}",
|
|
117
|
-
f"## Review Mode: {valid['review_mode']}",
|
|
118
|
-
f"## Reviewer Strategy: {valid.get('reviewer_strategy', 'not-applicable')}",
|
|
119
|
-
f"## Execution Protocol: {valid['execution_protocol']}",
|
|
120
|
-
f"## Capability Attestation: {valid['capability_attestation']}",
|
|
121
|
-
f"## Descendant Execution Units Observed: {valid['descendant_execution_units_observed']}",
|
|
122
|
-
f"## Delegation Depth Observed: {valid['delegation_depth_observed']}",
|
|
123
|
-
f"## Snapshot Verification: {valid['snapshot_verification']}",
|
|
124
|
-
f"## Snapshot Identity: {valid.get('snapshot_identity', 'not-applicable')}",
|
|
125
|
-
f"## Blocker Code: {valid['blocker_code']}",
|
|
126
108
|
f"## Total Findings: {len(findings)} → Fixed: {fixed}, Rejected: {rejected}, Unresolved: {unresolved}",
|
|
127
109
|
"",
|
|
128
110
|
]
|
|
129
111
|
|
|
130
|
-
if
|
|
131
|
-
lines.extend(["##
|
|
132
|
-
|
|
133
|
-
for review_round in valid["rounds"]:
|
|
112
|
+
if findings:
|
|
113
|
+
lines.extend(["## Findings", ""])
|
|
114
|
+
for index, finding in enumerate(findings, start=1):
|
|
134
115
|
lines.extend(
|
|
135
116
|
[
|
|
136
|
-
f"
|
|
117
|
+
f"### Finding {index}: {finding['title']}",
|
|
137
118
|
"",
|
|
138
|
-
f"-
|
|
139
|
-
f"-
|
|
140
|
-
f"-
|
|
141
|
-
f"-
|
|
142
|
-
f"-
|
|
143
|
-
f"- Rejected: {review_round['rejected_count']}",
|
|
119
|
+
f"- Severity: {finding['severity']}",
|
|
120
|
+
f"- Location: {finding['location']}",
|
|
121
|
+
f"- Problem: {finding['problem']}",
|
|
122
|
+
f"- Failure Scenario: {finding['failure_scenario']}",
|
|
123
|
+
f"- Disposition: {finding['disposition']}",
|
|
144
124
|
"",
|
|
145
125
|
]
|
|
146
126
|
)
|
|
147
127
|
|
|
148
|
-
|
|
149
|
-
lines.extend(
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
"",
|
|
153
|
-
f"- Severity: {finding['severity']}",
|
|
154
|
-
f"- Dimension: {finding['dimension']}",
|
|
155
|
-
f"- Location: {finding['location']}",
|
|
156
|
-
f"- Problem: {finding['problem']}",
|
|
157
|
-
f"- Failure Scenario: {finding['failure_scenario']}",
|
|
158
|
-
f"- Status: {finding['status']}",
|
|
159
|
-
"",
|
|
160
|
-
]
|
|
161
|
-
)
|
|
162
|
-
|
|
163
|
-
lines.extend(["## Final Summary", "", valid["final_summary"], ""])
|
|
128
|
+
if valid["verdict"] == "BLOCKED":
|
|
129
|
+
lines.extend(["## Blocker", "", valid["blocker"], ""])
|
|
130
|
+
|
|
131
|
+
lines.extend(["## Summary", "", valid["summary"], ""])
|
|
164
132
|
return "\n".join(lines)
|
|
165
133
|
|
|
166
134
|
|
|
167
135
|
def parse_args() -> argparse.Namespace:
|
|
168
136
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
169
|
-
parser.add_argument("state", type=Path, help="completed review-result-state.json")
|
|
170
137
|
parser.add_argument("output", type=Path, help="review-report.md destination")
|
|
138
|
+
parser.add_argument(
|
|
139
|
+
"input",
|
|
140
|
+
nargs="?",
|
|
141
|
+
type=Path,
|
|
142
|
+
help="optional JSON input path; reads standard input when omitted",
|
|
143
|
+
)
|
|
171
144
|
return parser.parse_args()
|
|
172
145
|
|
|
173
146
|
|
|
174
147
|
def main() -> int:
|
|
175
148
|
args = parse_args()
|
|
176
149
|
try:
|
|
177
|
-
|
|
178
|
-
|
|
150
|
+
raw = args.input.read_text(encoding="utf-8") if args.input else sys.stdin.read()
|
|
151
|
+
data = json.loads(raw)
|
|
152
|
+
report = render_report(data)
|
|
179
153
|
args.output.parent.mkdir(parents=True, exist_ok=True)
|
|
180
154
|
args.output.write_text(report, encoding="utf-8")
|
|
181
155
|
except (OSError, json.JSONDecodeError, ReportStateError, KeyError) as error:
|