easy-coding-harness 0.10.0-beta.4 → 0.10.0-beta.5
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 +30 -0
- package/README.md +8 -4
- package/package.json +1 -1
- package/templates/common/bundled-skills/ec-init/SKILL.md +3 -1
- package/templates/common/bundled-skills/ec-meta/references/local-architecture/README.md +15 -7
- package/templates/common/skills/ec-analysis/SKILL.md +14 -4
- package/templates/common/skills/ec-git/SKILL.md +7 -1
- package/templates/common/skills/ec-implementing/SKILL.md +16 -1
- package/templates/common/skills/ec-memory/SKILL.md +65 -3
- package/templates/common/skills/ec-reviewing/SKILL.md +6 -0
- package/templates/common/skills/ec-task-close/SKILL.md +4 -0
- package/templates/common/skills/ec-task-management/SKILL.md +7 -1
- package/templates/common/skills/ec-verification/SKILL.md +11 -1
- package/templates/common/skills/ec-workflow/SKILL.md +16 -2
- package/templates/main-constraint/AGENTS.md.tpl +7 -0
- package/templates/main-constraint/CLAUDE.md.tpl +7 -0
- package/templates/runtime/templates/dev-spec-skeleton.md +3 -1
- package/templates/shared-hooks/easy_coding_state.py +1963 -96
- package/templates/shared-hooks/easy_dev_spec.py +87 -12
- package/templates/shared-hooks/easy_dev_spec_execution.py +1014 -0
- package/templates/shared-hooks/easy_dev_spec_protocol.py +1426 -18
|
@@ -11,7 +11,9 @@ from typing import Any, Iterable
|
|
|
11
11
|
from easy_dev_spec_protocol import SCHEMA, select_scope, validate_spec
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
UPSTREAM_PROTOCOL_COMMIT = "
|
|
14
|
+
UPSTREAM_PROTOCOL_COMMIT = "8239a5befae08b41da43b7cfbf41acf07e487d04"
|
|
15
|
+
UPSTREAM_PROTOCOL_SHA256 = "a6016f04b4ce18794038ebcdbcab6e400a8a08aa2929a3e777c2b35ee3f7e7a1"
|
|
16
|
+
UPSTREAM_EXECUTION_WRITER_SHA256 = "17f03314adce341269e2689aa41bb7bb29c236979be530a373fef58fe88a2524"
|
|
15
17
|
|
|
16
18
|
|
|
17
19
|
class EasyDevSpecError(ValueError):
|
|
@@ -21,12 +23,17 @@ class EasyDevSpecError(ValueError):
|
|
|
21
23
|
def _read_spec(
|
|
22
24
|
path: Path,
|
|
23
25
|
require_ready: bool = False,
|
|
24
|
-
|
|
26
|
+
require_execution: bool = False,
|
|
27
|
+
) -> tuple[str, dict[str, Any], dict[str, str], Any]:
|
|
25
28
|
if not path.is_file():
|
|
26
29
|
raise EasyDevSpecError(f"Spec file does not exist: {path}")
|
|
27
30
|
try:
|
|
28
31
|
text = path.read_text(encoding="utf-8")
|
|
29
|
-
report = validate_spec(
|
|
32
|
+
report = validate_spec(
|
|
33
|
+
text,
|
|
34
|
+
require_ready=require_ready,
|
|
35
|
+
require_execution=require_execution,
|
|
36
|
+
)
|
|
30
37
|
except (OSError, UnicodeError) as exc:
|
|
31
38
|
raise EasyDevSpecError(f"Cannot read Spec as UTF-8: {path}: {exc}") from exc
|
|
32
39
|
if report.protocol == "legacy":
|
|
@@ -40,7 +47,7 @@ def _read_spec(
|
|
|
40
47
|
section_id: section.content
|
|
41
48
|
for section_id, section in report.sections.items()
|
|
42
49
|
}
|
|
43
|
-
return text, report.manifest, sections
|
|
50
|
+
return text, report.manifest, sections, report
|
|
44
51
|
|
|
45
52
|
|
|
46
53
|
def normalize_remote(remote: str) -> str:
|
|
@@ -153,7 +160,7 @@ def inspect_spec(
|
|
|
153
160
|
selected_task_ids: Iterable[str] | None = None,
|
|
154
161
|
) -> dict[str, Any]:
|
|
155
162
|
resolved_spec_path = spec_path.resolve()
|
|
156
|
-
text, manifest, sections = _read_spec(resolved_spec_path)
|
|
163
|
+
text, manifest, sections, report = _read_spec(resolved_spec_path)
|
|
157
164
|
repositories = manifest["repositories"]
|
|
158
165
|
tasks = manifest["tasks"]
|
|
159
166
|
changes = manifest["changes"]
|
|
@@ -223,8 +230,9 @@ def inspect_spec(
|
|
|
223
230
|
try:
|
|
224
231
|
source_path = portable_path(root, resolved_spec_path)
|
|
225
232
|
except EasyDevSpecError:
|
|
226
|
-
#
|
|
233
|
+
# Explicit project-external input remains readable and is stored as an absolute locator.
|
|
227
234
|
source_path = str(resolved_spec_path)
|
|
235
|
+
execution = report.execution
|
|
228
236
|
return {
|
|
229
237
|
"protocol": "canonical-v1",
|
|
230
238
|
"schema": SCHEMA,
|
|
@@ -234,6 +242,12 @@ def inspect_spec(
|
|
|
234
242
|
"title": manifest["title"],
|
|
235
243
|
"source_path": source_path,
|
|
236
244
|
"source_sha256": hashlib.sha256(text.encode("utf-8")).hexdigest(),
|
|
245
|
+
"design_sha256": report.design_sha256,
|
|
246
|
+
"document_sha256": report.document_sha256,
|
|
247
|
+
"execution_revision": (
|
|
248
|
+
execution.get("execution_revision") if isinstance(execution, dict) else None
|
|
249
|
+
),
|
|
250
|
+
"execution": execution,
|
|
237
251
|
"repositories": repositories,
|
|
238
252
|
"tasks": tasks,
|
|
239
253
|
"changes": changes,
|
|
@@ -256,7 +270,11 @@ def select_consumption_scopes(
|
|
|
256
270
|
"""Return final-protocol consumption closures grouped by repository."""
|
|
257
271
|
|
|
258
272
|
resolved_spec_path = spec_path.resolve()
|
|
259
|
-
text, manifest, _ = _read_spec(
|
|
273
|
+
text, manifest, _, report = _read_spec(
|
|
274
|
+
resolved_spec_path,
|
|
275
|
+
require_ready=True,
|
|
276
|
+
require_execution=True,
|
|
277
|
+
)
|
|
260
278
|
selected_ids = list(dict.fromkeys(selected_task_ids))
|
|
261
279
|
if not selected_ids:
|
|
262
280
|
raise EasyDevSpecError("At least one Canonical Spec task must be selected")
|
|
@@ -306,6 +324,9 @@ def select_consumption_scopes(
|
|
|
306
324
|
"status": manifest["status"],
|
|
307
325
|
"source_path": source_path,
|
|
308
326
|
"source_sha256": source_sha256,
|
|
327
|
+
"design_sha256": report.design_sha256,
|
|
328
|
+
"document_sha256": report.document_sha256,
|
|
329
|
+
"execution_revision": report.execution.get("execution_revision"),
|
|
309
330
|
"selected_task_ids": selected_ids,
|
|
310
331
|
"scopes": scopes,
|
|
311
332
|
}
|
|
@@ -331,6 +352,14 @@ def select_tasks(
|
|
|
331
352
|
|
|
332
353
|
selected_set = set(selected_ids)
|
|
333
354
|
evidence_by_dependency = dependency_evidence or {}
|
|
355
|
+
execution = inspection.get("execution")
|
|
356
|
+
execution_by_task = {
|
|
357
|
+
str(snapshot.get("task_id")): snapshot
|
|
358
|
+
for snapshot in execution.get("tasks", [])
|
|
359
|
+
if isinstance(execution, dict)
|
|
360
|
+
and isinstance(snapshot, dict)
|
|
361
|
+
and isinstance(snapshot.get("task_id"), str)
|
|
362
|
+
} if isinstance(execution, dict) else {}
|
|
334
363
|
dependency_target_counts: dict[str, int] = {}
|
|
335
364
|
for source_task_id in selected_ids:
|
|
336
365
|
for dependency in task_by_id[source_task_id].get("depends_on", []):
|
|
@@ -346,15 +375,30 @@ def select_tasks(
|
|
|
346
375
|
evidence = evidence_by_dependency.get(edge_key)
|
|
347
376
|
if evidence is None and dependency_target_counts[dependency_id] == 1:
|
|
348
377
|
evidence = evidence_by_dependency.get(dependency_id)
|
|
378
|
+
source_snapshot = execution_by_task.get(source_task_id, {})
|
|
379
|
+
shared_dependency = next(
|
|
380
|
+
(
|
|
381
|
+
item
|
|
382
|
+
for item in source_snapshot.get("dependencies", [])
|
|
383
|
+
if isinstance(item, dict) and item.get("task_id") == dependency_id
|
|
384
|
+
),
|
|
385
|
+
None,
|
|
386
|
+
)
|
|
387
|
+
dependency_snapshot = execution_by_task.get(dependency_id, {})
|
|
388
|
+
shared_satisfied = bool(
|
|
389
|
+
isinstance(shared_dependency, dict)
|
|
390
|
+
and shared_dependency.get("status") == "satisfied"
|
|
391
|
+
)
|
|
392
|
+
dependency_completed = dependency_snapshot.get("status") == "completed"
|
|
349
393
|
if dependency_type == "hard":
|
|
350
|
-
satisfied =
|
|
351
|
-
if not satisfied:
|
|
394
|
+
satisfied = shared_satisfied or dependency_completed or bool(evidence)
|
|
395
|
+
if dependency_id not in selected_set and not satisfied:
|
|
352
396
|
missing_hard.append(f"{source_task_id}->{dependency_id}")
|
|
353
397
|
elif dependency_type == "contract":
|
|
354
|
-
satisfied =
|
|
398
|
+
satisfied = shared_satisfied or inspection.get("status") == "READY"
|
|
355
399
|
evidence = evidence or "canonical-spec-ready-contract"
|
|
356
400
|
else:
|
|
357
|
-
satisfied = bool(evidence)
|
|
401
|
+
satisfied = shared_satisfied or bool(evidence)
|
|
358
402
|
dependency_records.append(
|
|
359
403
|
{
|
|
360
404
|
"source_task_id": source_task_id,
|
|
@@ -362,6 +406,11 @@ def select_tasks(
|
|
|
362
406
|
"dependency_type": dependency_type,
|
|
363
407
|
"required_evidence": dependency["required_evidence"],
|
|
364
408
|
"status": "satisfied" if satisfied else "pending",
|
|
409
|
+
"shared_status": (
|
|
410
|
+
shared_dependency.get("status")
|
|
411
|
+
if isinstance(shared_dependency, dict)
|
|
412
|
+
else "pending"
|
|
413
|
+
),
|
|
365
414
|
**({"evidence": evidence} if evidence else {}),
|
|
366
415
|
}
|
|
367
416
|
)
|
|
@@ -391,6 +440,12 @@ def select_tasks(
|
|
|
391
440
|
test for test in inspection["tests"] if test.get("task_id") in selected_set
|
|
392
441
|
],
|
|
393
442
|
"dependency_records": dependency_records,
|
|
443
|
+
"execution_revision": inspection.get("execution_revision"),
|
|
444
|
+
"execution_tasks": [
|
|
445
|
+
execution_by_task[task_id]
|
|
446
|
+
for task_id in selected_ids
|
|
447
|
+
if task_id in execution_by_task
|
|
448
|
+
],
|
|
394
449
|
}
|
|
395
450
|
|
|
396
451
|
|
|
@@ -406,6 +461,9 @@ def inspection_summary(inspection: dict[str, Any]) -> dict[str, Any]:
|
|
|
406
461
|
"title",
|
|
407
462
|
"source_path",
|
|
408
463
|
"source_sha256",
|
|
464
|
+
"design_sha256",
|
|
465
|
+
"document_sha256",
|
|
466
|
+
"execution_revision",
|
|
409
467
|
"repositories",
|
|
410
468
|
"tasks",
|
|
411
469
|
"dependency_edges",
|
|
@@ -414,4 +472,21 @@ def inspection_summary(inspection: dict[str, Any]) -> dict[str, Any]:
|
|
|
414
472
|
"baseline_status",
|
|
415
473
|
"repository_bindings",
|
|
416
474
|
)
|
|
417
|
-
|
|
475
|
+
summary = {key: inspection[key] for key in keys}
|
|
476
|
+
execution = inspection.get("execution")
|
|
477
|
+
if isinstance(execution, dict):
|
|
478
|
+
summary["execution"] = {
|
|
479
|
+
key: execution.get(key)
|
|
480
|
+
for key in (
|
|
481
|
+
"schema",
|
|
482
|
+
"spec_id",
|
|
483
|
+
"design_revision",
|
|
484
|
+
"design_sha256",
|
|
485
|
+
"execution_revision",
|
|
486
|
+
"updated_at",
|
|
487
|
+
"tasks",
|
|
488
|
+
)
|
|
489
|
+
}
|
|
490
|
+
else:
|
|
491
|
+
summary["execution"] = None
|
|
492
|
+
return summary
|