Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions project/TICKETS.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,4 +190,5 @@ project analysis or human-owned ticket inputs.
| **ticket-189** | [`README.md`](./ticket-189/README.md) | - | - | - | - | - |
| **ticket-190** | [`README.md`](./ticket-190/README.md) | - | - | - | - | - |
| **ticket-191** | [`README.md`](./ticket-191/README.md) | - | - | - | - | - |
| **ticket-193** | [`README.md`](./ticket-193/README.md) | - | - | - | - | - |
<!-- AUTO:TICKET_INDEX:END -->
33 changes: 33 additions & 0 deletions project/ticket-193/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Ticket 193: Reduce cyclomatic complexity: publish (CC=16)

- **ID**: ticket-193
- **Owner**: unresolved:human
- **Status**: IN_PROGRESS
- **Workflow state**: EDIT
- **Created**: 2026-09-20

## Goal and scope

SESSION_EXECUTION_AUTHORIZATION: the planfile queue owner handed STARTER-612
to this session with explicit completion instructions (checks, commit,
`planfile ticket done STARTER-612`).

`project/analysis.toon.yaml` reports `publish` defined in
`src/koru/ticket_command/publication.py:9` with cyclomatic complexity 16
(limit 15). Extract exact-head verification, direct-main publication,
pull-request listing/creation/binding, the protected validator merge
invocation and post-merge observation into focused module-level helpers with
unchanged behaviour, bringing the function under the limit 15.

## Acceptance criteria

- [ ] AC-01: re-measured code2llm complexity for `publish` is below 15.
- [ ] AC-02: delivery and consumer suites pass unmodified; a new test pins
the previously uncovered pull-request creation path.
- [ ] AC-03: ruff passes on the touched modules.
- [ ] AC-04: `project/governance-check.sh --base origin/main` passes.

## Tracking boundary

This directory contains the minimal reviewed intent. Optional participant
prose and raw command logs are not required delivery output.
97 changes: 97 additions & 0 deletions project/ticket-193/intent.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
{
"schema": "new-project.intent/v3",
"ticket": "ticket-193",
"summary": "Reduce cyclomatic complexity: publish (CC=16)",
"workstream": "application",
"classification": {
"kind": "SERVICE",
"priority": "P2",
"origin": "health"
},
"allowedPaths": [
"project/ticket-193/**",
"TODO.md",
"project/TICKETS.md",
"src/koru/ticket_command/publication.py",
"tests/test_ticket_command_delivery.py"
],
"forbiddenPaths": [
"project/ticket-*/user-*.md"
],
"stacks": [],
"dependsOn": [],
"conflictsWith": [],
"integrationTicket": null,
"delivery": {
"acceptedBaseSha": "9bbd7ff89a907df1affbea1e1578956940ccdc49",
"targetBranch": "main",
"outcome": "publish in src/koru/ticket_command/publication.py drops from cyclomatic complexity 16 to below the code2llm limit 15 by extracting behavior-preserving module-level helpers (_require_verified_head, _publish_direct_main, _verified_pulls, _create_pull_request, _merge_through_validator, _observed_pull) and delegating from a small publish that only dispatches between main-only and validator delivery. HEAD verification, the main-only push/ls-remote gate, ticket-branch prefix enforcement, pull-request identity/ambiguity/binding checks, PR creation argv and body text, the protected validator merge invocation with pinned environment, the pending-merge RuntimeError and both published result payloads are byte-for-byte equivalent; the duplicated gh pr list argv collapses into _verified_pulls. A new test pins the previously uncovered pull-request creation path; the existing delivery suites pass unmodified.",
"nonGoals": [
"Changing any observable command argv, environment, diagnostic message or result payload",
"Regenerating project/analysis.toon.yaml or project/planfile-tickets.yaml (integration-owned artifacts)",
"Renaming or removing the public publish function or its signature",
"Reformatting pre-existing spans of src/koru/ticket_command/publication.py outside the refactored function"
],
"complexity": "S",
"estimatedMinutes": 30,
"budgets": {
"maxImplementationFiles": 2,
"maxAffectedComponents": 1,
"maxPublicInterfaceChanges": 0,
"maxRuntimeDependencies": 0
},
"architecture": {
"status": "accepted",
"decision": "Decompose publish into single-responsibility private module functions mirroring the module's existing function-oriented style: exact-head verification, direct-main publication, filtered pull-request listing, pull-request creation, the protected validator merge invocation and post-merge observation. The public function keeps workspace/head extraction and delivery dispatch only; each helper returns explicit values so behavior is equivalent and the public surface is unchanged.",
"components": [
{
"name": "ticket-command-delivery",
"paths": [
"src/koru/ticket_command/publication.py",
"tests/test_ticket_command_delivery.py"
]
}
],
"responsibilityChanges": false,
"interfaceChanges": [],
"dataChanges": [],
"ui": {
"impact": "none",
"states": [],
"evidence": []
},
"rollback": "Revert the commit; the original single publish body is restored verbatim."
},
"runtimeDependencies": [],
"validation": [
{
"criterion": "AC-01",
"commands": [
"code2llm <worktree> -f all -o /tmp/opencode/code2llm-ticket193 --no-chunk --exclude '*.md' (complexity re-measured on publish)"
],
"evidence": "Re-run code2llm reports CC < 15 for publish."
},
{
"criterion": "AC-02",
"commands": [
"PYTHONPATH=src python -m pytest tests/test_ticket_command_delivery.py tests/test_ticket_command.py tests/test_ticket_command_batch.py tests/test_ticket_command_reporting.py -q"
],
"evidence": "New behavior-pinning test and existing consumer suites pass unmodified."
},
{
"criterion": "AC-03",
"commands": [
"ruff check src/koru/ticket_command/publication.py tests/test_ticket_command_delivery.py"
],
"evidence": "Lint passes on the refactored module and the extended test module."
},
{
"criterion": "AC-04",
"commands": [
"bash project/governance-check.sh --base origin/main"
],
"evidence": "Governance gate passes with zero errors."
}
]
}
}
199 changes: 108 additions & 91 deletions src/koru/ticket_command/publication.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,31 +9,72 @@
def publish(profile: dict, state: dict, folder: Path) -> dict:
workspace = Path(state["workspace"])
head = state["head"]
if git(workspace, "rev-parse", "HEAD") != head:
raise ValueError("HEAD changed after verification; publication stopped")
_require_verified_head(workspace, head)
branch = git(workspace, "branch", "--show-current")
if profile["delivery"] == "main-only":
if branch != "main":
raise ValueError("main-only publication requires main")
git(workspace, "push", "origin", "main")
observed = git(workspace, "ls-remote", "origin", "refs/heads/main").split()[0]
if observed != head:
raise RuntimeError("remote main no longer matches the tested commit")
return {"state": "published", "publication": "direct-main", "head": head}
return _publish_direct_main(workspace, head, branch)
return _publish_through_validator(profile, state, folder, workspace, head, branch)


def _require_verified_head(workspace: Path, head: str) -> None:
if git(workspace, "rev-parse", "HEAD") != head:
raise ValueError("HEAD changed after verification; publication stopped")


def _publish_direct_main(workspace: Path, head: str, branch: str) -> dict:
if branch != "main":
raise ValueError("main-only publication requires main")
git(workspace, "push", "origin", "main")
observed = git(workspace, "ls-remote", "origin", "refs/heads/main").split()[0]
if observed != head:
raise RuntimeError("remote main no longer matches the tested commit")
return {"state": "published", "publication": "direct-main", "head": head}


def _publish_through_validator(
profile: dict, state: dict, folder: Path, workspace: Path, head: str, branch: str
) -> dict:
prefix = state["ticket"].replace("ticket-", "ticket/", 1) + "-"
if not branch.startswith(prefix):
raise ValueError("validator delivery requires the allocated ticket branch")
git(workspace, "push", "origin", branch)
pulls = json.loads(
matching = [
p
for p in _listed_pulls(workspace, profile["repository"], branch)
if p["headRefOid"] == head and p["state"] in {"OPEN", "MERGED"}
]
if len(matching) > 1:
raise ValueError("ambiguous pull-request identity")
if not matching:
_create_pull_request(profile, state, folder, workspace, branch)
matching = _listed_pulls(workspace, profile["repository"], branch)
if len(matching) != 1 or matching[0]["headRefOid"] != head:
raise ValueError("pull request does not bind the verified head")
pull = matching[0]
if pull["state"] != "MERGED":
_merge_through_validator(profile, state, folder, workspace, pull, head)
observed = _observed_pull(workspace, profile["repository"], pull["number"])
if observed["state"] != "MERGED" or observed["headRefOid"] != head:
raise RuntimeError("protected validation/merge remains pending; execution will not repeat")
return {
"state": "published",
"publication": "validator",
"head": head,
"pull_request": observed["url"],
"merge_commit": observed["mergeCommit"]["oid"],
}


def _listed_pulls(workspace: Path, repository: str, branch: str) -> list[dict]:
return json.loads(
command(
workspace,
[
"gh",
"pr",
"list",
"--repo",
profile["repository"],
repository,
"--head",
branch,
"--state",
Expand All @@ -43,95 +84,71 @@ def publish(profile: dict, state: dict, folder: Path) -> dict:
],
)
)
matching = [p for p in pulls if p["headRefOid"] == head and p["state"] in {"OPEN", "MERGED"}]
if len(matching) > 1:
raise ValueError("ambiguous pull-request identity")
if not matching:
body = folder / "pull-request.md"
body.write_text(
f"Resolve {profile['url']} under {state['ticket']}.\n\nLocal verification passed. "
"Independent OneDev/Validator checks are required before merge.\n"
)
command(
workspace,
[
"gh",
"pr",
"create",
"--repo",
profile["repository"],
"--base",
"main",
"--head",
branch,
"--title",
f"fix: resolve issue {profile['number']}",
"--body-file",
str(body),
],
)
matching = json.loads(
command(
workspace,
[
"gh",
"pr",
"list",
"--repo",
profile["repository"],
"--head",
branch,
"--json",
"number,headRefOid,state,url",
],
)
)
if len(matching) != 1 or matching[0]["headRefOid"] != head:
raise ValueError("pull request does not bind the verified head")
pr = matching[0]
if pr["state"] != "MERGED":
adapter = profile["validator"]
command(
workspace,
[
adapter["launcher"],
"--repository",
profile["repository"],
"--pull-request",
str(pr["number"]),
"--ticket",
state["ticket"],
"--expected-head-sha",
head,
"--key-file",
adapter["key_file"],
"--output-dir",
str(folder / "validator"),
"--merge",
],
env=validator_environment(adapter, profile["primary"]),
)
observed = json.loads(


def _create_pull_request(profile: dict, state: dict, folder: Path, workspace: Path, branch: str) -> None:
body = folder / "pull-request.md"
body.write_text(
f"Resolve {profile['url']} under {state['ticket']}.\n\nLocal verification passed. "
"Independent OneDev/Validator checks are required before merge.\n"
)
command(
workspace,
[
"gh",
"pr",
"create",
"--repo",
profile["repository"],
"--base",
"main",
"--head",
branch,
"--title",
f"fix: resolve issue {profile['number']}",
"--body-file",
str(body),
],
)


def _merge_through_validator(profile: dict, state: dict, folder: Path, workspace: Path, pull: dict, head: str) -> None:
adapter = profile["validator"]
command(
workspace,
[
adapter["launcher"],
"--repository",
profile["repository"],
"--pull-request",
str(pull["number"]),
"--ticket",
state["ticket"],
"--expected-head-sha",
head,
"--key-file",
adapter["key_file"],
"--output-dir",
str(folder / "validator"),
"--merge",
],
env=validator_environment(adapter, profile["primary"]),
)


def _observed_pull(workspace: Path, repository: str, number: int) -> dict:
return json.loads(
command(
workspace,
[
"gh",
"pr",
"view",
str(pr["number"]),
str(number),
"--repo",
profile["repository"],
repository,
"--json",
"state,headRefOid,mergeCommit,url",
],
)
)
if observed["state"] != "MERGED" or observed["headRefOid"] != head:
raise RuntimeError("protected validation/merge remains pending; execution will not repeat")
return {
"state": "published",
"publication": "validator",
"head": head,
"pull_request": observed["url"],
"merge_commit": observed["mergeCommit"]["oid"],
}
Loading
Loading