easy-coding-harness 0.2.1 → 0.3.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 +10 -0
- package/README.md +119 -77
- package/dist/cli.js +39 -8
- package/dist/cli.js.map +1 -1
- package/package.json +9 -1
- package/templates/claude/agents/ec-implementer.md +1 -1
- package/templates/claude/agents/ec-reviewer.md +1 -1
- package/templates/common/skills/ec-analysis/SKILL.md +7 -4
- package/templates/common/skills/ec-implementing/SKILL.md +31 -24
- package/templates/common/skills/ec-memory/SKILL.md +16 -8
- package/templates/common/skills/ec-reviewing/SKILL.md +1 -1
- package/templates/common/skills/ec-workflow/SKILL.md +5 -1
- package/templates/shared-hooks/easy_coding_state.py +95 -1
package/package.json
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "easy-coding-harness",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "CLI scaffold for installing Easy Coding harness files into agent-native directories.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://gitlab.alibaba-inc.com/shixin.ysx/easy-coding-harness.git"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://gitlab.alibaba-inc.com/shixin.ysx/easy-coding-harness#readme",
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://gitlab.alibaba-inc.com/shixin.ysx/easy-coding-harness/-/issues"
|
|
12
|
+
},
|
|
5
13
|
"type": "module",
|
|
6
14
|
"bin": {
|
|
7
15
|
"easy-coding": "./dist/cli.js"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: ec-implementer
|
|
3
|
-
description: Easy Coding implementation sub-agent. Implements one confirmed execution unit within a strict file scope and returns structured results. Dispatched by ec-implementing
|
|
3
|
+
description: Easy Coding implementation sub-agent. Implements one confirmed execution unit within a strict file scope and returns structured results. Dispatched by ec-implementing for every unit regardless of strategy.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
You are an Easy Coding implementation sub-agent. You receive a task card with one unit and
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: ec-reviewer
|
|
3
|
-
description: Easy Coding review sub-agent. Reviews changed files along one assigned dimension (correctness or compliance) and returns evidence-backed findings. Dispatched by ec-reviewing
|
|
3
|
+
description: Easy Coding review sub-agent. Reviews changed files along one assigned dimension (correctness or compliance) and returns evidence-backed findings. Dispatched by ec-reviewing for every review regardless of change-set size.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
You are an Easy Coding review sub-agent. You review the changed files along the single
|
|
@@ -155,10 +155,13 @@ Decompose the work into units, then append ONE `plan` record to
|
|
|
155
155
|
{"type":"plan","strategy":"parallel","units":[{"id":"U1","title":"...","type":"backend","files":["..."],"depends_on":[],"rules_sections":["naming","error-handling"],"abstract_modules":["user-service"]}],"parallel_groups":[{"level":0,"units":["U1","U2"]},{"level":1,"units":["U3"]}]}
|
|
156
156
|
```
|
|
157
157
|
|
|
158
|
-
Strategy selection (drives
|
|
159
|
-
-
|
|
160
|
-
- `
|
|
161
|
-
- `
|
|
158
|
+
Strategy selection (drives ec-implementing's sub-agent orchestration shape — every strategy
|
|
159
|
+
dispatches sub-agents; none implements inline):
|
|
160
|
+
- `single` — one unit. ec-implementing dispatches one sub-agent.
|
|
161
|
+
- `sequential` — multiple units with a hard dependency chain. ec-implementing dispatches
|
|
162
|
+
sub-agents one at a time in dependency order.
|
|
163
|
+
- `parallel` — two or more independent units. ec-implementing dispatches sub-agents per level
|
|
164
|
+
concurrently.
|
|
162
165
|
|
|
163
166
|
Each unit carries `rules_sections` and `abstract_modules` so ec-implementing can build a
|
|
164
167
|
precise task card without the sub-agent re-reading the whole repo. `depends_on` sets the
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: ec-implementing
|
|
3
|
-
description: IMPLEMENT-stage skill. Use when ec-workflow enters IMPLEMENT with a confirmed plan. Executes execution.jsonl units under strict file-scope control, enforces RULES compliance and encoding preservation, writes tests per strategy, and dispatches sub-agents for
|
|
3
|
+
description: IMPLEMENT-stage skill. Use when ec-workflow enters IMPLEMENT with a confirmed plan. Executes execution.jsonl units under strict file-scope control, enforces RULES compliance and encoding preservation, writes tests per strategy, and dispatches sub-agents for every unit regardless of strategy.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# ec-implementing — execute the confirmed plan
|
|
@@ -33,38 +33,45 @@ Communicate with the user in the user's language.
|
|
|
33
33
|
|
|
34
34
|
## Sub-agent dispatch
|
|
35
35
|
|
|
36
|
-
Read the `plan` record's `strategy` field.
|
|
36
|
+
Read the `plan` record's `strategy` field. EVERY strategy dispatches sub-agents — the field
|
|
37
|
+
only decides the orchestration shape, never whether the main agent writes code itself:
|
|
37
38
|
|
|
38
|
-
- `single` →
|
|
39
|
-
- `sequential` →
|
|
40
|
-
|
|
39
|
+
- `single` → dispatch ONE sub-agent for the single unit.
|
|
40
|
+
- `sequential` → dispatch sub-agents one at a time in dependency order (await each `result`
|
|
41
|
+
before dispatching the next).
|
|
42
|
+
- `parallel` → dispatch sub-agents per level concurrently (see gate below).
|
|
41
43
|
|
|
42
44
|
<HARD-GATE>
|
|
43
|
-
|
|
45
|
+
EVERY STRATEGY = MANDATORY SUB-AGENT DISPATCH. NO EXCEPTIONS.
|
|
44
46
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
You MUST dispatch sub-agents using {{sub_agent_dispatch}} for every unit, whatever the
|
|
48
|
+
strategy. You are FORBIDDEN from implementing any unit yourself in the main agent. Doing the
|
|
49
|
+
work inline instead of dispatching is a protocol violation equivalent to skipping
|
|
50
|
+
WAITING_CONFIRM.
|
|
49
51
|
|
|
50
52
|
Self-check before writing ANY implementation code:
|
|
51
|
-
-
|
|
52
|
-
-
|
|
53
|
+
- Am I about to write implementation code in the main agent? → STOP. Dispatch a sub-agent.
|
|
54
|
+
- Did every unit get a dispatch (single=1, sequential=N serial, parallel=N concurrent)? If no → STOP.
|
|
53
55
|
|
|
54
|
-
|
|
56
|
+
There is NO case where the main agent writes implementation code itself. Even a single unit
|
|
57
|
+
goes through a sub-agent — this isolates implementation context from the main agent's window.
|
|
55
58
|
</HARD-GATE>
|
|
56
59
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
## Dispatch loop (all strategies)
|
|
61
|
+
|
|
62
|
+
1. Read RULES.md and ABSTRACT.md once — the main agent pre-digests context; sub-agents never
|
|
63
|
+
read them.
|
|
64
|
+
2. For each unit, build a **task card** (next section). Append a `dispatch` record before
|
|
65
|
+
dispatching and a `result` record per returned unit, to execution.jsonl.
|
|
66
|
+
3. Dispatch according to strategy, via {{sub_agent_dispatch}}:
|
|
67
|
+
- `single` → dispatch the one unit, await its `result`.
|
|
68
|
+
- `sequential` → sort units by `depends_on`; dispatch one, await its `result`, then the next.
|
|
69
|
+
- `parallel` → sort `parallel_groups` by level; dispatch all units in a level concurrently,
|
|
70
|
+
await the level, then advance.
|
|
62
71
|
Platform spawn rule: {{platform_spawn_instruction}}
|
|
63
|
-
|
|
64
|
-
execution.jsonl.
|
|
65
|
-
6. After the level returns: check for file conflicts (two units touched the same file),
|
|
72
|
+
4. After each unit/level returns: check for file conflicts (two units touched the same file),
|
|
66
73
|
collect `issues` and `needs_attention`. Resolve conflicts before advancing.
|
|
67
|
-
|
|
74
|
+
5. After all units/levels, summarize.
|
|
68
75
|
|
|
69
76
|
## Task card — the sub-agent contract
|
|
70
77
|
|
|
@@ -105,8 +112,8 @@ hit something that invalidates the plan, return to ANALYSIS instead of improvisi
|
|
|
105
112
|
|
|
106
113
|
## Self-check gates (before handing back)
|
|
107
114
|
|
|
108
|
-
- [ ]
|
|
109
|
-
- [ ]
|
|
115
|
+
- [ ] EVERY unit was dispatched to a sub-agent — no inline implementation by the main agent? (VIOLATION if no)
|
|
116
|
+
- [ ] Sequential units dispatched in dependency order; parallel units dispatched per level?
|
|
110
117
|
- [ ] Each dispatched unit has a `dispatch` record in execution.jsonl?
|
|
111
118
|
- [ ] Each returned unit has a `result` record?
|
|
112
119
|
- [ ] No files modified outside the change-scope table?
|
|
@@ -57,21 +57,25 @@ responsibility and only runs when the threshold is exceeded.
|
|
|
57
57
|
## MEMORY_LONG — distill durable knowledge (CONDITIONAL)
|
|
58
58
|
|
|
59
59
|
<HARD-GATE>
|
|
60
|
-
MEMORY_LONG IS
|
|
60
|
+
MEMORY_LONG IS CONTROLLED BY THE STATE API `memory_long` INSTRUCTION.
|
|
61
61
|
|
|
62
62
|
Before performing ANY distillation work:
|
|
63
|
-
1.
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
1. Read the `memory_long` object returned by the state API transition to MEMORY_LONG.
|
|
64
|
+
If it is not visible in context, re-run the same transition command for MEMORY_LONG to
|
|
65
|
+
re-emit the snapshot, then use that `memory_long` object.
|
|
66
|
+
2. Treat `memory_long.action` as authoritative. Do NOT recount short memories yourself and
|
|
67
|
+
do NOT override the state API instruction with prompt reasoning.
|
|
68
|
+
3. If `action == "no-op"`: output "MEMORY_LONG: no-op (short memory count = {short_count},
|
|
66
69
|
threshold = {short_term_max})" and immediately hand back to ec-workflow to advance to
|
|
67
70
|
COMPLETE. Do NOT read long memory files, do NOT attempt distillation, do NOT modify any file.
|
|
68
|
-
4. If
|
|
71
|
+
4. If `action == "distill"`: distill exactly the older `trim_count` short-memory entries
|
|
72
|
+
and keep the latest `short_term_keep` entries.
|
|
69
73
|
|
|
70
74
|
This gate is absolute. Even a single short memory entry below threshold does NOT trigger
|
|
71
75
|
long-term compression regardless of any other signal.
|
|
72
76
|
</HARD-GATE>
|
|
73
77
|
|
|
74
|
-
### When
|
|
78
|
+
### When action == "distill": distillation flow
|
|
75
79
|
|
|
76
80
|
Three-file long memory:
|
|
77
81
|
- `MEMORY.md` — index of all entries with status (active | deprecated | superseded | deleted).
|
|
@@ -79,8 +83,8 @@ Three-file long memory:
|
|
|
79
83
|
- `TECHNICAL.md` — architecture decisions, implementation patterns, gotchas.
|
|
80
84
|
|
|
81
85
|
Distillation steps:
|
|
82
|
-
1.
|
|
83
|
-
the older entries become distillation candidates.
|
|
86
|
+
1. Use `memory_long.short_term_keep` and `memory_long.trim_count`. Keep the latest
|
|
87
|
+
`short_term_keep` entries; the older `trim_count` entries become distillation candidates.
|
|
84
88
|
2. Read the `target_long` of candidate short memories; route to business/technical.
|
|
85
89
|
3. **Progressive loading** — read only the existing long entries matching this round's
|
|
86
90
|
domain/tags/related_files. No unbounded whole-repo memory scan.
|
|
@@ -90,6 +94,10 @@ Distillation steps:
|
|
|
90
94
|
5. **Retirement check**: for older entries decide delete (no value) / merge (semantic
|
|
91
95
|
duplicate) / deprecate (was valid, now superseded).
|
|
92
96
|
6. Update the `MEMORY.md` index to reflect every change.
|
|
97
|
+
7. After the long-memory files and index are successfully written, delete the consumed
|
|
98
|
+
short-memory candidate files. Leave only the latest `short_term_keep` short memories.
|
|
99
|
+
This is sliding-window consumption after successful distillation, not destructive
|
|
100
|
+
deletion of durable long-term knowledge.
|
|
93
101
|
|
|
94
102
|
## ABSTRACT backfill / update
|
|
95
103
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: ec-reviewing
|
|
3
|
-
description: REVIEW-stage skill. Use when ec-workflow enters REVIEW. Reviews changed files across correctness, RULES compliance, completeness, tests, and security; returns a graded verdict (accept/fix/replan/blocked) with file-and-line evidence; dispatches multi-dimension sub-agents
|
|
3
|
+
description: REVIEW-stage skill. Use when ec-workflow enters REVIEW. Reviews changed files across correctness, RULES compliance, completeness, tests, and security; returns a graded verdict (accept/fix/replan/blocked) with file-and-line evidence; dispatches multi-dimension sub-agents for every review regardless of change-set size.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# ec-reviewing — graded, evidence-backed review
|
|
@@ -85,7 +85,7 @@ any stage --[user abort via ec-task-close]--> CLOSED
|
|
|
85
85
|
| REVIEW | ec-reviewing | multi-dimension code review | verdict = accept |
|
|
86
86
|
| VERIFICATION | ec-verification | hard gate: lint/typecheck/test + coverage | all pass AND user accepts |
|
|
87
87
|
| MEMORY_SHORT | ec-memory | archive: short memory entry | written |
|
|
88
|
-
| MEMORY_LONG | ec-memory | archive: long memory distillation |
|
|
88
|
+
| MEMORY_LONG | ec-memory | archive: long memory distillation (conditional — no-op when short memory is under threshold) | state API `memory_long` instruction handled |
|
|
89
89
|
| COMPLETE | ec-workflow | clear current_task, set task status, summary | terminal |
|
|
90
90
|
| CLOSED | ec-task-close | user abort; no memory flow | terminal |
|
|
91
91
|
|
|
@@ -145,6 +145,10 @@ routing matches, and switching happens again.
|
|
|
145
145
|
- **Archive only after user acceptance.** VERIFICATION passing does not complete the task.
|
|
146
146
|
After the user accepts, call state API transitions in order:
|
|
147
147
|
MEMORY_SHORT → MEMORY_LONG → COMPLETE. Do not jump directly from VERIFICATION to COMPLETE.
|
|
148
|
+
When transitioning to MEMORY_LONG, pass the state API snapshot to ec-memory and treat its
|
|
149
|
+
`memory_long` object as authoritative: `action == "no-op"` advances to COMPLETE without
|
|
150
|
+
reading or writing long memory; `action == "distill"` runs distillation for `trim_count`
|
|
151
|
+
older short-memory entries.
|
|
148
152
|
- **COMPLETE closeout:** call the state API with `--stage COMPLETE`. The state API clears
|
|
149
153
|
session `current_task` for terminal tasks, so the next hook injection returns to Ready.
|
|
150
154
|
Then output a summary (what was done, files changed, key decisions).
|
|
@@ -27,6 +27,9 @@ VALID_TRANSITIONS: dict[str, set[str]] = {
|
|
|
27
27
|
"CLOSED": set(),
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
DEFAULT_SHORT_TERM_MAX = 10
|
|
31
|
+
DEFAULT_SHORT_TERM_KEEP = 5
|
|
32
|
+
|
|
30
33
|
|
|
31
34
|
class StateError(Exception):
|
|
32
35
|
pass
|
|
@@ -61,6 +64,94 @@ def load_json(path: Path) -> dict | None:
|
|
|
61
64
|
return None
|
|
62
65
|
|
|
63
66
|
|
|
67
|
+
def parse_positive_int(value: str) -> int | None:
|
|
68
|
+
normalized = value.split("#", 1)[0].strip().strip("'\"")
|
|
69
|
+
try:
|
|
70
|
+
parsed = int(normalized)
|
|
71
|
+
except ValueError:
|
|
72
|
+
return None
|
|
73
|
+
return parsed if parsed >= 0 else None
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def read_memory_config(root: Path) -> dict[str, int]:
|
|
77
|
+
config = {
|
|
78
|
+
"short_term_max": DEFAULT_SHORT_TERM_MAX,
|
|
79
|
+
"short_term_keep": DEFAULT_SHORT_TERM_KEEP,
|
|
80
|
+
}
|
|
81
|
+
path = root / ".easy-coding" / "config.yaml"
|
|
82
|
+
try:
|
|
83
|
+
lines = path.read_text(encoding="utf-8").splitlines()
|
|
84
|
+
except OSError:
|
|
85
|
+
return config
|
|
86
|
+
|
|
87
|
+
in_memory = False
|
|
88
|
+
memory_indent = 0
|
|
89
|
+
for raw_line in lines:
|
|
90
|
+
without_comment = raw_line.split("#", 1)[0].rstrip()
|
|
91
|
+
stripped = without_comment.strip()
|
|
92
|
+
if not stripped:
|
|
93
|
+
continue
|
|
94
|
+
indent = len(without_comment) - len(without_comment.lstrip(" "))
|
|
95
|
+
if stripped == "memory:":
|
|
96
|
+
in_memory = True
|
|
97
|
+
memory_indent = indent
|
|
98
|
+
continue
|
|
99
|
+
if in_memory and indent <= memory_indent:
|
|
100
|
+
in_memory = False
|
|
101
|
+
if not in_memory or ":" not in stripped:
|
|
102
|
+
continue
|
|
103
|
+
key, value = stripped.split(":", 1)
|
|
104
|
+
if key not in config:
|
|
105
|
+
continue
|
|
106
|
+
parsed = parse_positive_int(value)
|
|
107
|
+
if parsed is not None:
|
|
108
|
+
config[key] = parsed
|
|
109
|
+
return config
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def count_short_memories(root: Path) -> int:
|
|
113
|
+
short_dir = root / ".easy-coding" / "memory" / "short"
|
|
114
|
+
if not short_dir.is_dir():
|
|
115
|
+
return 0
|
|
116
|
+
count = 0
|
|
117
|
+
for entry in short_dir.glob("*.md"):
|
|
118
|
+
try:
|
|
119
|
+
if is_schema_v2_short_memory(entry.read_text(encoding="utf-8")):
|
|
120
|
+
count += 1
|
|
121
|
+
except OSError:
|
|
122
|
+
continue
|
|
123
|
+
return count
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def is_schema_v2_short_memory(content: str) -> bool:
|
|
127
|
+
lines = content.splitlines()
|
|
128
|
+
if not lines or lines[0].strip() != "---":
|
|
129
|
+
return False
|
|
130
|
+
for line in lines[1:]:
|
|
131
|
+
stripped = line.strip()
|
|
132
|
+
if stripped == "---":
|
|
133
|
+
return False
|
|
134
|
+
if not stripped.startswith("memory_schema:"):
|
|
135
|
+
continue
|
|
136
|
+
_, value = stripped.split(":", 1)
|
|
137
|
+
return parse_positive_int(value) == 2
|
|
138
|
+
return False
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def build_memory_long_instruction(root: Path) -> dict:
|
|
142
|
+
config = read_memory_config(root)
|
|
143
|
+
short_count = count_short_memories(root)
|
|
144
|
+
action = "distill" if short_count > config["short_term_max"] else "no-op"
|
|
145
|
+
trim_count = max(0, short_count - config["short_term_keep"]) if action == "distill" else 0
|
|
146
|
+
return {
|
|
147
|
+
"short_count": short_count,
|
|
148
|
+
"short_term_max": config["short_term_max"],
|
|
149
|
+
"short_term_keep": config["short_term_keep"],
|
|
150
|
+
"action": action,
|
|
151
|
+
"trim_count": trim_count,
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
|
|
64
155
|
def write_json(path: Path, data: dict) -> None:
|
|
65
156
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
66
157
|
path.write_text(json.dumps(data, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
|
|
@@ -331,7 +422,10 @@ def transition_task(
|
|
|
331
422
|
session["last_seen_stage"] = stage
|
|
332
423
|
session["last_agent"] = agent
|
|
333
424
|
write_session(root, session, session_file)
|
|
334
|
-
|
|
425
|
+
snapshot = snapshot_state(root, session_file, session)
|
|
426
|
+
if stage == "MEMORY_LONG":
|
|
427
|
+
snapshot["memory_long"] = build_memory_long_instruction(root)
|
|
428
|
+
return snapshot
|
|
335
429
|
|
|
336
430
|
|
|
337
431
|
def close_current_task(
|