claude-dev-env 1.59.0 → 1.60.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/audit-rubrics/category_rubrics/category-b-selector-engine-compat.md +1 -1
- package/audit-rubrics/category_rubrics/category-e-dead-code.md +1 -0
- package/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md +1 -1
- package/audit-rubrics/prompts/category-b-selector-engine-compat.md +2 -2
- package/hooks/blocking/code_rules_dead_module_constant.py +321 -0
- package/hooks/blocking/code_rules_duplicate_body.py +152 -0
- package/hooks/blocking/code_rules_enforcer.py +30 -15
- package/hooks/blocking/code_rules_typeddict_stub.py +172 -0
- package/hooks/blocking/config/__init__.py +5 -0
- package/hooks/blocking/config/verified_commit_constants.py +106 -0
- package/hooks/blocking/test_code_rules_enforcer_cross_skill_duplicate.py +146 -0
- package/hooks/blocking/test_code_rules_enforcer_dead_module_constant.py +188 -0
- package/hooks/blocking/test_code_rules_enforcer_zero_payload_alias.py +415 -0
- package/hooks/blocking/test_code_rules_enforcer_zero_payload_alias_hook_routing.py +156 -0
- package/hooks/blocking/test_verdict_directory_write_blocker.py +720 -0
- package/hooks/blocking/test_verification_verdict_store.py +278 -0
- package/hooks/blocking/test_verified_commit_gate.py +368 -0
- package/hooks/blocking/test_verified_commit_message_accuracy_blocker.py +131 -0
- package/hooks/blocking/test_verifier_verdict_minter.py +214 -0
- package/hooks/blocking/verdict_directory_write_blocker.py +667 -0
- package/hooks/blocking/verification_verdict_store.py +446 -0
- package/hooks/blocking/verified_commit_gate.py +523 -0
- package/hooks/blocking/verified_commit_message_accuracy_blocker.py +152 -0
- package/hooks/blocking/verifier_verdict_minter.py +299 -0
- package/hooks/diagnostic/test_hook_log_extractor.py +3 -3
- package/hooks/hooks.json +43 -1
- package/hooks/hooks_constants/blocking_check_limits.py +1 -0
- package/hooks/hooks_constants/dead_module_constant_constants.py +20 -0
- package/hooks/hooks_constants/duplicate_function_body_constants.py +22 -5
- package/hooks/hooks_constants/precommit_code_rules_gate_constants.py +1 -1
- package/package.json +1 -1
- package/rules/file-global-constants.md +7 -1
- package/rules/no-cross-skill-duplicate-helpers.md +29 -0
- package/skills/_shared/pr-loop/scripts/preflight_worktree.py +392 -0
- package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/preflight_constants.py +70 -0
- package/skills/_shared/pr-loop/scripts/test_preflight_worktree.py +263 -0
- package/skills/autoconverge/SKILL.md +54 -17
- package/skills/autoconverge/reference/closing-report.md +59 -17
- package/skills/autoconverge/workflow/aggregate_runs.py +371 -0
- package/skills/autoconverge/workflow/autoconverge_report_constants/render_report_constants.py +193 -76
- package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +76 -0
- package/skills/autoconverge/workflow/converge.contract.test.mjs +206 -206
- package/skills/autoconverge/workflow/converge.mjs +128 -6
- package/skills/autoconverge/workflow/convergence_summary.py +110 -0
- package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-ab1c2d3e4f5a6b7c8.jsonl +2 -0
- package/skills/autoconverge/workflow/fixtures/wf_run/workflows/wf_881252e6-700.json +7 -0
- package/skills/autoconverge/workflow/render_report.py +488 -397
- package/skills/autoconverge/workflow/test_aggregate_runs.py +134 -0
- package/skills/autoconverge/workflow/test_convergence_summary.py +132 -0
- package/skills/autoconverge/workflow/test_render_report.py +488 -259
- package/skills/pr-converge/reference/per-tick.md +28 -8
- package/skills/rebase/SKILL.md +2 -4
- package/system-prompts/software-engineer.xml +2 -6
- package/hooks/blocking/content_search_to_zoekt_redirector.py +0 -59
- package/hooks/blocking/content_search_zoekt_bash_block_reason.py +0 -25
- package/hooks/blocking/content_search_zoekt_block_payload.py +0 -21
- package/hooks/blocking/content_search_zoekt_indexed_paths.py +0 -24
- package/hooks/blocking/content_search_zoekt_indexed_roots_config.py +0 -131
- package/hooks/blocking/content_search_zoekt_redirect_guidance.py +0 -52
- package/hooks/blocking/test_content_search_to_zoekt_redirector_integration.py +0 -61
- package/hooks/blocking/test_content_search_to_zoekt_redirector_unit.py +0 -92
- package/hooks/blocking/test_content_search_zoekt_indexed_roots_config.py +0 -102
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
"""Merge every autoconverge run journal for one PR into a single combined journal."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import re
|
|
7
|
+
import shutil
|
|
8
|
+
import stat
|
|
9
|
+
import sys
|
|
10
|
+
from collections.abc import Callable
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import TextIO
|
|
14
|
+
|
|
15
|
+
import convergence_summary
|
|
16
|
+
import render_report
|
|
17
|
+
from autoconverge_report_constants.render_report_constants import (
|
|
18
|
+
ARGS_FIELD_OWNER,
|
|
19
|
+
ARGS_FIELD_PR_NUMBER,
|
|
20
|
+
ARGS_FIELD_REPO,
|
|
21
|
+
COMBINED_RUN_ID_PREFIX,
|
|
22
|
+
JOURNAL_FIELD_ARGS,
|
|
23
|
+
JOURNAL_FIELD_RESULT,
|
|
24
|
+
JOURNAL_FIELD_RUN_ID,
|
|
25
|
+
JOURNAL_FIELD_TIMESTAMP,
|
|
26
|
+
JOURNAL_FIELD_WORKFLOW_NAME,
|
|
27
|
+
JOURNAL_FIELD_WORKFLOW_PROGRESS,
|
|
28
|
+
JOURNAL_SIBLING_SUBAGENTS,
|
|
29
|
+
JOURNAL_SIBLING_WORKFLOWS,
|
|
30
|
+
LABEL_RESOLVE_HEAD,
|
|
31
|
+
ONEXC_PYTHON_MAJOR_VERSION,
|
|
32
|
+
ONEXC_PYTHON_MINOR_VERSION,
|
|
33
|
+
PROGRESS_FIELD_AGENT_ID,
|
|
34
|
+
PROGRESS_FIELD_LABEL,
|
|
35
|
+
PROJECTS_DIR_NAME,
|
|
36
|
+
RESULT_FIELD_FINAL_SHA,
|
|
37
|
+
SUMMARY_DETAIL_MAX_CHARS,
|
|
38
|
+
WORKFLOW_NAME_AUTOCONVERGE,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass(frozen=True)
|
|
43
|
+
class AggregateResult:
|
|
44
|
+
"""The combined journal plus the aggregated inputs a closing report needs."""
|
|
45
|
+
|
|
46
|
+
combined_journal_path: Path
|
|
47
|
+
findings: list[dict]
|
|
48
|
+
fix_summaries: list[str]
|
|
49
|
+
round_count: int
|
|
50
|
+
final_sha: str
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _parse_args_object(journal: dict) -> dict:
|
|
54
|
+
"""Return the journal's args as a dict, parsing a JSON string when needed.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
journal: A parsed run-journal object.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
The args object as a dict, or an empty dict when absent or unparseable.
|
|
61
|
+
"""
|
|
62
|
+
raw_args = journal.get(JOURNAL_FIELD_ARGS, "")
|
|
63
|
+
if isinstance(raw_args, dict):
|
|
64
|
+
return raw_args
|
|
65
|
+
try:
|
|
66
|
+
parsed = json.loads(raw_args)
|
|
67
|
+
except (ValueError, TypeError):
|
|
68
|
+
return {}
|
|
69
|
+
return parsed if isinstance(parsed, dict) else {}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _resolve_projects_root(seed_journal_path: Path) -> Path:
|
|
73
|
+
"""Return the projects directory that contains the seed run journal.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
seed_journal_path: Path to one of the PR's wf_<runId>.json journals.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
The nearest ancestor directory named 'projects', or the seed's
|
|
80
|
+
four-levels-up ancestor when no such ancestor exists.
|
|
81
|
+
"""
|
|
82
|
+
for each_ancestor in seed_journal_path.parents:
|
|
83
|
+
if each_ancestor.name == PROJECTS_DIR_NAME:
|
|
84
|
+
return each_ancestor
|
|
85
|
+
return seed_journal_path.parents[3]
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _discover_pr_runs(
|
|
89
|
+
projects_root: Path, owner: str, repo: str, pr_number: int
|
|
90
|
+
) -> list[tuple[Path, dict]]:
|
|
91
|
+
"""Return every autoconverge (journal path, journal) pair for the PR, oldest first.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
projects_root: The projects directory holding all run trees.
|
|
95
|
+
owner: The PR's repository owner.
|
|
96
|
+
repo: The PR's repository name.
|
|
97
|
+
pr_number: The PR number.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
A timestamp-sorted list of (journal path, parsed journal) pairs.
|
|
101
|
+
"""
|
|
102
|
+
runs: list[tuple[Path, dict]] = []
|
|
103
|
+
for each_journal_path in projects_root.glob("*/*/workflows/wf_*.json"):
|
|
104
|
+
try:
|
|
105
|
+
journal = json.loads(each_journal_path.read_text(encoding="utf-8"))
|
|
106
|
+
except (ValueError, OSError):
|
|
107
|
+
continue
|
|
108
|
+
if journal.get(JOURNAL_FIELD_WORKFLOW_NAME) != WORKFLOW_NAME_AUTOCONVERGE:
|
|
109
|
+
continue
|
|
110
|
+
args_object = _parse_args_object(journal)
|
|
111
|
+
matches_pr = (
|
|
112
|
+
args_object.get(ARGS_FIELD_OWNER) == owner
|
|
113
|
+
and args_object.get(ARGS_FIELD_REPO) == repo
|
|
114
|
+
and args_object.get(ARGS_FIELD_PR_NUMBER) == pr_number
|
|
115
|
+
)
|
|
116
|
+
if matches_pr:
|
|
117
|
+
runs.append((each_journal_path, journal))
|
|
118
|
+
runs.sort(key=lambda pair: pair[1].get(JOURNAL_FIELD_TIMESTAMP, ""))
|
|
119
|
+
return runs
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def discover_pr_journals(
|
|
123
|
+
projects_root: Path, owner: str, repo: str, pr_number: int
|
|
124
|
+
) -> list[Path]:
|
|
125
|
+
"""Return every autoconverge journal path for the PR, oldest first.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
projects_root: The projects directory holding all run trees.
|
|
129
|
+
owner: The PR's repository owner.
|
|
130
|
+
repo: The PR's repository name.
|
|
131
|
+
pr_number: The PR number.
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
A timestamp-sorted list of journal paths matching the PR.
|
|
135
|
+
"""
|
|
136
|
+
return [
|
|
137
|
+
path
|
|
138
|
+
for path, _journal in _discover_pr_runs(projects_root, owner, repo, pr_number)
|
|
139
|
+
]
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _retry_after_chmod(
|
|
143
|
+
remove_func: Callable[[str], None], failing_path: str, *_exc_info: object
|
|
144
|
+
) -> None:
|
|
145
|
+
"""Clear the Windows read-only bit on a path, then retry the removal that failed.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
remove_func: The os removal call rmtree was attempting when it failed.
|
|
149
|
+
failing_path: The path the failed removal call could not delete.
|
|
150
|
+
_exc_info: The exception or exc_info tuple the rmtree handler passes.
|
|
151
|
+
"""
|
|
152
|
+
os.chmod(failing_path, stat.S_IWRITE)
|
|
153
|
+
remove_func(failing_path)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _select_rmtree_handler_keyword() -> dict[str, Callable[..., None]]:
|
|
157
|
+
"""Return the rmtree retry-handler keyword argument for the running Python.
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
A single-entry dict using the 'onexc' keyword on the Python versions that
|
|
161
|
+
accept it and 'onerror' on the earlier versions that do not.
|
|
162
|
+
"""
|
|
163
|
+
onexc_required_version = (ONEXC_PYTHON_MAJOR_VERSION, ONEXC_PYTHON_MINOR_VERSION)
|
|
164
|
+
if sys.version_info >= onexc_required_version:
|
|
165
|
+
return {"onexc": _retry_after_chmod}
|
|
166
|
+
return {"onerror": _retry_after_chmod}
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _force_remove(target_path: Path) -> None:
|
|
170
|
+
"""Remove a directory tree, clearing the Windows read-only bit on failure.
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
target_path: The directory tree to remove when it exists.
|
|
174
|
+
"""
|
|
175
|
+
if not target_path.exists():
|
|
176
|
+
return
|
|
177
|
+
handler_keyword = _select_rmtree_handler_keyword()
|
|
178
|
+
if "onexc" in handler_keyword:
|
|
179
|
+
shutil.rmtree(target_path, onexc=_retry_after_chmod)
|
|
180
|
+
return
|
|
181
|
+
shutil.rmtree(target_path, onerror=_retry_after_chmod)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def aggregate_pr_journals(
|
|
185
|
+
seed_journal_path: Path, owner: str, repo: str, pr_number: int, work_dir: Path
|
|
186
|
+
) -> AggregateResult:
|
|
187
|
+
"""Merge every autoconverge journal for the PR into one combined journal.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
seed_journal_path: One of the PR's run journals, used to locate the rest.
|
|
191
|
+
owner: The PR's repository owner.
|
|
192
|
+
repo: The PR's repository name.
|
|
193
|
+
pr_number: The PR number.
|
|
194
|
+
work_dir: A directory the combined run tree is written under.
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
An AggregateResult carrying the combined journal path, the deduped
|
|
198
|
+
findings, the fix summaries, the resolve-head round count, and the
|
|
199
|
+
final commit sha drawn from the latest run's result.
|
|
200
|
+
"""
|
|
201
|
+
projects_root = _resolve_projects_root(seed_journal_path)
|
|
202
|
+
runs = _discover_pr_runs(projects_root, owner, repo, pr_number)
|
|
203
|
+
|
|
204
|
+
combined_run_id = f"{COMBINED_RUN_ID_PREFIX}{pr_number}"
|
|
205
|
+
dest_base = work_dir / combined_run_id
|
|
206
|
+
_force_remove(dest_base)
|
|
207
|
+
combined_workflows_dir = dest_base / JOURNAL_SIBLING_WORKFLOWS
|
|
208
|
+
combined_workflows_dir.mkdir(parents=True)
|
|
209
|
+
combined_agents_dir = (
|
|
210
|
+
dest_base
|
|
211
|
+
/ JOURNAL_SIBLING_SUBAGENTS
|
|
212
|
+
/ JOURNAL_SIBLING_WORKFLOWS
|
|
213
|
+
/ combined_run_id
|
|
214
|
+
)
|
|
215
|
+
combined_agents_dir.mkdir(parents=True)
|
|
216
|
+
|
|
217
|
+
combined_progress: list[dict] = []
|
|
218
|
+
final_sha = ""
|
|
219
|
+
latest_timestamp = ""
|
|
220
|
+
for each_journal_path, each_journal in runs:
|
|
221
|
+
source_agents_dir = (
|
|
222
|
+
each_journal_path.parent.parent
|
|
223
|
+
/ JOURNAL_SIBLING_SUBAGENTS
|
|
224
|
+
/ JOURNAL_SIBLING_WORKFLOWS
|
|
225
|
+
/ each_journal_path.stem
|
|
226
|
+
)
|
|
227
|
+
for each_entry in each_journal.get(JOURNAL_FIELD_WORKFLOW_PROGRESS, []):
|
|
228
|
+
agent_id = each_entry.get(PROGRESS_FIELD_AGENT_ID)
|
|
229
|
+
if agent_id:
|
|
230
|
+
source_transcript = source_agents_dir / f"agent-{agent_id}.jsonl"
|
|
231
|
+
if source_transcript.exists():
|
|
232
|
+
shutil.copy(
|
|
233
|
+
source_transcript,
|
|
234
|
+
combined_agents_dir / f"agent-{agent_id}.jsonl",
|
|
235
|
+
)
|
|
236
|
+
combined_progress.append(each_entry)
|
|
237
|
+
result_block = each_journal.get(JOURNAL_FIELD_RESULT) or {}
|
|
238
|
+
if result_block.get(RESULT_FIELD_FINAL_SHA):
|
|
239
|
+
final_sha = result_block[RESULT_FIELD_FINAL_SHA]
|
|
240
|
+
timestamp = each_journal.get(JOURNAL_FIELD_TIMESTAMP, "")
|
|
241
|
+
if timestamp > latest_timestamp:
|
|
242
|
+
latest_timestamp = timestamp
|
|
243
|
+
|
|
244
|
+
round_count = sum(
|
|
245
|
+
1
|
|
246
|
+
for each_entry in combined_progress
|
|
247
|
+
if each_entry.get(PROGRESS_FIELD_LABEL) == LABEL_RESOLVE_HEAD
|
|
248
|
+
)
|
|
249
|
+
combined_journal = {
|
|
250
|
+
JOURNAL_FIELD_RUN_ID: combined_run_id,
|
|
251
|
+
JOURNAL_FIELD_TIMESTAMP: latest_timestamp,
|
|
252
|
+
JOURNAL_FIELD_WORKFLOW_NAME: WORKFLOW_NAME_AUTOCONVERGE,
|
|
253
|
+
JOURNAL_FIELD_ARGS: json.dumps(
|
|
254
|
+
{
|
|
255
|
+
ARGS_FIELD_OWNER: owner,
|
|
256
|
+
ARGS_FIELD_REPO: repo,
|
|
257
|
+
ARGS_FIELD_PR_NUMBER: pr_number,
|
|
258
|
+
}
|
|
259
|
+
),
|
|
260
|
+
JOURNAL_FIELD_RESULT: {RESULT_FIELD_FINAL_SHA: final_sha},
|
|
261
|
+
JOURNAL_FIELD_WORKFLOW_PROGRESS: combined_progress,
|
|
262
|
+
}
|
|
263
|
+
combined_journal_path = combined_workflows_dir / f"{combined_run_id}.json"
|
|
264
|
+
combined_journal_path.write_text(json.dumps(combined_journal), encoding="utf-8")
|
|
265
|
+
|
|
266
|
+
run_data = render_report.load_run_data(combined_journal_path)
|
|
267
|
+
findings = [
|
|
268
|
+
{
|
|
269
|
+
"severity": each_finding.severity,
|
|
270
|
+
"category": each_finding.category,
|
|
271
|
+
"file": each_finding.file,
|
|
272
|
+
"line": each_finding.line,
|
|
273
|
+
"title": each_finding.title,
|
|
274
|
+
"detail": (each_finding.detail or "")[:SUMMARY_DETAIL_MAX_CHARS],
|
|
275
|
+
}
|
|
276
|
+
for each_finding in run_data.all_distinct_findings
|
|
277
|
+
]
|
|
278
|
+
fix_summaries = [
|
|
279
|
+
each_fix.summary
|
|
280
|
+
for each_fix in run_data.fix_by_round.values()
|
|
281
|
+
if each_fix.summary
|
|
282
|
+
]
|
|
283
|
+
return AggregateResult(
|
|
284
|
+
combined_journal_path=combined_journal_path,
|
|
285
|
+
findings=findings,
|
|
286
|
+
fix_summaries=fix_summaries,
|
|
287
|
+
round_count=round_count,
|
|
288
|
+
final_sha=final_sha,
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def main(out_stream: TextIO = sys.stdout, err_stream: TextIO = sys.stderr) -> int:
|
|
293
|
+
"""Aggregate a PR's journals, write the summary prompt, print the combined facts.
|
|
294
|
+
|
|
295
|
+
Args:
|
|
296
|
+
out_stream: Stream the result JSON is written to on success.
|
|
297
|
+
err_stream: Stream error messages are written to.
|
|
298
|
+
|
|
299
|
+
Returns:
|
|
300
|
+
Exit code (0 on success, 1 on argument error).
|
|
301
|
+
"""
|
|
302
|
+
argument_parser = argparse.ArgumentParser(
|
|
303
|
+
description="Aggregate every autoconverge journal for a PR into one journal."
|
|
304
|
+
)
|
|
305
|
+
argument_parser.add_argument(
|
|
306
|
+
"--journal", required=True, help="Path to one of the PR's wf_<runId>.json files"
|
|
307
|
+
)
|
|
308
|
+
argument_parser.add_argument("--pr", required=True, help="owner/repo#number")
|
|
309
|
+
argument_parser.add_argument(
|
|
310
|
+
"--work-dir",
|
|
311
|
+
required=True,
|
|
312
|
+
help="Directory the combined run tree is written under",
|
|
313
|
+
)
|
|
314
|
+
argument_parser.add_argument(
|
|
315
|
+
"--out-prompt",
|
|
316
|
+
required=True,
|
|
317
|
+
help="Path the summary agent prompt is written to",
|
|
318
|
+
)
|
|
319
|
+
argument_parser.add_argument(
|
|
320
|
+
"--standards-note", default=None, help="Deferred code-standard note, when any"
|
|
321
|
+
)
|
|
322
|
+
argument_parser.add_argument(
|
|
323
|
+
"--copilot-note", default=None, help="Copilot gate outage note, when any"
|
|
324
|
+
)
|
|
325
|
+
parsed_args = argument_parser.parse_args()
|
|
326
|
+
|
|
327
|
+
pr_arg_pattern = r"(?P<owner>[^/]+)/(?P<repo>[^#]+)#(?P<number>\d+)"
|
|
328
|
+
pr_match = re.fullmatch(pr_arg_pattern, parsed_args.pr)
|
|
329
|
+
if pr_match is None:
|
|
330
|
+
err_stream.write(
|
|
331
|
+
f"Invalid --pr format: {parsed_args.pr!r}. Expected owner/repo#number.\n"
|
|
332
|
+
)
|
|
333
|
+
return 1
|
|
334
|
+
owner = pr_match.group("owner")
|
|
335
|
+
repo = pr_match.group("repo")
|
|
336
|
+
pr_number = int(pr_match.group("number"))
|
|
337
|
+
|
|
338
|
+
work_dir = Path(parsed_args.work_dir)
|
|
339
|
+
work_dir.mkdir(parents=True, exist_ok=True)
|
|
340
|
+
aggregation = aggregate_pr_journals(
|
|
341
|
+
Path(parsed_args.journal).resolve(), owner, repo, pr_number, work_dir
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
prompt = convergence_summary.build_summary_prompt(
|
|
345
|
+
owner,
|
|
346
|
+
repo,
|
|
347
|
+
pr_number,
|
|
348
|
+
aggregation.round_count,
|
|
349
|
+
aggregation.findings,
|
|
350
|
+
aggregation.fix_summaries,
|
|
351
|
+
parsed_args.standards_note,
|
|
352
|
+
parsed_args.copilot_note,
|
|
353
|
+
)
|
|
354
|
+
Path(parsed_args.out_prompt).write_text(prompt, encoding="utf-8")
|
|
355
|
+
|
|
356
|
+
out_stream.write(
|
|
357
|
+
json.dumps(
|
|
358
|
+
{
|
|
359
|
+
"combinedJournal": str(aggregation.combined_journal_path),
|
|
360
|
+
"roundCount": aggregation.round_count,
|
|
361
|
+
"finalSha": aggregation.final_sha,
|
|
362
|
+
"findingCount": len(aggregation.findings),
|
|
363
|
+
}
|
|
364
|
+
)
|
|
365
|
+
+ "\n"
|
|
366
|
+
)
|
|
367
|
+
return 0
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
if __name__ == "__main__":
|
|
371
|
+
sys.exit(main())
|