opencode-codeops 2.1.0 → 2.2.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.
@@ -13,6 +13,7 @@ import re
13
13
  import sys
14
14
  from dataclasses import asdict, dataclass
15
15
  from pathlib import Path
16
+ from typing import Iterator
16
17
 
17
18
 
18
19
  IMPLEMENTS_RE = re.compile(r"^>[ \t]*\*\*Implements\*\*:[ \t]*(.+?)[ \t]*$", re.MULTILINE)
@@ -21,7 +22,9 @@ TARGET_RE = re.compile(
21
22
  r"(?:RD-(?:[A-Za-z0-9]+-)*\d+|T-\d+|REQ-[A-Za-z0-9]+(?:-[A-Za-z0-9]+)*)"
22
23
  r"(?![A-Za-z0-9_-])"
23
24
  )
24
- TASK_RE = re.compile(r"^-\s*\[([ xX~!])\]\s+(.+?)\s*$", re.MULTILINE)
25
+ TASK_RE = re.compile(r"^-\s*\[([ xX~!])\]\s+(.+?)\s*$")
26
+ FENCE_OPEN_RE = re.compile(r"^ {0,3}(`{3,}|~{3,})")
27
+ TASK_ID_RE = re.compile(r"^\s*(?:\*\*)?([0-9]+\.[0-9]+\.[0-9]+|T-[0-9]+\.[0-9]+)(?=[\s*—–-]|$)")
25
28
  BLOCKED_REASON_RE = re.compile(r"(?:blocked|reason)\s*:\s*\S", re.IGNORECASE)
26
29
  _PROGRESS_WIDTH = 10
27
30
  _PROGRESS_FILLED = "█"
@@ -67,9 +70,70 @@ def parse_implements(index_text: str) -> tuple[str, ...]:
67
70
  return tuple(dict.fromkeys(TARGET_RE.findall(match.group(1))))
68
71
 
69
72
 
73
+ def _task_lines(execution_text: str) -> Iterator[tuple[str, str, str]]:
74
+ """Yield ``(marker, text, task_id)`` for every real execution-task line.
75
+
76
+ A line qualifies when it carries one of the four checkbox markers outside a
77
+ fenced code block and its text begins with a task id. Fences follow the
78
+ Markdown shape: an opening run of three or more backticks or tildes (at
79
+ most three leading spaces, optional info string) hides everything up to a
80
+ closing run of the same character with at least the same length and nothing
81
+ else on the line; an unclosed fence hides the rest of the document. An id
82
+ is ``N.N.N`` or ``T-N.N`` after an optional ``**`` opener and must be
83
+ followed by whitespace, ``*``, a dash, or the end of the line, so
84
+ near-misses such as ``1.1.1.1``, ``T-05.1x``, and ``1.1.1:`` stay ignored.
85
+ Duplicate ids are yielded; the caller decides which occurrence counts.
86
+ """
87
+ fence_char = ""
88
+ fence_length = 0
89
+ for line in execution_text.splitlines():
90
+ line = line.rstrip()
91
+ if fence_char:
92
+ indent = len(line) - len(line.lstrip(" "))
93
+ body = line[indent:] if indent <= 3 else ""
94
+ if len(body) >= fence_length and body == fence_char * len(body):
95
+ fence_char = ""
96
+ continue
97
+ opener = FENCE_OPEN_RE.match(line)
98
+ if opener:
99
+ fence_char = opener.group(1)[0]
100
+ fence_length = len(opener.group(1))
101
+ continue
102
+ task = TASK_RE.match(line)
103
+ task_id = TASK_ID_RE.match(task.group(2)) if task else None
104
+ if task_id is None:
105
+ continue
106
+ yield task.group(1).lower(), task.group(2).strip(), task_id.group(1)
107
+
108
+
70
109
  def parse_tasks(execution_text: str) -> tuple[Task, ...]:
71
- """Parse only the four authoritative execution checklist markers."""
72
- return tuple(Task(marker.lower(), text.strip()) for marker, text in TASK_RE.findall(execution_text))
110
+ """Return the execution tasks a plan document really declares.
111
+
112
+ Fenced examples and checkbox lines without a task id (deliverable and
113
+ acceptance lists, quoted templates) are ignored, and a task id that appears
114
+ more than once counts once, from its first occurrence in document order.
115
+ """
116
+ tasks: list[Task] = []
117
+ seen: set[str] = set()
118
+ for marker, text, task_id in _task_lines(execution_text):
119
+ if task_id in seen:
120
+ continue
121
+ seen.add(task_id)
122
+ tasks.append(Task(marker, text))
123
+ return tuple(tasks)
124
+
125
+
126
+ def has_checkbox_lines(execution_text: str) -> bool:
127
+ """Return whether a document carries at least one checklist line.
128
+
129
+ Presence is deliberately id-agnostic: legacy execution plans may list
130
+ tasks without ids, and migration admission must stay tolerant of them.
131
+ Counting correctness is ``parse_tasks``' job; this predicate only answers
132
+ "does the document look like it declares tasks at all". Fences are not
133
+ stripped here for the same reason — a quoted checklist still proves the
134
+ document format.
135
+ """
136
+ return any(TASK_RE.match(line.rstrip()) for line in execution_text.splitlines())
73
137
 
74
138
 
75
139
  def next_task(tasks: tuple[Task, ...]) -> Task | None:
@@ -17,9 +17,21 @@ from dataclasses import dataclass
17
17
  from pathlib import Path
18
18
 
19
19
  try:
20
- from scripts.codeops_plan import BLOCKED_REASON_RE, IMPLEMENTS_RE, parse_implements, parse_tasks
20
+ from scripts.codeops_plan import (
21
+ BLOCKED_REASON_RE,
22
+ IMPLEMENTS_RE,
23
+ has_checkbox_lines,
24
+ parse_implements,
25
+ parse_tasks,
26
+ )
21
27
  except ModuleNotFoundError: # Direct execution adds scripts/, not the repository root, to sys.path.
22
- from codeops_plan import BLOCKED_REASON_RE, IMPLEMENTS_RE, parse_implements, parse_tasks
28
+ from codeops_plan import (
29
+ BLOCKED_REASON_RE,
30
+ IMPLEMENTS_RE,
31
+ has_checkbox_lines,
32
+ parse_implements,
33
+ parse_tasks,
34
+ )
23
35
 
24
36
 
25
37
  TARGET_ID_RE = re.compile(
@@ -231,7 +243,9 @@ def inspect_migration(codeops_root: Path) -> Migration:
231
243
  continue
232
244
  execution_text = execution.read_text(encoding="utf-8")
233
245
  tasks = parse_tasks(execution_text)
234
- if not tasks:
246
+ # Presence stays checkbox-based (id-agnostic) so legacy id-less
247
+ # checklists keep migrating; only real tasks are checked further.
248
+ if not has_checkbox_lines(execution_text):
235
249
  problems.append(f"{execution}: contains no execution tasks")
236
250
  for task in tasks:
237
251
  if task.marker == "!" and not BLOCKED_REASON_RE.search(task.text):
@@ -172,6 +172,11 @@ For each task, in order:
172
172
  the Master Progress Checklist — see the protocol's dual-format detection) and bump the Progress
173
173
  counter / Last Updated stamp as soon as implementation finishes (crash-safe), promote it to
174
174
  `[x]` only after its verification passes. A task never shows `[x]` with a failing verify.
175
+
176
+ - **Report progress** — when the `codeops_progress` tool is available — at the transition
177
+ points in [execution-protocol.md](execution-protocol.md) §Progress Reporting; a failed or
178
+ missing report never blocks any step.
179
+
175
180
  3. **Verify** — run your project's verify command (from the project's AGENTS.md, or detected
176
181
  project conventions), output captured per the protocol's **Verify-output capture rule**
177
182
  (PASS one-liner; on failure the last 50 log lines + log path). Pass → promote `[~]` → `[x]`;
@@ -444,6 +444,35 @@ otherwise still `[~]` — with the progress counter and Last Updated stamp curre
444
444
 
445
445
  ---
446
446
 
447
+ ## Progress Reporting (fail-soft)
448
+
449
+ While a plan executes, the agent may report live progress through the `codeops_progress` tool so
450
+ the sidebar shows the current run. Reporting is an observability aid only: the Markdown execution
451
+ plan remains the single progress source of truth, and a missing tool or a failed call never
452
+ blocks, delays, or changes any verify, commit, or plan update — when the tool is unavailable,
453
+ execution proceeds silently.
454
+
455
+ Report at these moments; `plan` is the plan folder name, and every call carries `plan` and
456
+ `activity` (absent optional fields are carried by the run state):
457
+
458
+ | Moment | Report payload |
459
+ | ------ | -------------- |
460
+ | Run start (start point determined) | `{ plan, phase, task, activity: "implementing" }` |
461
+ | Phase start (subsequent phases) | `{ plan, phase, activity: "starting" }` |
462
+ | Task implemented (after the `[~]` mark) | `{ plan, task, activity: "implementing" }` |
463
+ | Verification (before running verify) | `{ plan, activity: "verifying" }` |
464
+ | Task verified (after `[x]` + the progress-bar display) | `{ plan, verified, total, activity: "implementing" }` — counts from that display |
465
+ | Blocker (after the `[!]` mark) | `{ plan, activity: "blocked", detail: "<short reason>" }` |
466
+ | Delegation (around an executor/reviewer dispatch) | `{ plan, activity: "delegating", detail: "<role>" }` |
467
+ | Review step (post-phase quality loop) | `{ plan, activity: "reviewing" }` |
468
+ | Waiting (before pausing for an explicit user decision) | `{ plan, activity: "waiting" }` |
469
+ | Run done (all tasks complete) | `{ plan, activity: "done" }` |
470
+
471
+ One call per listed moment, no other calls (noise control). A reporting failure or a host without
472
+ the tool never blocks, delays, or changes any step.
473
+
474
+ ---
475
+
447
476
  ## Step 3: Session Wrap-Up
448
477
 
449
478
  1. Complete the current task before stopping.