okstra 0.207.1 → 0.209.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/README.md +3 -2
- package/dist/cli-registry.mjs +6 -0
- package/dist/cli-registry.mjs.map +1 -1
- package/dist/commands/execute/render-bundle.mjs +1 -1
- package/dist/commands/lifecycle/doctor.mjs +1 -1
- package/dist/lib/skill-catalog.mjs +1 -0
- package/dist/lib/skill-catalog.mjs.map +1 -1
- package/docs/architecture/storage-model.md +14 -0
- package/docs/architecture.md +30 -9
- package/docs/cli.md +26 -22
- package/docs/contributor-change-matrix.md +1 -1
- package/docs/project-structure-overview.md +15 -8
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/operations/explain-flow.json +6 -0
- package/runtime/bin/lib/okstra/cli.sh +1 -5
- package/runtime/bin/lib/okstra/globals.sh +0 -2
- package/runtime/bin/lib/okstra/usage.sh +5 -3
- package/runtime/bin/okstra.sh +0 -2
- package/runtime/prompts/duties/business-flow-investigator.json +14 -0
- package/runtime/prompts/lead/context-loader.md +1 -1
- package/runtime/prompts/lead/convergence.md +22 -7
- package/runtime/prompts/lead/okstra-lead-contract.md +10 -6
- package/runtime/prompts/lead/report-writer.md +1 -1
- package/runtime/prompts/lead/team-contract.md +12 -17
- package/runtime/prompts/profiles/_common-contract.md +3 -3
- package/runtime/prompts/wizard/prompts.ko.json +0 -91
- package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +6 -0
- package/runtime/python/okstra_ctl/agent/invocation.py +1 -1
- package/runtime/python/okstra_ctl/agent/prompt_cli/batch.py +1 -0
- package/runtime/python/okstra_ctl/agent/prompt_cli/cli.py +1 -0
- package/runtime/python/okstra_ctl/agent/prompt_cli/materialize.py +11 -125
- package/runtime/python/okstra_ctl/agent/standalone.py +183 -0
- package/runtime/python/okstra_ctl/analysis_packet.py +39 -8
- package/runtime/python/okstra_ctl/assignment_resolver.py +7 -1
- package/runtime/python/okstra_ctl/brief_frontmatter.py +10 -0
- package/runtime/python/okstra_ctl/business_flow/__init__.py +4 -0
- package/runtime/python/okstra_ctl/business_flow/cli.py +134 -0
- package/runtime/python/okstra_ctl/business_flow/contracts.py +268 -0
- package/runtime/python/okstra_ctl/business_flow/engine.py +518 -0
- package/runtime/python/okstra_ctl/business_flow/hooks.py +221 -0
- package/runtime/python/okstra_ctl/business_flow/invocation.py +170 -0
- package/runtime/python/okstra_ctl/business_flow/report.py +49 -0
- package/runtime/python/okstra_ctl/business_flow/source.py +206 -0
- package/runtime/python/okstra_ctl/business_flow/store.py +388 -0
- package/runtime/python/okstra_ctl/cmux.py +21 -16
- package/runtime/python/okstra_ctl/convergence.py +173 -2
- package/runtime/python/okstra_ctl/convergence_critic_verify_prompt.py +18 -0
- package/runtime/python/okstra_ctl/convergence_provenance.py +8 -0
- package/runtime/python/okstra_ctl/coverage_census.py +603 -0
- package/runtime/python/okstra_ctl/design_surfaces.py +4 -0
- package/runtime/python/okstra_ctl/direct_work.py +1 -1
- package/runtime/python/okstra_ctl/dispatch_core.py +7 -3
- package/runtime/python/okstra_ctl/doctor.py +12 -6
- package/runtime/python/okstra_ctl/domain/role.py +1 -0
- package/runtime/python/okstra_ctl/group_context.py +5 -4
- package/runtime/python/okstra_ctl/legacy_model_selection.py +7 -51
- package/runtime/python/okstra_ctl/manager_split.py +4 -1
- package/runtime/python/okstra_ctl/model_io/lines.py +1 -24
- package/runtime/python/okstra_ctl/model_io/renderers.py +54 -41
- package/runtime/python/okstra_ctl/phases/change_impact_analysis/profile.json +1 -1
- package/runtime/python/okstra_ctl/phases/change_impact_analysis/profile.md +0 -8
- package/runtime/python/okstra_ctl/phases/error_analysis/profile.json +1 -1
- package/runtime/python/okstra_ctl/phases/error_analysis/profile.md +6 -8
- package/runtime/python/okstra_ctl/phases/feature_analysis/profile.json +1 -1
- package/runtime/python/okstra_ctl/phases/feature_analysis/profile.md +0 -8
- package/runtime/python/okstra_ctl/phases/final_verification/profile.json +1 -1
- package/runtime/python/okstra_ctl/phases/final_verification/profile.md +2 -8
- package/runtime/python/okstra_ctl/phases/implementation/boundary.json +1 -1
- package/runtime/python/okstra_ctl/phases/implementation/profile.json +1 -1
- package/runtime/python/okstra_ctl/phases/implementation/profile.md +0 -6
- package/runtime/python/okstra_ctl/phases/implementation/report_assets/implementation-input.template.md +1 -1
- package/runtime/python/okstra_ctl/phases/implementation_option_selection/authoring.py +4 -3
- package/runtime/python/okstra_ctl/phases/implementation_option_selection/entry.py +1 -14
- package/runtime/python/okstra_ctl/phases/implementation_option_selection/profile.json +1 -1
- package/runtime/python/okstra_ctl/phases/implementation_option_selection/profile.md +5 -8
- package/runtime/python/okstra_ctl/phases/implementation_option_selection/spec.md +3 -3
- package/runtime/python/okstra_ctl/phases/implementation_option_selection/validation.py +15 -5
- package/runtime/python/okstra_ctl/phases/implementation_planning/authoring.py +9 -1
- package/runtime/python/okstra_ctl/phases/implementation_planning/boundary.json +1 -1
- package/runtime/python/okstra_ctl/phases/implementation_planning/instructions/plan-body-verification.md +4 -2
- package/runtime/python/okstra_ctl/phases/implementation_planning/plan_body.py +25 -9
- package/runtime/python/okstra_ctl/phases/implementation_planning/profile.json +1 -1
- package/runtime/python/okstra_ctl/phases/implementation_planning/profile.md +7 -10
- package/runtime/python/okstra_ctl/phases/improvement_discovery/profile.json +1 -1
- package/runtime/python/okstra_ctl/phases/improvement_discovery/profile.md +4 -11
- package/runtime/python/okstra_ctl/phases/project_analysis/profile.json +1 -1
- package/runtime/python/okstra_ctl/phases/project_analysis/profile.md +0 -8
- package/runtime/python/okstra_ctl/phases/release_handoff/profile.md +1 -1
- package/runtime/python/okstra_ctl/phases/release_handoff/spec.md +1 -1
- package/runtime/python/okstra_ctl/phases/requirements_discovery/profile.json +1 -1
- package/runtime/python/okstra_ctl/phases/requirements_discovery/profile.md +14 -8
- package/runtime/python/okstra_ctl/phases/requirements_discovery/spec.md +3 -3
- package/runtime/python/okstra_ctl/phases/technical_verification/profile.json +1 -1
- package/runtime/python/okstra_ctl/phases/technical_verification/profile.md +0 -4
- package/runtime/python/okstra_ctl/plan_items.py +1 -1
- package/runtime/python/okstra_ctl/render.py +10 -43
- package/runtime/python/okstra_ctl/render_final_report.py +3 -0
- package/runtime/python/okstra_ctl/report_assembly.py +15 -1
- package/runtime/python/okstra_ctl/report_finalize.py +50 -0
- package/runtime/python/okstra_ctl/report_html/render.py +3 -0
- package/runtime/python/okstra_ctl/report_synthesis_packet.py +1 -2
- package/runtime/python/okstra_ctl/run.py +78 -409
- package/runtime/python/okstra_ctl/wizard/__init__.py +2 -24
- package/runtime/python/okstra_ctl/wizard/cli.py +3 -6
- package/runtime/python/okstra_ctl/wizard/confirmation.py +3 -35
- package/runtime/python/okstra_ctl/wizard/engine.py +2 -4
- package/runtime/python/okstra_ctl/wizard/ids.py +1 -88
- package/runtime/python/okstra_ctl/wizard/registry.py +36 -228
- package/runtime/python/okstra_ctl/wizard/render.py +2 -2
- package/runtime/python/okstra_ctl/wizard/roles.py +1 -3
- package/runtime/python/okstra_ctl/wizard/sources.py +9 -40
- package/runtime/python/okstra_ctl/wizard/state.py +36 -145
- package/runtime/python/okstra_ctl/wizard/statefile.py +27 -128
- package/runtime/python/okstra_ctl/wizard/steps_identity.py +22 -10
- package/runtime/python/okstra_ctl/wizard/steps_options.py +5 -4
- package/runtime/python/okstra_ctl/wizard/steps_roles.py +15 -565
- package/runtime/python/okstra_ctl/worker_prompt_policy.py +9 -2
- package/runtime/schemas/business-flow-v1.schema.json +847 -0
- package/runtime/schemas/convergence-groups-v2.0.schema.json +7 -0
- package/runtime/skills/okstra-explain-flow/SKILL.md +42 -0
- package/runtime/skills/okstra-inspect/facets/history.md +5 -5
- package/runtime/skills/okstra-run/SKILL.md +2 -2
- package/runtime/templates/reports/business-flow.template.md +106 -0
- package/runtime/templates/reports/html/base.template.html +14 -1
- package/runtime/templates/reports/html/business-flow.template.html +31 -0
- package/runtime/templates/reports/html/i18n/en.json +1 -0
- package/runtime/templates/reports/html/i18n/ko.json +1 -0
- package/runtime/templates/worker-prompt-preamble.md +11 -2
- package/runtime/validators/checks/validate-prompt-metadata-01.py +10 -10
- package/runtime/validators/validate-run.py +70 -21
- package/runtime/validators/validate_analysis_report.py +21 -21
- package/runtime/python/okstra_ctl/workers.py +0 -133
|
@@ -60,6 +60,14 @@ from .convergence_store import (
|
|
|
60
60
|
write_final_state_atomic,
|
|
61
61
|
write_json_atomic,
|
|
62
62
|
)
|
|
63
|
+
from .coverage_census import (
|
|
64
|
+
CensusError,
|
|
65
|
+
audit_census,
|
|
66
|
+
audit_filename,
|
|
67
|
+
census_filename,
|
|
68
|
+
gapfill_prompt_body,
|
|
69
|
+
)
|
|
70
|
+
from .path_resolve import relative_to_project_root
|
|
63
71
|
from .execution_identity import ExecutionManifest
|
|
64
72
|
from .execution_manifest import read_execution_manifest_view
|
|
65
73
|
from .final_report_schema import load_named_schema, validate as validate_schema
|
|
@@ -323,6 +331,10 @@ _CLI_EPILOG = r"""Usage:
|
|
|
323
331
|
okstra convergence reverify-prompt --run-manifest <path> --plan <path> \
|
|
324
332
|
--worker <worker-id>
|
|
325
333
|
okstra convergence apply-critic-gaps --work-state <path> --results <path>
|
|
334
|
+
okstra convergence census-audit --run-manifest <path> \
|
|
335
|
+
--result <worker>=<path>... [--gapfill <worker>=<path>...]
|
|
336
|
+
okstra convergence census-gapfill-prompt --run-manifest <path> \
|
|
337
|
+
--worker <worker-id>
|
|
326
338
|
okstra convergence apply-acceptance-critic --work-state <path> \
|
|
327
339
|
--results <path>
|
|
328
340
|
okstra convergence finalize --work-state <path> --output <path>
|
|
@@ -475,6 +487,51 @@ def _parser() -> argparse.ArgumentParser:
|
|
|
475
487
|
)
|
|
476
488
|
critic_verify_prompt.add_argument("--worker", required=True)
|
|
477
489
|
|
|
490
|
+
census_audit = subparsers.add_parser(
|
|
491
|
+
"census-audit",
|
|
492
|
+
help="audit each analyser's coverage-census verdicts",
|
|
493
|
+
description=(
|
|
494
|
+
"Read every analysis worker's `Coverage Verdicts` against this run's "
|
|
495
|
+
"coverage census and write state/coverage-census-audit-<task-type>-"
|
|
496
|
+
"<seq>.json: per worker the cells still without a usable verdict, "
|
|
497
|
+
"the cell x worker matrix, the cells one worker judged `finding` and "
|
|
498
|
+
"another `clean`, and per-cell grouping suggestions. Run it after the "
|
|
499
|
+
"initial batch, and again with `--gapfill` after the one gap-fill "
|
|
500
|
+
"dispatch. Cells left unjudged are recorded as warnings; they never "
|
|
501
|
+
"change the exit code. A run whose profile has no census writes "
|
|
502
|
+
"nothing and exits 0."
|
|
503
|
+
),
|
|
504
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
505
|
+
)
|
|
506
|
+
census_audit.add_argument("--run-manifest", type=Path, required=True)
|
|
507
|
+
census_audit.add_argument(
|
|
508
|
+
"--result", action="append", default=[], required=True,
|
|
509
|
+
metavar="<worker>=<path>",
|
|
510
|
+
help="one analysis worker's initial result file (repeatable); a path "
|
|
511
|
+
"that does not exist counts as a missing result",
|
|
512
|
+
)
|
|
513
|
+
census_audit.add_argument(
|
|
514
|
+
"--gapfill", action="append", default=[], metavar="<worker>=<path>",
|
|
515
|
+
help="that worker's census-gapfill result file (repeatable); a path that "
|
|
516
|
+
"does not exist records a failed gap-fill",
|
|
517
|
+
)
|
|
518
|
+
|
|
519
|
+
census_gapfill_prompt = subparsers.add_parser(
|
|
520
|
+
"census-gapfill-prompt",
|
|
521
|
+
help="render one analyser's census gap-fill instructions to stdout",
|
|
522
|
+
description=(
|
|
523
|
+
"Print the census gap-fill instruction body for one analysis worker "
|
|
524
|
+
"from the latest `census-audit`: only the cells that worker left "
|
|
525
|
+
"without a usable verdict, grouped by why. The lead writes it "
|
|
526
|
+
"verbatim to the file the prompt materializer's `--instruction` "
|
|
527
|
+
"takes, with `--dispatch-kind census-gapfill`. Refused for a worker "
|
|
528
|
+
"with no unjudged cell or one that already had its single gap-fill."
|
|
529
|
+
),
|
|
530
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
531
|
+
)
|
|
532
|
+
census_gapfill_prompt.add_argument("--run-manifest", type=Path, required=True)
|
|
533
|
+
census_gapfill_prompt.add_argument("--worker", required=True)
|
|
534
|
+
|
|
478
535
|
apply_critic = subparsers.add_parser(
|
|
479
536
|
"apply-critic-gaps",
|
|
480
537
|
help="apply one coverage-critic verification batch",
|
|
@@ -1325,7 +1382,7 @@ def _group_from_fields(
|
|
|
1325
1382
|
origin_worker, separator, origin_item = scalar.get("origin", "").partition(":")
|
|
1326
1383
|
if not separator or discovered.get(origin_worker, {}).get("itemId") != origin_item:
|
|
1327
1384
|
raise ConvergenceContractError("Origin must identify one Source item")
|
|
1328
|
-
|
|
1385
|
+
group = {
|
|
1329
1386
|
"findingId": finding_id, "summary": scalar.get("summary", ""),
|
|
1330
1387
|
"category": scalar.get("category", ""),
|
|
1331
1388
|
"ticketIds": [item.strip() for item in scalar.get("tickets", "").split(",") if item.strip()],
|
|
@@ -1334,6 +1391,10 @@ def _group_from_fields(
|
|
|
1334
1391
|
"discoveredBy": discovered,
|
|
1335
1392
|
"sourceItems": source_items,
|
|
1336
1393
|
}
|
|
1394
|
+
cells = [cell.strip() for cell in scalar.get("cells", "").split(",") if cell.strip()]
|
|
1395
|
+
if cells:
|
|
1396
|
+
group["cellRefs"] = list(dict.fromkeys(cells))
|
|
1397
|
+
return group
|
|
1337
1398
|
|
|
1338
1399
|
|
|
1339
1400
|
def _group_source_provenance(
|
|
@@ -1579,6 +1640,7 @@ def _critic_verify_prompt(args: argparse.Namespace) -> str:
|
|
|
1579
1640
|
result_path, audit_path = critic_result_paths(
|
|
1580
1641
|
groups, critic_provider=provider,
|
|
1581
1642
|
project_root=authority.project_root, run_dir=authority.run_dir,
|
|
1643
|
+
recorded_result=_settled_critic_result(authority.payload),
|
|
1582
1644
|
)
|
|
1583
1645
|
return critic_verify_prompt_body(
|
|
1584
1646
|
task_key=task_key,
|
|
@@ -1589,6 +1651,109 @@ def _critic_verify_prompt(args: argparse.Namespace) -> str:
|
|
|
1589
1651
|
)
|
|
1590
1652
|
|
|
1591
1653
|
|
|
1654
|
+
def _worker_paths(raw_pairs: list[str], label: str) -> dict[str, Path]:
|
|
1655
|
+
paths: dict[str, Path] = {}
|
|
1656
|
+
for raw in raw_pairs:
|
|
1657
|
+
worker, value = _split_pair(raw, label)
|
|
1658
|
+
if worker in paths:
|
|
1659
|
+
raise ConvergenceContractError(f"{label} names `{worker}` twice")
|
|
1660
|
+
paths[worker] = Path(value)
|
|
1661
|
+
return paths
|
|
1662
|
+
|
|
1663
|
+
|
|
1664
|
+
def _read_if_present(path: Path) -> str | None:
|
|
1665
|
+
return path.read_text(encoding="utf-8") if path.is_file() else None
|
|
1666
|
+
|
|
1667
|
+
|
|
1668
|
+
def _census_audit(args: argparse.Namespace) -> dict[str, Any]:
|
|
1669
|
+
"""Audit the census verdicts. Unjudged cells are data, never an error."""
|
|
1670
|
+
authority = validated_run_authority(args.run_manifest)
|
|
1671
|
+
state_dir = authority.run_dir / "state"
|
|
1672
|
+
census_path = state_dir / census_filename(
|
|
1673
|
+
authority.task_type, authority.state_sequence
|
|
1674
|
+
)
|
|
1675
|
+
summary: dict[str, Any] = {"ok": True, "operation": "census-audit"}
|
|
1676
|
+
if not census_path.is_file():
|
|
1677
|
+
return {**summary, "census": "absent"}
|
|
1678
|
+
census = load_owned_json_object(census_path)
|
|
1679
|
+
result_paths = _worker_paths(args.result, "--result")
|
|
1680
|
+
gapfill_paths = _worker_paths(args.gapfill, "--gapfill")
|
|
1681
|
+
unrostered = sorted(set(gapfill_paths) - set(result_paths))
|
|
1682
|
+
if unrostered:
|
|
1683
|
+
raise ConvergenceContractError(
|
|
1684
|
+
f"--gapfill names {unrostered}, which no --result names"
|
|
1685
|
+
)
|
|
1686
|
+
audit = audit_census(
|
|
1687
|
+
census,
|
|
1688
|
+
{worker: _read_if_present(path) for worker, path in result_paths.items()},
|
|
1689
|
+
{worker: _read_if_present(path) for worker, path in gapfill_paths.items()},
|
|
1690
|
+
)
|
|
1691
|
+
for row in audit["workers"]:
|
|
1692
|
+
row["resultPath"] = relative_to_project_root(
|
|
1693
|
+
result_paths[row["workerId"]], authority.project_root
|
|
1694
|
+
)
|
|
1695
|
+
if row["workerId"] in gapfill_paths:
|
|
1696
|
+
row["gapfillPath"] = relative_to_project_root(
|
|
1697
|
+
gapfill_paths[row["workerId"]], authority.project_root
|
|
1698
|
+
)
|
|
1699
|
+
output = state_dir / audit_filename(authority.task_type, authority.state_sequence)
|
|
1700
|
+
write_json_atomic(output, audit)
|
|
1701
|
+
return {
|
|
1702
|
+
**summary,
|
|
1703
|
+
"census": "present",
|
|
1704
|
+
"path": str(output),
|
|
1705
|
+
"crossCheck": audit["crossCheck"],
|
|
1706
|
+
"gapfillNeeded": audit["gapfillNeeded"],
|
|
1707
|
+
"unverdicted": {
|
|
1708
|
+
row["workerId"]: len(row["unverdicted"]) for row in audit["workers"]
|
|
1709
|
+
},
|
|
1710
|
+
"disagreements": len(audit["disagreements"]),
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
|
|
1714
|
+
def _census_gapfill_prompt(args: argparse.Namespace) -> str:
|
|
1715
|
+
authority = validated_run_authority(args.run_manifest)
|
|
1716
|
+
state_dir = authority.run_dir / "state"
|
|
1717
|
+
audit_path = state_dir / audit_filename(authority.task_type, authority.state_sequence)
|
|
1718
|
+
if not audit_path.is_file():
|
|
1719
|
+
raise ConvergenceContractError(
|
|
1720
|
+
"the gap-fill prompt reads the census audit; run "
|
|
1721
|
+
f"`okstra convergence census-audit` first: {audit_path}"
|
|
1722
|
+
)
|
|
1723
|
+
census_path = state_dir / census_filename(authority.task_type, authority.state_sequence)
|
|
1724
|
+
return gapfill_prompt_body(
|
|
1725
|
+
load_owned_json_object(audit_path),
|
|
1726
|
+
args.worker,
|
|
1727
|
+
task_key=authority.task_key,
|
|
1728
|
+
analysis_packet_path=_manifest_authority_string(
|
|
1729
|
+
authority.payload, "analysisPacketPath"
|
|
1730
|
+
),
|
|
1731
|
+
census_path=relative_to_project_root(census_path, authority.project_root),
|
|
1732
|
+
)
|
|
1733
|
+
|
|
1734
|
+
|
|
1735
|
+
def _settled_critic_result(manifest: Mapping[str, Any]) -> str:
|
|
1736
|
+
"""critic dispatch 의 가장 최근 `ok` attempt 가 기록한 resultPath. 없으면 ''."""
|
|
1737
|
+
invocations = manifest.get("invocations")
|
|
1738
|
+
attempts = manifest.get("attempts")
|
|
1739
|
+
if not isinstance(invocations, list) or not isinstance(attempts, list):
|
|
1740
|
+
return ""
|
|
1741
|
+
critic_refs = {
|
|
1742
|
+
row.get("invocationRef") for row in invocations
|
|
1743
|
+
if isinstance(row, Mapping) and row.get("dispatchKind") == "critic"
|
|
1744
|
+
}
|
|
1745
|
+
settled = [
|
|
1746
|
+
row for row in attempts
|
|
1747
|
+
if isinstance(row, Mapping) and row.get("invocationRef") in critic_refs
|
|
1748
|
+
and row.get("status") == "ok"
|
|
1749
|
+
and isinstance(row.get("resultPath"), str) and row["resultPath"]
|
|
1750
|
+
]
|
|
1751
|
+
if not settled:
|
|
1752
|
+
return ""
|
|
1753
|
+
latest = max(settled, key=lambda row: (str(row.get("finishedAt") or ""), row.get("attempt") or 0))
|
|
1754
|
+
return latest["resultPath"]
|
|
1755
|
+
|
|
1756
|
+
|
|
1592
1757
|
def _execute(args: argparse.Namespace) -> tuple[str, Path]:
|
|
1593
1758
|
operations: dict[str, Any] = {
|
|
1594
1759
|
"prepare-groups": _prepare_groups,
|
|
@@ -1626,9 +1791,15 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
1626
1791
|
if args.operation == "critic-verify-prompt":
|
|
1627
1792
|
print(_critic_verify_prompt(args), end="")
|
|
1628
1793
|
return 0
|
|
1794
|
+
if args.operation == "census-audit":
|
|
1795
|
+
print(json.dumps(_census_audit(args), ensure_ascii=False))
|
|
1796
|
+
return 0
|
|
1797
|
+
if args.operation == "census-gapfill-prompt":
|
|
1798
|
+
print(_census_gapfill_prompt(args), end="")
|
|
1799
|
+
return 0
|
|
1629
1800
|
action, path = _execute(args)
|
|
1630
1801
|
except (ConvergenceContractError, VerdictBlockError, ReverifyPromptError,
|
|
1631
|
-
CriticVerifyPromptError,
|
|
1802
|
+
CriticVerifyPromptError, CensusError,
|
|
1632
1803
|
json.JSONDecodeError, ValueError) as exc:
|
|
1633
1804
|
print(f"error: {exc}", file=sys.stderr)
|
|
1634
1805
|
return 2
|
|
@@ -77,6 +77,7 @@ def critic_result_paths(
|
|
|
77
77
|
critic_provider: str,
|
|
78
78
|
project_root: Path,
|
|
79
79
|
run_dir: Path,
|
|
80
|
+
recorded_result: str = "",
|
|
80
81
|
) -> tuple[str, str]:
|
|
81
82
|
"""critic 결과 파일과 그 감사 사이드카의 프로젝트 상대 경로.
|
|
82
83
|
|
|
@@ -85,7 +86,24 @@ def critic_result_paths(
|
|
|
85
86
|
§"Coverage critic pass"). 접미사는 분석자 결과와 같은 규칙으로 그룹의
|
|
86
87
|
`runManifestPath` 에서 읽는다. 파일이 없으면 critic 결과가 아직 수집되지
|
|
87
88
|
않은 것이라 거절한다 — gap 검증은 critic 결과 뒤에만 온다.
|
|
89
|
+
|
|
90
|
+
`recorded_result` 는 매니페스트 attempt 원장이 critic 의 마지막 `ok` attempt
|
|
91
|
+
에 적은 경로다. 있으면 이름 규칙보다 먼저 쓴다 — 리드가 run seq 로 이름을
|
|
92
|
+
붙여도(실측 2026-10-03, fontsninja-v3-site dev-11118 requirements-discovery
|
|
93
|
+
002: prompts 002, workerResults 001) 실제로 수집된 파일을 가리킨다.
|
|
88
94
|
"""
|
|
95
|
+
if recorded_result:
|
|
96
|
+
recorded = Path(recorded_result)
|
|
97
|
+
result = recorded if recorded.is_absolute() else Path(project_root) / recorded
|
|
98
|
+
if not result.is_file():
|
|
99
|
+
raise CriticVerifyPromptError(
|
|
100
|
+
f"the critic attempt recorded {result} but the file is missing"
|
|
101
|
+
)
|
|
102
|
+
result_rel = _project_relative(project_root, result)
|
|
103
|
+
try:
|
|
104
|
+
return result_rel, audit_sidecar_rel(result_rel)
|
|
105
|
+
except WorkerArtifactPathError as exc:
|
|
106
|
+
raise CriticVerifyPromptError(str(exc)) from exc
|
|
89
107
|
if not _nonempty_string(critic_provider):
|
|
90
108
|
raise CriticVerifyPromptError("run manifest has no critic assignment provider")
|
|
91
109
|
suffix = worker_result_suffix(Path(run_dir), groups)
|
|
@@ -26,6 +26,7 @@ from okstra_ctl.execution_manifest import read_execution_manifest_view
|
|
|
26
26
|
from okstra_ctl.json_boundary import (
|
|
27
27
|
load_owned_object,
|
|
28
28
|
)
|
|
29
|
+
from okstra_ctl.worker_prompt_policy import CENSUS_GAPFILL_DISPATCH_KIND
|
|
29
30
|
|
|
30
31
|
GROUPS_BASENAME_RE = re.compile(
|
|
31
32
|
r"^convergence-groups-(?P<suffix>[a-z][a-z-]*?-\d{3})\.json$"
|
|
@@ -223,6 +224,13 @@ def provenance_errors(
|
|
|
223
224
|
text = contents[worker]
|
|
224
225
|
if text is None or id_occurs_wordbounded(text, item_id):
|
|
225
226
|
continue
|
|
227
|
+
gapfill = read_canonical_worker_result(
|
|
228
|
+
worker_results_dir, worker, f"{CENSUS_GAPFILL_DISPATCH_KIND}-{suffix}"
|
|
229
|
+
)
|
|
230
|
+
# A finding the census gap-fill raised lives in that worker's gap-fill
|
|
231
|
+
# result, not in its first one.
|
|
232
|
+
if gapfill is not None and id_occurs_wordbounded(gapfill, item_id):
|
|
233
|
+
continue
|
|
226
234
|
errors.append(
|
|
227
235
|
f"convergence groups `{groups_label}` group {finding_id} "
|
|
228
236
|
f"cites source item `{worker}:{item_id}`, but that ID does not "
|