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.
- package/CHANGELOG.md +101 -0
- package/README.md +16 -5
- package/_shared/workspace-hygiene.md +6 -4
- package/bin/lib/codeops-progress.d.mts +236 -0
- package/bin/lib/codeops-progress.mjs +564 -0
- package/bin/lib/codeops-rpc.d.mts +160 -0
- package/bin/lib/codeops-rpc.mjs +177 -0
- package/bin/lib/tmp-hygiene.d.mts +0 -3
- package/bin/lib/tmp-hygiene.mjs +2 -34
- package/package.json +24 -2
- package/plugin/index.ts +61 -13
- package/plugin/tui.tsx +112 -0
- package/scripts/__pycache__/codeops_plan.cpython-312.pyc +0 -0
- package/scripts/__pycache__/install_agents.cpython-312.pyc +0 -0
- package/scripts/codeops_plan.py +67 -3
- package/scripts/codeops_plan_migrate.py +17 -3
- package/skills/exec-plan/SKILL.md +5 -0
- package/skills/exec-plan/execution-protocol.md +29 -0
package/scripts/codeops_plan.py
CHANGED
|
@@ -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*$"
|
|
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
|
-
"""
|
|
72
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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.
|