@hunter-harness/workflow-harness 0.2.5 → 0.2.6
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/harness/bundles/general/claude-code/.harness-build.json +1 -1
- package/harness/bundles/general/claude-code/harness-archive/SKILL.md +5 -2
- package/harness/bundles/general/claude-code/harness-archive/reference.md +1 -1
- package/harness/bundles/general/claude-code/harness-archive/templates/render-summary.mjs +78 -155
- package/harness/bundles/general/claude-code/harness-codebase-map/SKILL.md +4 -2
- package/harness/bundles/general/claude-code/harness-knowledge-ingest/SKILL.md +5 -3
- package/harness/bundles/general/claude-code/harness-knowledge-ingest/scripts/harness_knowledge.py +83 -6
- package/harness/bundles/general/claude-code/harness-knowledge-query/SKILL.md +4 -2
- package/harness/bundles/general/claude-code/harness-plan/SKILL.md +6 -3
- package/harness/bundles/general/claude-code/harness-review/SKILL.md +6 -3
- package/harness/bundles/general/claude-code/harness-run/SKILL.md +5 -2
- package/harness/bundles/general/claude-code/harness-run/reference.md +1 -1
- package/harness/bundles/general/claude-code/harness-submit/SKILL.md +5 -2
- package/harness/bundles/general/claude-code/harness-sync/SKILL.md +5 -3
- package/harness/bundles/general/claude-code/harness-test/SKILL.md +5 -2
- package/harness/bundles/general/claude-code/harness-test/reference.md +1 -1
- package/harness/bundles/general/claude-code/protocols/ledger-protocol.md +1 -1
- package/harness/bundles/general/claude-code/protocols/report-pipeline-protocol.md +11 -4
- package/harness/bundles/general/claude-code/scripts/harness_archive.py +190 -32
- package/harness/bundles/general/claude-code/scripts/harness_events.py +134 -40
- package/harness/bundles/general/claude-code/scripts/harness_preflight.py +46 -3
- package/harness/bundles/general/claude-code/scripts/harness_profile.py +2 -2
- package/harness/bundles/general/claude-code/scripts/harness_state.py +148 -0
- package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +5 -2
- package/harness/bundles/general/codebuddy/harness-archive/reference.md +1 -1
- package/harness/bundles/general/codebuddy/harness-archive/templates/render-summary.mjs +78 -155
- package/harness/bundles/general/codebuddy/harness-codebase-map/SKILL.md +4 -2
- package/harness/bundles/general/codebuddy/harness-knowledge-ingest/SKILL.md +5 -3
- package/harness/bundles/general/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge.py +83 -6
- package/harness/bundles/general/codebuddy/harness-knowledge-query/SKILL.md +4 -2
- package/harness/bundles/general/codebuddy/harness-plan/SKILL.md +6 -3
- package/harness/bundles/general/codebuddy/harness-review/SKILL.md +6 -3
- package/harness/bundles/general/codebuddy/harness-run/SKILL.md +5 -2
- package/harness/bundles/general/codebuddy/harness-run/reference.md +1 -1
- package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +5 -2
- package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +5 -3
- package/harness/bundles/general/codebuddy/harness-test/SKILL.md +5 -2
- package/harness/bundles/general/codebuddy/harness-test/reference.md +1 -1
- package/harness/bundles/general/codebuddy/protocols/ledger-protocol.md +1 -1
- package/harness/bundles/general/codebuddy/protocols/report-pipeline-protocol.md +11 -4
- package/harness/bundles/general/codebuddy/scripts/harness_archive.py +190 -32
- package/harness/bundles/general/codebuddy/scripts/harness_events.py +134 -40
- package/harness/bundles/general/codebuddy/scripts/harness_preflight.py +46 -3
- package/harness/bundles/general/codebuddy/scripts/harness_profile.py +2 -2
- package/harness/bundles/general/codebuddy/scripts/harness_state.py +148 -0
- package/harness/bundles/general/codex/.harness-build.json +1 -1
- package/harness/bundles/general/codex/harness-archive/SKILL.md +5 -2
- package/harness/bundles/general/codex/harness-archive/reference.md +1 -1
- package/harness/bundles/general/codex/harness-archive/templates/render-summary.mjs +78 -155
- package/harness/bundles/general/codex/harness-codebase-map/SKILL.md +4 -2
- package/harness/bundles/general/codex/harness-knowledge-ingest/SKILL.md +5 -3
- package/harness/bundles/general/codex/harness-knowledge-ingest/scripts/harness_knowledge.py +83 -6
- package/harness/bundles/general/codex/harness-knowledge-query/SKILL.md +4 -2
- package/harness/bundles/general/codex/harness-plan/SKILL.md +6 -3
- package/harness/bundles/general/codex/harness-review/SKILL.md +5 -2
- package/harness/bundles/general/codex/harness-run/SKILL.md +5 -2
- package/harness/bundles/general/codex/harness-run/reference.md +1 -1
- package/harness/bundles/general/codex/harness-submit/SKILL.md +5 -2
- package/harness/bundles/general/codex/harness-sync/SKILL.md +5 -3
- package/harness/bundles/general/codex/harness-test/SKILL.md +5 -2
- package/harness/bundles/general/codex/harness-test/reference.md +1 -1
- package/harness/bundles/general/codex/protocols/ledger-protocol.md +1 -1
- package/harness/bundles/general/codex/protocols/report-pipeline-protocol.md +11 -4
- package/harness/bundles/general/codex/scripts/harness_archive.py +190 -32
- package/harness/bundles/general/codex/scripts/harness_events.py +134 -40
- package/harness/bundles/general/codex/scripts/harness_preflight.py +46 -3
- package/harness/bundles/general/codex/scripts/harness_profile.py +2 -2
- package/harness/bundles/general/codex/scripts/harness_state.py +148 -0
- package/harness/bundles/general/cursor/.harness-build.json +1 -1
- package/harness/bundles/general/cursor/harness-archive/SKILL.md +5 -2
- package/harness/bundles/general/cursor/harness-archive/reference.md +1 -1
- package/harness/bundles/general/cursor/harness-archive/templates/render-summary.mjs +78 -155
- package/harness/bundles/general/cursor/harness-codebase-map/SKILL.md +4 -2
- package/harness/bundles/general/cursor/harness-knowledge-ingest/SKILL.md +5 -3
- package/harness/bundles/general/cursor/harness-knowledge-ingest/scripts/harness_knowledge.py +83 -6
- package/harness/bundles/general/cursor/harness-knowledge-query/SKILL.md +4 -2
- package/harness/bundles/general/cursor/harness-plan/SKILL.md +6 -3
- package/harness/bundles/general/cursor/harness-review/SKILL.md +5 -2
- package/harness/bundles/general/cursor/harness-run/SKILL.md +5 -2
- package/harness/bundles/general/cursor/harness-run/reference.md +1 -1
- package/harness/bundles/general/cursor/harness-submit/SKILL.md +5 -2
- package/harness/bundles/general/cursor/harness-sync/SKILL.md +5 -3
- package/harness/bundles/general/cursor/harness-test/SKILL.md +5 -2
- package/harness/bundles/general/cursor/harness-test/reference.md +1 -1
- package/harness/bundles/general/cursor/protocols/ledger-protocol.md +1 -1
- package/harness/bundles/general/cursor/protocols/report-pipeline-protocol.md +11 -4
- package/harness/bundles/general/cursor/scripts/harness_archive.py +190 -32
- package/harness/bundles/general/cursor/scripts/harness_events.py +134 -40
- package/harness/bundles/general/cursor/scripts/harness_preflight.py +46 -3
- package/harness/bundles/general/cursor/scripts/harness_profile.py +2 -2
- package/harness/bundles/general/cursor/scripts/harness_state.py +148 -0
- package/harness/bundles/java/claude-code/.harness-build.json +1 -1
- package/harness/bundles/java/claude-code/harness-apidoc/SKILL.md +5 -2
- package/harness/bundles/java/claude-code/harness-archive/SKILL.md +5 -2
- package/harness/bundles/java/claude-code/harness-archive/reference.md +1 -1
- package/harness/bundles/java/claude-code/harness-archive/templates/render-summary.mjs +78 -155
- package/harness/bundles/java/claude-code/harness-codebase-map/SKILL.md +4 -2
- package/harness/bundles/java/claude-code/harness-knowledge-ingest/SKILL.md +5 -3
- package/harness/bundles/java/claude-code/harness-knowledge-ingest/scripts/harness_knowledge.py +83 -6
- package/harness/bundles/java/claude-code/harness-knowledge-query/SKILL.md +4 -2
- package/harness/bundles/java/claude-code/harness-package/SKILL.md +5 -2
- package/harness/bundles/java/claude-code/harness-plan/SKILL.md +6 -3
- package/harness/bundles/java/claude-code/harness-review/SKILL.md +6 -3
- package/harness/bundles/java/claude-code/harness-run/SKILL.md +5 -2
- package/harness/bundles/java/claude-code/harness-run/reference.md +1 -1
- package/harness/bundles/java/claude-code/harness-submit/SKILL.md +5 -2
- package/harness/bundles/java/claude-code/harness-sync/SKILL.md +5 -3
- package/harness/bundles/java/claude-code/harness-test/SKILL.md +5 -2
- package/harness/bundles/java/claude-code/harness-test/reference.md +1 -1
- package/harness/bundles/java/claude-code/protocols/ledger-protocol.md +1 -1
- package/harness/bundles/java/claude-code/protocols/report-pipeline-protocol.md +11 -4
- package/harness/bundles/java/claude-code/scripts/harness_archive.py +190 -32
- package/harness/bundles/java/claude-code/scripts/harness_events.py +134 -40
- package/harness/bundles/java/claude-code/scripts/harness_preflight.py +46 -3
- package/harness/bundles/java/claude-code/scripts/harness_profile.py +2 -2
- package/harness/bundles/java/claude-code/scripts/harness_state.py +148 -0
- package/harness/bundles/java/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +5 -2
- package/harness/bundles/java/codebuddy/harness-archive/SKILL.md +5 -2
- package/harness/bundles/java/codebuddy/harness-archive/reference.md +1 -1
- package/harness/bundles/java/codebuddy/harness-archive/templates/render-summary.mjs +78 -155
- package/harness/bundles/java/codebuddy/harness-codebase-map/SKILL.md +4 -2
- package/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +5 -3
- package/harness/bundles/java/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge.py +83 -6
- package/harness/bundles/java/codebuddy/harness-knowledge-query/SKILL.md +4 -2
- package/harness/bundles/java/codebuddy/harness-package/SKILL.md +5 -2
- package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +6 -3
- package/harness/bundles/java/codebuddy/harness-review/SKILL.md +6 -3
- package/harness/bundles/java/codebuddy/harness-run/SKILL.md +5 -2
- package/harness/bundles/java/codebuddy/harness-run/reference.md +1 -1
- package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +5 -2
- package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +5 -3
- package/harness/bundles/java/codebuddy/harness-test/SKILL.md +5 -2
- package/harness/bundles/java/codebuddy/harness-test/reference.md +1 -1
- package/harness/bundles/java/codebuddy/protocols/ledger-protocol.md +1 -1
- package/harness/bundles/java/codebuddy/protocols/report-pipeline-protocol.md +11 -4
- package/harness/bundles/java/codebuddy/scripts/harness_archive.py +190 -32
- package/harness/bundles/java/codebuddy/scripts/harness_events.py +134 -40
- package/harness/bundles/java/codebuddy/scripts/harness_preflight.py +46 -3
- package/harness/bundles/java/codebuddy/scripts/harness_profile.py +2 -2
- package/harness/bundles/java/codebuddy/scripts/harness_state.py +148 -0
- package/harness/bundles/java/codex/.harness-build.json +1 -1
- package/harness/bundles/java/codex/harness-apidoc/SKILL.md +5 -2
- package/harness/bundles/java/codex/harness-archive/SKILL.md +5 -2
- package/harness/bundles/java/codex/harness-archive/reference.md +1 -1
- package/harness/bundles/java/codex/harness-archive/templates/render-summary.mjs +78 -155
- package/harness/bundles/java/codex/harness-codebase-map/SKILL.md +4 -2
- package/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +5 -3
- package/harness/bundles/java/codex/harness-knowledge-ingest/scripts/harness_knowledge.py +83 -6
- package/harness/bundles/java/codex/harness-knowledge-query/SKILL.md +4 -2
- package/harness/bundles/java/codex/harness-package/SKILL.md +5 -2
- package/harness/bundles/java/codex/harness-plan/SKILL.md +6 -3
- package/harness/bundles/java/codex/harness-review/SKILL.md +5 -2
- package/harness/bundles/java/codex/harness-run/SKILL.md +5 -2
- package/harness/bundles/java/codex/harness-run/reference.md +1 -1
- package/harness/bundles/java/codex/harness-submit/SKILL.md +5 -2
- package/harness/bundles/java/codex/harness-sync/SKILL.md +5 -3
- package/harness/bundles/java/codex/harness-test/SKILL.md +5 -2
- package/harness/bundles/java/codex/harness-test/reference.md +1 -1
- package/harness/bundles/java/codex/protocols/ledger-protocol.md +1 -1
- package/harness/bundles/java/codex/protocols/report-pipeline-protocol.md +11 -4
- package/harness/bundles/java/codex/scripts/harness_archive.py +190 -32
- package/harness/bundles/java/codex/scripts/harness_events.py +134 -40
- package/harness/bundles/java/codex/scripts/harness_preflight.py +46 -3
- package/harness/bundles/java/codex/scripts/harness_profile.py +2 -2
- package/harness/bundles/java/codex/scripts/harness_state.py +148 -0
- package/harness/bundles/java/cursor/.harness-build.json +1 -1
- package/harness/bundles/java/cursor/harness-apidoc/SKILL.md +5 -2
- package/harness/bundles/java/cursor/harness-archive/SKILL.md +5 -2
- package/harness/bundles/java/cursor/harness-archive/reference.md +1 -1
- package/harness/bundles/java/cursor/harness-archive/templates/render-summary.mjs +78 -155
- package/harness/bundles/java/cursor/harness-codebase-map/SKILL.md +4 -2
- package/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +5 -3
- package/harness/bundles/java/cursor/harness-knowledge-ingest/scripts/harness_knowledge.py +83 -6
- package/harness/bundles/java/cursor/harness-knowledge-query/SKILL.md +4 -2
- package/harness/bundles/java/cursor/harness-package/SKILL.md +5 -2
- package/harness/bundles/java/cursor/harness-plan/SKILL.md +6 -3
- package/harness/bundles/java/cursor/harness-review/SKILL.md +5 -2
- package/harness/bundles/java/cursor/harness-run/SKILL.md +5 -2
- package/harness/bundles/java/cursor/harness-run/reference.md +1 -1
- package/harness/bundles/java/cursor/harness-submit/SKILL.md +5 -2
- package/harness/bundles/java/cursor/harness-sync/SKILL.md +5 -3
- package/harness/bundles/java/cursor/harness-test/SKILL.md +5 -2
- package/harness/bundles/java/cursor/harness-test/reference.md +1 -1
- package/harness/bundles/java/cursor/protocols/ledger-protocol.md +1 -1
- package/harness/bundles/java/cursor/protocols/report-pipeline-protocol.md +11 -4
- package/harness/bundles/java/cursor/scripts/harness_archive.py +190 -32
- package/harness/bundles/java/cursor/scripts/harness_events.py +134 -40
- package/harness/bundles/java/cursor/scripts/harness_preflight.py +46 -3
- package/harness/bundles/java/cursor/scripts/harness_profile.py +2 -2
- package/harness/bundles/java/cursor/scripts/harness_state.py +148 -0
- package/harness/manifests/general/claude-code.json +24 -24
- package/harness/manifests/general/codebuddy.json +24 -24
- package/harness/manifests/general/codex.json +24 -24
- package/harness/manifests/general/cursor.json +24 -24
- package/harness/manifests/java/claude-code.json +26 -26
- package/harness/manifests/java/codebuddy.json +26 -26
- package/harness/manifests/java/codex.json +26 -26
- package/harness/manifests/java/cursor.json +26 -26
- package/hunter-workflow-family.json +1 -1
- package/package.json +1 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"""Harness events.ndjson writer and execution-log renderer (D2).
|
|
3
3
|
|
|
4
4
|
Subcommands:
|
|
5
|
-
append — append one schema_version
|
|
5
|
+
append — append one schema_version 3 event, then auto-render execution-log.md
|
|
6
6
|
render — full re-render of logs/execution-log.md from events.ndjson
|
|
7
7
|
summary — phase durations, event counts, and issue list (JSON)
|
|
8
8
|
|
|
@@ -42,7 +42,7 @@ EVENT_TYPES = frozenset(
|
|
|
42
42
|
}
|
|
43
43
|
)
|
|
44
44
|
|
|
45
|
-
SCHEMA_VERSION =
|
|
45
|
+
SCHEMA_VERSION = 3
|
|
46
46
|
HEADER_LINE = (
|
|
47
47
|
"本文件由 harness_events.py 自动渲染,请勿手工编辑;事实源为 events.ndjson"
|
|
48
48
|
)
|
|
@@ -62,6 +62,13 @@ OPTIONAL_FIELDS = (
|
|
|
62
62
|
"message",
|
|
63
63
|
"decision",
|
|
64
64
|
"reason",
|
|
65
|
+
"run_id",
|
|
66
|
+
"attempt",
|
|
67
|
+
"executor_tool",
|
|
68
|
+
"executor_agent",
|
|
69
|
+
"executor_model",
|
|
70
|
+
"handoff_from_tool",
|
|
71
|
+
"handoff_reason",
|
|
65
72
|
)
|
|
66
73
|
|
|
67
74
|
|
|
@@ -120,7 +127,7 @@ def duration_ms_between(start: Any, end: Any) -> int | None:
|
|
|
120
127
|
|
|
121
128
|
|
|
122
129
|
def normalize_event(raw: dict[str, Any]) -> dict[str, Any]:
|
|
123
|
-
"""Normalize schema_version 1/2 events for rendering and summary."""
|
|
130
|
+
"""Normalize schema_version 1/2/3 events for rendering and summary."""
|
|
124
131
|
event = dict(raw)
|
|
125
132
|
version = event.get("schema_version", 1)
|
|
126
133
|
try:
|
|
@@ -273,6 +280,15 @@ def build_event(args: argparse.Namespace, existing: list[dict[str, Any]]) -> dic
|
|
|
273
280
|
if value is None:
|
|
274
281
|
continue
|
|
275
282
|
event[field] = value
|
|
283
|
+
environment_defaults = {
|
|
284
|
+
"run_id": "HUNTER_HARNESS_RUN_ID",
|
|
285
|
+
"executor_tool": "HUNTER_HARNESS_TOOL",
|
|
286
|
+
"executor_agent": "HUNTER_HARNESS_AGENT",
|
|
287
|
+
"executor_model": "HUNTER_HARNESS_MODEL",
|
|
288
|
+
}
|
|
289
|
+
for field, env_name in environment_defaults.items():
|
|
290
|
+
if field not in event and os.environ.get(env_name):
|
|
291
|
+
event[field] = os.environ[env_name]
|
|
276
292
|
if "note" not in event:
|
|
277
293
|
event["note"] = ""
|
|
278
294
|
return event
|
|
@@ -333,6 +349,46 @@ def group_events_by_phase(events: list[dict[str, Any]]) -> list[tuple[str, list[
|
|
|
333
349
|
return [(phase, buckets[phase]) for phase in order]
|
|
334
350
|
|
|
335
351
|
|
|
352
|
+
def split_phase_attempts(phase_events: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
353
|
+
"""Split repeated starts into attempts and flag legacy events written after end."""
|
|
354
|
+
attempts: list[dict[str, Any]] = []
|
|
355
|
+
current: dict[str, Any] | None = None
|
|
356
|
+
next_attempt = 1
|
|
357
|
+
for event in phase_events:
|
|
358
|
+
if event.get("type") == "phase.start":
|
|
359
|
+
if current is not None:
|
|
360
|
+
current["warnings"].append("new phase.start before prior phase.end")
|
|
361
|
+
attempts.append(current)
|
|
362
|
+
raw_attempt = event.get("attempt")
|
|
363
|
+
attempt = raw_attempt if isinstance(raw_attempt, int) and raw_attempt > 0 else next_attempt
|
|
364
|
+
next_attempt = max(next_attempt, attempt + 1)
|
|
365
|
+
current = {"attempt": attempt, "events": [event], "warnings": []}
|
|
366
|
+
continue
|
|
367
|
+
if current is None:
|
|
368
|
+
if attempts:
|
|
369
|
+
prior_type = attempts[-1]["events"][-1].get("type")
|
|
370
|
+
attempts[-1]["events"].append(event)
|
|
371
|
+
if prior_type == "phase.end":
|
|
372
|
+
attempts[-1]["warnings"].append("event recorded after phase.end")
|
|
373
|
+
continue
|
|
374
|
+
raw_attempt = event.get("attempt")
|
|
375
|
+
attempt = raw_attempt if isinstance(raw_attempt, int) and raw_attempt > 0 else next_attempt
|
|
376
|
+
next_attempt = max(next_attempt, attempt + 1)
|
|
377
|
+
current = {
|
|
378
|
+
"attempt": attempt,
|
|
379
|
+
"events": [event],
|
|
380
|
+
"warnings": ["missing phase.start"],
|
|
381
|
+
}
|
|
382
|
+
else:
|
|
383
|
+
current["events"].append(event)
|
|
384
|
+
if event.get("type") == "phase.end":
|
|
385
|
+
attempts.append(current)
|
|
386
|
+
current = None
|
|
387
|
+
if current is not None:
|
|
388
|
+
attempts.append(current)
|
|
389
|
+
return attempts
|
|
390
|
+
|
|
391
|
+
|
|
336
392
|
def phase_start_time(phase_events: list[dict[str, Any]]) -> str:
|
|
337
393
|
for event in phase_events:
|
|
338
394
|
if event.get("type") == "phase.start":
|
|
@@ -352,7 +408,9 @@ def phase_duration_ms(phase_events: list[dict[str, Any]]) -> int | None:
|
|
|
352
408
|
elif etype == "phase.end":
|
|
353
409
|
end_ts = event.get("timestamp")
|
|
354
410
|
if start_ts and end_ts:
|
|
355
|
-
|
|
411
|
+
stamps = [e.get("timestamp") for e in phase_events if e.get("timestamp")]
|
|
412
|
+
effective_end = stamps[-1] if stamps else end_ts
|
|
413
|
+
return duration_ms_between(start_ts, effective_end)
|
|
356
414
|
# Fallback: first to last timestamp in the phase bucket.
|
|
357
415
|
stamps = [e.get("timestamp") for e in phase_events if e.get("timestamp")]
|
|
358
416
|
if len(stamps) >= 2:
|
|
@@ -400,7 +458,15 @@ def render_event_line(event: dict[str, Any]) -> list[str]:
|
|
|
400
458
|
if etype == "phase.start":
|
|
401
459
|
note = str(event.get("note") or "").strip()
|
|
402
460
|
suffix = f" — {note}" if note else ""
|
|
403
|
-
|
|
461
|
+
lines = [f"- phase.start @ {event.get('timestamp', '')}{suffix}"]
|
|
462
|
+
tool = str(event.get("executor_tool") or "").strip()
|
|
463
|
+
agent = str(event.get("executor_agent") or "").strip()
|
|
464
|
+
handoff = str(event.get("handoff_from_tool") or "").strip()
|
|
465
|
+
if handoff and tool:
|
|
466
|
+
lines.append(f"- 工具交接: {handoff} → {tool}")
|
|
467
|
+
elif tool:
|
|
468
|
+
lines.append(f"- 执行来源: {tool}" + (f" / {agent}" if agent else ""))
|
|
469
|
+
return lines
|
|
404
470
|
if etype == "phase.end":
|
|
405
471
|
note = str(event.get("note") or "").strip()
|
|
406
472
|
suffix = f" — {note}" if note else ""
|
|
@@ -445,29 +511,34 @@ def render_execution_log(events: list[dict[str, Any]]) -> str:
|
|
|
445
511
|
return "\n".join(lines)
|
|
446
512
|
|
|
447
513
|
for phase, phase_events in group_events_by_phase(events):
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
514
|
+
attempts = split_phase_attempts(phase_events)
|
|
515
|
+
for attempt_record in attempts:
|
|
516
|
+
attempt_events = attempt_record["events"]
|
|
517
|
+
start = phase_start_time(attempt_events) or "—"
|
|
518
|
+
duration = phase_duration_ms(attempt_events)
|
|
519
|
+
label = f"{phase}(尝试 {attempt_record['attempt']})" if len(attempts) > 1 else phase
|
|
520
|
+
lines.append(f"## {label} — {start}")
|
|
521
|
+
lines.append("")
|
|
522
|
+
if duration is not None:
|
|
523
|
+
lines.append(f"- 阶段耗时: {format_duration(duration)}")
|
|
524
|
+
for warning in attempt_record["warnings"]:
|
|
525
|
+
lines.append(f"- 生命周期警告: {warning}")
|
|
526
|
+
buffer: list[dict[str, Any]] = []
|
|
527
|
+
|
|
528
|
+
def flush_commands() -> None:
|
|
529
|
+
nonlocal buffer
|
|
530
|
+
if buffer:
|
|
531
|
+
lines.extend(render_command_block(buffer))
|
|
532
|
+
buffer = []
|
|
533
|
+
|
|
534
|
+
for event in attempt_events:
|
|
535
|
+
if event.get("type") == "command":
|
|
536
|
+
buffer.append(event)
|
|
537
|
+
continue
|
|
538
|
+
flush_commands()
|
|
539
|
+
lines.extend(render_event_line(event))
|
|
467
540
|
flush_commands()
|
|
468
|
-
lines.
|
|
469
|
-
flush_commands()
|
|
470
|
-
lines.append("")
|
|
541
|
+
lines.append("")
|
|
471
542
|
|
|
472
543
|
return "\n".join(lines).rstrip() + "\n"
|
|
473
544
|
|
|
@@ -485,21 +556,37 @@ def build_summary(change_dir: Path, events: list[dict[str, Any]]) -> dict[str, A
|
|
|
485
556
|
issues: list[dict[str, Any]] = []
|
|
486
557
|
|
|
487
558
|
for phase, phase_events in group_events_by_phase(events):
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
if
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
559
|
+
attempt_records: list[dict[str, Any]] = []
|
|
560
|
+
for record in split_phase_attempts(phase_events):
|
|
561
|
+
attempt_events = record["events"]
|
|
562
|
+
starts = [e for e in attempt_events if e.get("type") == "phase.start"]
|
|
563
|
+
ends = [e for e in attempt_events if e.get("type") == "phase.end"]
|
|
564
|
+
start_ts = starts[0].get("timestamp") if starts else None
|
|
565
|
+
end_ts = ends[-1].get("timestamp") if ends else None
|
|
566
|
+
provenance = starts[0] if starts else (attempt_events[0] if attempt_events else {})
|
|
567
|
+
attempt_records.append({
|
|
568
|
+
"attempt": record["attempt"],
|
|
569
|
+
"event_count": len(attempt_events),
|
|
570
|
+
"started_at": start_ts,
|
|
571
|
+
"ended_at": end_ts,
|
|
572
|
+
"duration_ms": phase_duration_ms(attempt_events),
|
|
573
|
+
"status": ends[-1].get("status") if ends else None,
|
|
574
|
+
"run_id": provenance.get("run_id"),
|
|
575
|
+
"executor_tool": provenance.get("executor_tool"),
|
|
576
|
+
"executor_agent": provenance.get("executor_agent"),
|
|
577
|
+
"handoff_from_tool": provenance.get("handoff_from_tool"),
|
|
578
|
+
"warnings": record["warnings"],
|
|
579
|
+
})
|
|
580
|
+
durations = [a["duration_ms"] for a in attempt_records if a["duration_ms"] is not None]
|
|
581
|
+
first = attempt_records[0] if attempt_records else {}
|
|
582
|
+
last = attempt_records[-1] if attempt_records else {}
|
|
498
583
|
phases[phase] = {
|
|
499
584
|
"event_count": len(phase_events),
|
|
500
|
-
"started_at":
|
|
501
|
-
"ended_at":
|
|
502
|
-
"duration_ms":
|
|
585
|
+
"started_at": first.get("started_at"),
|
|
586
|
+
"ended_at": last.get("ended_at"),
|
|
587
|
+
"duration_ms": sum(durations) if durations else None,
|
|
588
|
+
"status": last.get("status"),
|
|
589
|
+
"attempts": attempt_records,
|
|
503
590
|
}
|
|
504
591
|
|
|
505
592
|
for event in events:
|
|
@@ -649,6 +736,13 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
649
736
|
p_append.add_argument("--message", default=None)
|
|
650
737
|
p_append.add_argument("--decision", default=None)
|
|
651
738
|
p_append.add_argument("--reason", default=None)
|
|
739
|
+
p_append.add_argument("--run-id", default=None)
|
|
740
|
+
p_append.add_argument("--attempt", type=int, default=None)
|
|
741
|
+
p_append.add_argument("--executor-tool", default=None)
|
|
742
|
+
p_append.add_argument("--executor-agent", default=None)
|
|
743
|
+
p_append.add_argument("--executor-model", default=None)
|
|
744
|
+
p_append.add_argument("--handoff-from-tool", default=None)
|
|
745
|
+
p_append.add_argument("--handoff-reason", default=None)
|
|
652
746
|
p_append.set_defaults(func=cmd_append)
|
|
653
747
|
|
|
654
748
|
p_render = sub.add_parser(
|
|
@@ -313,7 +313,23 @@ def _parse_scalar(val: str) -> Any:
|
|
|
313
313
|
return val
|
|
314
314
|
|
|
315
315
|
|
|
316
|
-
def
|
|
316
|
+
def _resolve_agents_root(skills_root: Path, agents_root: Path | None) -> tuple[Path | None, str | None]:
|
|
317
|
+
"""Resolve agent definitions without confusing a skills root with its sibling."""
|
|
318
|
+
if agents_root is not None:
|
|
319
|
+
return agents_root.resolve(), None
|
|
320
|
+
if skills_root.name == "skills" and skills_root.parent.name in (".claude", ".codebuddy"):
|
|
321
|
+
return (skills_root.parent / "agents").resolve(), None
|
|
322
|
+
if skills_root.name == "skills" and skills_root.parent.name in (".agents", ".cursor"):
|
|
323
|
+
return None, "CUSTOM_AGENTS_UNSUPPORTED"
|
|
324
|
+
return (skills_root / "agents").resolve(), None
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def cmd_check_agents(
|
|
328
|
+
skills_root: Path,
|
|
329
|
+
agent: str,
|
|
330
|
+
*,
|
|
331
|
+
agents_root: Path | None = None,
|
|
332
|
+
) -> dict[str, Any]:
|
|
317
333
|
skills_root = skills_root.resolve()
|
|
318
334
|
agent_name = agent.strip()
|
|
319
335
|
if not agent_name:
|
|
@@ -325,9 +341,23 @@ def cmd_check_agents(skills_root: Path, agent: str) -> dict[str, Any]:
|
|
|
325
341
|
"reason": "agent name is empty",
|
|
326
342
|
}
|
|
327
343
|
|
|
344
|
+
resolved_agents_root, capability_error = _resolve_agents_root(skills_root, agents_root)
|
|
345
|
+
if capability_error is not None:
|
|
346
|
+
return {
|
|
347
|
+
"ok": True,
|
|
348
|
+
"action": "check-agents",
|
|
349
|
+
"agent": agent_name,
|
|
350
|
+
"usable": False,
|
|
351
|
+
"definitionValid": False,
|
|
352
|
+
"runtimeSupported": False,
|
|
353
|
+
"reasonCode": capability_error,
|
|
354
|
+
"reason": "current adapter runtime does not support custom agent definitions; use main-session fallback",
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
assert resolved_agents_root is not None
|
|
328
358
|
# Accept bare name or with .md
|
|
329
359
|
filename = agent_name if agent_name.endswith(".md") else f"{agent_name}.md"
|
|
330
|
-
agent_path =
|
|
360
|
+
agent_path = resolved_agents_root / filename
|
|
331
361
|
|
|
332
362
|
if not agent_path.is_file():
|
|
333
363
|
return {
|
|
@@ -335,7 +365,11 @@ def cmd_check_agents(skills_root: Path, agent: str) -> dict[str, Any]:
|
|
|
335
365
|
"action": "check-agents",
|
|
336
366
|
"agent": agent_name,
|
|
337
367
|
"path": str(agent_path),
|
|
368
|
+
"agentsRoot": str(resolved_agents_root),
|
|
338
369
|
"usable": False,
|
|
370
|
+
"definitionValid": False,
|
|
371
|
+
"runtimeSupported": True,
|
|
372
|
+
"reasonCode": "AGENT_DEFINITION_NOT_FOUND",
|
|
339
373
|
"reason": f"agent file not found: {agent_path}",
|
|
340
374
|
}
|
|
341
375
|
|
|
@@ -390,7 +424,11 @@ def cmd_check_agents(skills_root: Path, agent: str) -> dict[str, Any]:
|
|
|
390
424
|
"action": "check-agents",
|
|
391
425
|
"agent": agent_name,
|
|
392
426
|
"path": str(agent_path),
|
|
427
|
+
"agentsRoot": str(resolved_agents_root),
|
|
393
428
|
"usable": True,
|
|
429
|
+
"definitionValid": True,
|
|
430
|
+
"runtimeSupported": True,
|
|
431
|
+
"reasonCode": "READY",
|
|
394
432
|
"reason": "agent file exists; frontmatter parsed; tools declared",
|
|
395
433
|
"name": name_field,
|
|
396
434
|
"tools": tools,
|
|
@@ -431,6 +469,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
431
469
|
|
|
432
470
|
p_agents = sub.add_parser("check-agents", help="Validate agent definition usability")
|
|
433
471
|
p_agents.add_argument("--skills-root", required=True, type=Path)
|
|
472
|
+
p_agents.add_argument("--agents-root", type=Path)
|
|
434
473
|
p_agents.add_argument("--agent", required=True)
|
|
435
474
|
p_agents.add_argument("--json", action="store_true")
|
|
436
475
|
|
|
@@ -459,7 +498,11 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
459
498
|
)
|
|
460
499
|
return emit_json(result, ok=bool(result.get("ok", False)))
|
|
461
500
|
if args.command == "check-agents":
|
|
462
|
-
result = cmd_check_agents(
|
|
501
|
+
result = cmd_check_agents(
|
|
502
|
+
args.skills_root,
|
|
503
|
+
args.agent,
|
|
504
|
+
agents_root=args.agents_root,
|
|
505
|
+
)
|
|
463
506
|
return emit_json(result, ok=True)
|
|
464
507
|
|
|
465
508
|
parser.error(f"unknown command: {args.command}")
|
|
@@ -223,8 +223,8 @@ def _java_commands(reactor_modules: list[str], pom_hash: str) -> dict[str, Any]:
|
|
|
223
223
|
"basis": dict(basis),
|
|
224
224
|
},
|
|
225
225
|
"package": {
|
|
226
|
-
"command": "mvn -f pom.xml package '-Dmaven.test.skip=true'",
|
|
227
|
-
"argvTemplate": ["mvn", "-f", "pom.xml", "package", "-Dmaven.test.skip=true"],
|
|
226
|
+
"command": "mvn -f pom.xml clean package '-Dmaven.test.skip=true'",
|
|
227
|
+
"argvTemplate": ["mvn", "-f", "pom.xml", "clean", "package", "-Dmaven.test.skip=true"],
|
|
228
228
|
"scope": "module",
|
|
229
229
|
"inputs": _cmd_inputs(reactor_modules, pom=True, main=True, test=True),
|
|
230
230
|
"coverage": "package",
|
|
@@ -12,9 +12,11 @@ Python 3.10+ stdlib only. UTF-8 without BOM. Windows path friendly.
|
|
|
12
12
|
from __future__ import annotations
|
|
13
13
|
|
|
14
14
|
import datetime as dt
|
|
15
|
+
import argparse
|
|
15
16
|
import hashlib
|
|
16
17
|
import json
|
|
17
18
|
import os
|
|
19
|
+
import subprocess
|
|
18
20
|
import sys
|
|
19
21
|
from pathlib import Path
|
|
20
22
|
from typing import Any
|
|
@@ -177,3 +179,149 @@ def refresh_segments(
|
|
|
177
179
|
refreshed["segments"] = refreshed_segments
|
|
178
180
|
refreshed["generatedAt"] = now_iso()
|
|
179
181
|
return refreshed
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _git(project: Path, *args: str) -> str:
|
|
185
|
+
proc = subprocess.run(
|
|
186
|
+
["git", "-C", str(project), *args],
|
|
187
|
+
capture_output=True,
|
|
188
|
+
text=True,
|
|
189
|
+
encoding="utf-8",
|
|
190
|
+
errors="replace",
|
|
191
|
+
check=False,
|
|
192
|
+
)
|
|
193
|
+
return proc.stdout.strip() if proc.returncode == 0 else ""
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _existing_files(paths: list[Path]) -> list[str]:
|
|
197
|
+
return sorted({str(path.resolve()) for path in paths if path.is_file()})
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def discover_segment_files(
|
|
201
|
+
project: Path, change_dir: Path, *, base: str = "", head: str = "HEAD"
|
|
202
|
+
) -> dict[str, list[str]]:
|
|
203
|
+
"""Discover the compact inputs shared by plan/run/test/review/submit."""
|
|
204
|
+
project = project.resolve()
|
|
205
|
+
rules = [project / "AGENTS.md", project / "CLAUDE.md"]
|
|
206
|
+
for pattern in (
|
|
207
|
+
".claude/rules/**/*",
|
|
208
|
+
".cursor/rules/**/*",
|
|
209
|
+
".codebuddy/rules/**/*",
|
|
210
|
+
".codebuddy/.rules/**/*",
|
|
211
|
+
):
|
|
212
|
+
rules.extend(project.glob(pattern))
|
|
213
|
+
|
|
214
|
+
profile = [
|
|
215
|
+
project / ".harness/config/build-profile.json",
|
|
216
|
+
project / ".harness/context-index.json",
|
|
217
|
+
project / "project.yaml",
|
|
218
|
+
project / "harness.json",
|
|
219
|
+
]
|
|
220
|
+
codebase_map = list((project / ".harness/codebase/map").glob("**/*"))
|
|
221
|
+
knowledge = [
|
|
222
|
+
project / ".harness/knowledge/index.json",
|
|
223
|
+
project / ".harness/knowledge/status.json",
|
|
224
|
+
project / ".harness/knowledge/config.json",
|
|
225
|
+
]
|
|
226
|
+
change_inputs: list[Path] = []
|
|
227
|
+
for folder in ("spec", "plans", "meta"):
|
|
228
|
+
change_inputs.extend((change_dir / folder).glob("**/*"))
|
|
229
|
+
change_inputs = [path for path in change_inputs if path != snapshot_path(change_dir)]
|
|
230
|
+
|
|
231
|
+
changed_names: list[str] = []
|
|
232
|
+
if base:
|
|
233
|
+
raw = _git(project, "diff", "--name-only", "--diff-filter=ACMRT", f"{base}..{head}")
|
|
234
|
+
changed_names = [line for line in raw.splitlines() if line.strip()]
|
|
235
|
+
code = [project / name for name in changed_names]
|
|
236
|
+
return {
|
|
237
|
+
"profile": _existing_files(profile),
|
|
238
|
+
"rules": _existing_files(rules),
|
|
239
|
+
"map": _existing_files(codebase_map),
|
|
240
|
+
"knowledge": _existing_files(knowledge),
|
|
241
|
+
"change": _existing_files(change_inputs),
|
|
242
|
+
"code": _existing_files(code),
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def capture_current_state(
|
|
247
|
+
*,
|
|
248
|
+
project: Path,
|
|
249
|
+
change_dir: Path,
|
|
250
|
+
change_name: str,
|
|
251
|
+
worktree_root: Path,
|
|
252
|
+
base: str = "",
|
|
253
|
+
head: str = "HEAD",
|
|
254
|
+
) -> tuple[dict[str, Any], list[str]]:
|
|
255
|
+
resolved_head = _git(project, "rev-parse", head) or head
|
|
256
|
+
resolved_base = _git(project, "rev-parse", base) if base else ""
|
|
257
|
+
files = discover_segment_files(project, change_dir, base=resolved_base, head=resolved_head)
|
|
258
|
+
previous = load_snapshot(change_dir)
|
|
259
|
+
fresh = capture_snapshot(
|
|
260
|
+
change_dir,
|
|
261
|
+
change_name=change_name,
|
|
262
|
+
project=project,
|
|
263
|
+
worktree_root=worktree_root,
|
|
264
|
+
base=resolved_base,
|
|
265
|
+
head=resolved_head,
|
|
266
|
+
segment_files=files,
|
|
267
|
+
)
|
|
268
|
+
changed: list[str] = []
|
|
269
|
+
if previous:
|
|
270
|
+
old_segments = previous.get("segments") or {}
|
|
271
|
+
for name, segment in fresh["segments"].items():
|
|
272
|
+
old = old_segments.get(name)
|
|
273
|
+
if isinstance(old, dict) and old.get("fingerprint") == segment.get("fingerprint"):
|
|
274
|
+
segment["capturedAt"] = old.get("capturedAt", segment["capturedAt"])
|
|
275
|
+
else:
|
|
276
|
+
changed.append(name)
|
|
277
|
+
else:
|
|
278
|
+
changed = list(fresh["segments"])
|
|
279
|
+
write_snapshot(change_dir, fresh)
|
|
280
|
+
return fresh, changed
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
284
|
+
parser = argparse.ArgumentParser(description="Capture reusable Harness state")
|
|
285
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
286
|
+
capture = sub.add_parser("capture", help="capture or refresh changed segments")
|
|
287
|
+
capture.add_argument("--project", default=".")
|
|
288
|
+
capture.add_argument("--change-dir", required=True)
|
|
289
|
+
capture.add_argument("--change-name")
|
|
290
|
+
capture.add_argument("--worktree-root")
|
|
291
|
+
capture.add_argument("--base", default="")
|
|
292
|
+
capture.add_argument("--head", default="HEAD")
|
|
293
|
+
capture.add_argument("--json", action="store_true")
|
|
294
|
+
return parser
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def main(argv: list[str] | None = None) -> int:
|
|
298
|
+
args = build_parser().parse_args(argv)
|
|
299
|
+
project = Path(args.project).resolve()
|
|
300
|
+
change_dir = Path(args.change_dir).resolve()
|
|
301
|
+
name = args.change_name or change_dir.name
|
|
302
|
+
worktree = Path(args.worktree_root).resolve() if args.worktree_root else project
|
|
303
|
+
snapshot, changed = capture_current_state(
|
|
304
|
+
project=project,
|
|
305
|
+
change_dir=change_dir,
|
|
306
|
+
change_name=name,
|
|
307
|
+
worktree_root=worktree,
|
|
308
|
+
base=args.base,
|
|
309
|
+
head=args.head,
|
|
310
|
+
)
|
|
311
|
+
result = {
|
|
312
|
+
"ok": True,
|
|
313
|
+
"path": str(snapshot_path(change_dir)),
|
|
314
|
+
"changedSegments": changed,
|
|
315
|
+
"reusedSegments": sorted(set(snapshot["segments"]) - set(changed)),
|
|
316
|
+
"git": snapshot["git"],
|
|
317
|
+
}
|
|
318
|
+
if args.json:
|
|
319
|
+
print(json.dumps(result, ensure_ascii=False))
|
|
320
|
+
else:
|
|
321
|
+
print(f"state snapshot: {result['path']}")
|
|
322
|
+
print(f"changed: {', '.join(changed) if changed else 'none'}")
|
|
323
|
+
return 0
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
if __name__ == "__main__":
|
|
327
|
+
raise SystemExit(main())
|