@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
|
@@ -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())
|
|
@@ -3,7 +3,7 @@ name: harness-archive
|
|
|
3
3
|
description: 归档所有变更产出(计划/测试报告/审查/SQL/API文档)到
|
|
4
4
|
.harness/archive/,含归档元数据和可视化最终总结。使用场景:归档、完成归档、收尾、打包产出物
|
|
5
5
|
---
|
|
6
|
-
<!-- generated by harness_deploy.py; core=
|
|
6
|
+
<!-- generated by harness_deploy.py; core=7e031c982caba51d; overlay=none; agent=codex; do not edit -->
|
|
7
7
|
# harness-archive — 变更归档
|
|
8
8
|
|
|
9
9
|
## Purpose
|
|
@@ -49,6 +49,7 @@ description: 归档所有变更产出(计划/测试报告/审查/SQL/API文档
|
|
|
49
49
|
6. **implementation-detail** — 自适应执行参考;legacy 缺失 🟡WARN,不阻断
|
|
50
50
|
7. **test-scenarios** — 测试真相源:`plans/<change>-test-scenarios.md`
|
|
51
51
|
8. **禁止读取 `docs/superpowers/` 作为正式输入** — 旧草稿仅人工线索
|
|
52
|
+
9. **每个 skill 边界只刷新一次状态快照** — 先运行 `python <skills-root>/scripts/harness_state.py capture --project . --change-dir ".harness/changes/<change-name>" --base <baseCommit> --json`;`changedSegments=[]` 时复用已有 profile/rules/map/knowledge/change/code 指纹,不再重复跑全量 `harness-sync`、知识重建或代码库扫描。只有对应 segment 变化或缺失时才定向刷新;快照不能替代源码读取和验证门禁。
|
|
52
53
|
|
|
53
54
|
状态目录分层:新路径优先,旧路径兼容 → [[../protocols/state-layout-protocol.md|state-layout-protocol]]
|
|
54
55
|
|
|
@@ -185,7 +186,7 @@ git 命令通过 `powershell.exe -Command "..."` 执行;archive-meta.md 和 fi
|
|
|
185
186
|
|
|
186
187
|
## 执行日志
|
|
187
188
|
|
|
188
|
-
`events.ndjson` 为唯一事实源(schema_version
|
|
189
|
+
`events.ndjson` 为唯一事实源(schema_version 3,兼容读取 v1/v2;`note` 承载人类可读摘要);`logs/execution-log.md` 由 `harness_events.py` 渲染,**禁止用 Write/Edit 直接维护**。直接修改的内容会在 `phase.end` 或 finalize 时被完整重建覆盖,属于数据丢失;需要保留的详情必须进入事件 `note`。结构 → [[../protocols/report-pipeline-protocol.md|report-pipeline-protocol]]
|
|
189
190
|
|
|
190
191
|
```powershell
|
|
191
192
|
python <skills-root>/scripts/harness_events.py append --change-dir ".harness/changes/<change-name>" --phase <phase> --type phase.start --note "<触发指令>"
|
|
@@ -194,3 +195,5 @@ python <skills-root>/scripts/harness_events.py append --change-dir ".harness/cha
|
|
|
194
195
|
> **脚本接线**:`harness_events.py append`;`harness_archive.py finalize`;`harness_preflight.py check`;`harness_ledger.py can-reuse`;`harness_service.py ensure/stop`(须 `--files`/`serviceStart.inputFiles`)。JSON 输出按 D13 护栏解读。
|
|
195
196
|
|
|
196
197
|
> **Task 4 §6.1 写入契约**:普通 `append` = 加锁 -> 追加一行 -> fsync -> 解锁,**不 load 历史、不渲染**(O(1),跨进程锁 `events.ndjson.lock`,UUID 用完整 `uuid4().hex` 无需去重扫描)。仅 `--type phase.end` append 在追加成功后渲染一次 `execution-log.md`;显式 `harness_events.py render` 随时从完整 events 重建;`harness_archive.py finalize` 在 collect 前强制 render 一次。高频 command append 期间 log 可能滞后,phase 边界保持最新。
|
|
198
|
+
|
|
199
|
+
每次阶段结束必须写 `--status OK|WARN|FAIL|BLOCKED`;重试同一阶段时增加 `--attempt <n>`,不得覆盖或伪装成一次执行。跨工具继续执行时写 `--executor-tool <codex|claude-code|codebuddy|cursor>`,并在接棒事件写 `--handoff-from-tool` / `--handoff-reason`;也可由 `HUNTER_HARNESS_TOOL/AGENT/MODEL/RUN_ID` 环境变量统一注入。
|
|
@@ -92,4 +92,4 @@ powershell.exe -NoProfile -Command "& '<node-path>' 'harness-skills/harness-arch
|
|
|
92
92
|
|
|
93
93
|
## 执行日志记录
|
|
94
94
|
|
|
95
|
-
归档只向 `events.ndjson` 追加事件(schema_version
|
|
95
|
+
归档只向 `events.ndjson` 追加事件(schema_version 3,兼容读取 v1/v2);`logs/execution-log.md` 由 `harness_events.py append` 自动渲染,禁止手工 Edit。事件类型与脚本用法见 SKILL.md `## 执行日志` 与 `../protocols/report-pipeline-protocol.md`。
|
|
@@ -1,171 +1,94 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
//
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
// Keeps UTF-8, avoids PowerShell string interpolation issues.
|
|
6
|
-
// All numbers come from summary-data.json or manifest — never re-inferred here.
|
|
7
|
-
|
|
8
|
-
import fs from 'node:fs';
|
|
9
|
-
import path from 'node:path';
|
|
2
|
+
// Compact deterministic renderer for Harness archive summary-data.json.
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
10
5
|
|
|
11
6
|
const args = process.argv.slice(2);
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
return
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
const outPath = getArg('--out', args[1] || 'final-summary.html');
|
|
7
|
+
const arg = (name, fallback) => {
|
|
8
|
+
const index = args.indexOf(name);
|
|
9
|
+
return index >= 0 && args[index + 1] ? args[index + 1] : fallback;
|
|
10
|
+
};
|
|
11
|
+
const summaryPath = arg("--summary", args[0]);
|
|
12
|
+
const outPath = arg("--out", args[1] || "final-summary.html");
|
|
19
13
|
if (!summaryPath) {
|
|
20
|
-
console.error(
|
|
14
|
+
console.error("Usage: node render-summary.mjs --summary summary-data.json --out final-summary.html");
|
|
21
15
|
process.exit(2);
|
|
22
16
|
}
|
|
23
17
|
|
|
24
|
-
const data = JSON.parse(fs.readFileSync(summaryPath,
|
|
25
|
-
const esc = (
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
return
|
|
18
|
+
const data = JSON.parse(fs.readFileSync(summaryPath, "utf8"));
|
|
19
|
+
const esc = (value) => String(value ?? "").replace(/[&<>"']/g, (char) => ({
|
|
20
|
+
"&": "&", "<": "<", ">": ">", '"': """, "'": "'"
|
|
21
|
+
})[char]);
|
|
22
|
+
const list = (value) => Array.isArray(value) ? value : [];
|
|
23
|
+
const record = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
24
|
+
const number = (value) => Number(value) || 0;
|
|
25
|
+
const shortHash = (value) => String(value || "N/A").slice(0, 10);
|
|
26
|
+
const statusClass = (value) => {
|
|
27
|
+
const status = String(value || "UNKNOWN").toUpperCase();
|
|
28
|
+
if (/FAIL|ERROR/.test(status)) return "danger";
|
|
29
|
+
if (/WARN|BLOCK|SKIP|CONDITIONAL|PARTIAL|NOT_RUN|UNKNOWN/.test(status)) return "warning";
|
|
30
|
+
if (/ADVISORY|REUSED/.test(status)) return "neutral";
|
|
31
|
+
return "success";
|
|
35
32
|
};
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const m = String(rate).match(/(\d+)\s*\/\s*(\d+)/);
|
|
42
|
-
return m ? { passed: +m[1], total: +m[2] } : null;
|
|
33
|
+
const pill = (value) => `<span class="pill ${statusClass(value)}">${esc(value || "UNKNOWN")}</span>`;
|
|
34
|
+
const describe = (value) => {
|
|
35
|
+
if (typeof value === "string") return value;
|
|
36
|
+
if (!value || typeof value !== "object") return String(value ?? "");
|
|
37
|
+
return value.message || value.action || value.note || value.summary || JSON.stringify(value);
|
|
43
38
|
};
|
|
44
|
-
const
|
|
45
|
-
const
|
|
46
|
-
if (
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
return `<div class="rate"><div class="bar-track"><div class="bar-fill ${cls}" style="width:${pct}%"></div></div><span class="muted small">${esc(rateStr)} · ${pct}%</span></div>`;
|
|
39
|
+
const duration = (minutes) => {
|
|
40
|
+
const value = number(minutes);
|
|
41
|
+
if (value < 1) return `${Math.round(value * 60)} 秒`;
|
|
42
|
+
if (value < 60) return `${Math.round(value * 10) / 10} 分钟`;
|
|
43
|
+
return `${Math.floor(value / 60)} 小时 ${Math.round(value % 60)} 分钟`;
|
|
50
44
|
};
|
|
51
45
|
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
const
|
|
56
|
-
const
|
|
57
|
-
const
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const diffStat = obj(data.diffStat);
|
|
67
|
-
const changedRows = arr(data.changedFiles).map(f => `<tr><td><code>${esc(f.path || f.file || '')}</code></td><td>${esc(f.summary || f.change || '')}</td><td class="ins">+${esc(f.insertions ?? 0)}</td><td class="del">-${esc(f.deletions ?? 0)}</td></tr>`).join('\n') || `<tr><td colspan="4" class="muted">未记录变更文件</td></tr>`;
|
|
68
|
-
const artifactList = arr(data.artifacts);
|
|
69
|
-
const artifactRows = artifactList.map(a => `<tr><td>${esc(a.name || '')}</td><td><code>${esc(a.path || '')}</code></td><td>${esc(a.size || '')}</td><td><code>${esc(a.sha256 || '')}</code></td></tr>`).join('\n');
|
|
70
|
-
const artifactCard = artifactList.length > 0 ? `<div class="card"><h2>📦 产物清单</h2><table><tr><th>名称</th><th>路径</th><th>大小</th><th>SHA-256</th></tr>${artifactRows}</table></div>` : '';
|
|
71
|
-
const notes = arr(data.maintenanceNotes).map(n => `<li>${esc(n)}</li>`).join('\n') || `<li class="muted">无额外维护说明</li>`;
|
|
72
|
-
const risks = arr(data.knownRisks).map(n => `<li>${esc(n)}</li>`).join('\n') || `<li class="muted">无已知遗留风险</li>`;
|
|
73
|
-
const manual = arr(data.manualActions).map(n => `<li>${esc(n)}</li>`).join('\n') || `<li class="muted">无人工后续动作</li>`;
|
|
74
|
-
const uncommittedList = arr(data.uncommittedTestEvidence);
|
|
75
|
-
const uncommittedTests = uncommittedList.map(n => `<li>${esc(n)}</li>`).join('\n');
|
|
76
|
-
const uncommittedCard = uncommittedList.length > 0 ? `<div class="card"><h2>🧪 未提交测试证据</h2><ul>${uncommittedTests}</ul></div>` : '';
|
|
77
|
-
const review = obj(data.reviewSummary);
|
|
78
|
-
const manifest = obj(data.archiveManifest);
|
|
79
|
-
const timeline = arr(data.timeline).map(t => `<tr><td>${esc(t.stage || '')}</td><td>${badge(t.result || '')}</td><td>${esc(t.evidence || '')}</td></tr>`).join('\n');
|
|
80
|
-
|
|
81
|
-
// durations + skillCalls (schema 2.1); fall back to timeline when absent
|
|
82
|
-
const durations = obj(data.durations);
|
|
83
|
-
const durStages = arr(durations.stages);
|
|
84
|
-
const maxMin = durStages.reduce((m, s) => Math.max(m, num(s.minutes)), 0);
|
|
85
|
-
const durRows = durStages.map(s => {
|
|
86
|
-
const min = num(s.minutes);
|
|
87
|
-
const pct = maxMin > 0 ? Math.round(min / maxMin * 100) : 0;
|
|
88
|
-
return `<div class="dur-row"><div class="dur-label">${esc(s.skill || s.stage || '')} <span class="muted small">${esc(s.result || '')}</span></div><div class="dur-track"><div class="dur-bar" style="width:${pct}%"></div></div><div class="dur-min">${esc(min)}m</div></div>`;
|
|
89
|
-
}).join('\n');
|
|
90
|
-
const skillCallsHtml = arr(data.skillCalls).map(s => `${esc(s.skill)}×${esc(s.count)} ${badge(s.result)}`).join(' ') || '<span class="muted">未记录</span>';
|
|
91
|
-
|
|
92
|
-
const durCard = durStages.length > 0
|
|
93
|
-
? `<div class="card"><h2>⏱️ 阶段耗时与 Skill 调用</h2>${durRows}<div class="muted small" style="margin-top:10px">Skill 调用:${skillCallsHtml}</div></div>`
|
|
94
|
-
: (timeline ? `<div class="card"><h2>阶段时间线</h2><table><tr><th>阶段</th><th>结果</th><th>证据</th></tr>${timeline}</table></div>` : '');
|
|
46
|
+
const stages = Object.entries(record(data.stageStatus));
|
|
47
|
+
const verification = record(data.verification);
|
|
48
|
+
const unit = record(verification.unitTests);
|
|
49
|
+
const api = record(verification.apiTests);
|
|
50
|
+
const durations = record(data.durations);
|
|
51
|
+
const durationStages = list(durations.stages);
|
|
52
|
+
const maxMinutes = Math.max(1, ...durationStages.map((item) => number(item.minutes)));
|
|
53
|
+
const diff = record(data.diffStat);
|
|
54
|
+
const risks = list(data.knownRisks);
|
|
55
|
+
const actions = list(data.manualActions);
|
|
56
|
+
const files = list(data.changedFiles);
|
|
57
|
+
const commands = list(record(data.reportPipeline).commands);
|
|
58
|
+
const timeline = list(data.timeline);
|
|
95
59
|
|
|
96
|
-
const
|
|
60
|
+
const stageHtml = stages.map(([name, status]) => `<div class="row"><span>${esc(name)}</span>${pill(status)}</div>`).join("") || '<p class="empty">没有阶段状态记录</p>';
|
|
61
|
+
const verificationHtml = [
|
|
62
|
+
["单元测试", unit.status || ((number(unit.failures) + number(unit.errors)) > 0 ? "FAIL" : (number(unit.run) > 0 ? "OK" : "NOT_RUN")), `${number(unit.run)} 个 · ${number(unit.failures)} 失败 · ${number(unit.errors)} 错误`],
|
|
63
|
+
["API 测试", api.status || "NOT_RUN", `${number(api.passed)}/${number(api.total)} 通过 · ${number(api.blocked)} 阻塞`],
|
|
64
|
+
["数据库兼容", verification.dbCompatibility || "NOT_RUN", verification.coverageDisplay || "未记录覆盖率"]
|
|
65
|
+
].map(([name, status, note]) => `<div class="row"><div><strong>${esc(name)}</strong><small>${esc(note)}</small></div>${pill(status)}</div>`).join("");
|
|
66
|
+
const durationHtml = durationStages.map((item) => {
|
|
67
|
+
const width = Math.max(2, Math.round(number(item.minutes) / maxMinutes * 100));
|
|
68
|
+
const attempts = list(item.attempts).length;
|
|
69
|
+
return `<div class="duration"><div><span>${esc(item.skill || item.stage)}</span><span>${duration(item.minutes)}${attempts > 1 ? ` · ${attempts} 次尝试` : ""}</span></div><i><b style="width:${width}%"></b></i></div>`;
|
|
70
|
+
}).join("") || '<p class="empty">没有可计算的阶段耗时</p>';
|
|
71
|
+
const riskHtml = risks.map((item) => `<li>${esc(describe(item))}</li>`).join("") || "<li>未记录已知风险</li>";
|
|
72
|
+
const actionHtml = actions.map((item) => `<li>${esc(describe(item))}</li>`).join("") || "<li>无需人工后续动作</li>";
|
|
73
|
+
const fileRows = files.map((item) => `<tr><td><code>${esc(item.path || item.file)}</code></td><td class="plus">+${number(item.insertions)}</td><td class="minus">-${number(item.deletions)}</td></tr>`).join("") || '<tr><td colspan="3">没有变更文件证据</td></tr>';
|
|
74
|
+
const timelineRows = timeline.map((item) => `<tr><td>${esc(item.phase || item.stage || "-")}</td><td>${esc(item.attempt || "-")}</td><td>${pill(item.status || item.result || item.type)}</td><td>${esc(item.executorTool || item.executor_tool || item.summary || "-")}</td></tr>`).join("") || '<tr><td colspan="4">没有时间线记录</td></tr>';
|
|
75
|
+
const commandRows = commands.map((item) => `<tr><td>${esc(item.phase || "-")}</td><td><code>${esc(item.command)}</code></td><td>${pill(number(item.exit_code) === 0 ? "OK" : "FAIL")}</td></tr>`).join("") || '<tr><td colspan="3">没有命令证据</td></tr>';
|
|
97
76
|
|
|
98
|
-
const html = `<!doctype html>
|
|
99
|
-
<
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
.
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
h3 { margin: 18px 0 8px; }
|
|
113
|
-
.muted { color: #94a3b8; }
|
|
114
|
-
.small { font-size: 12px; }
|
|
115
|
-
table { width: 100%; border-collapse: collapse; table-layout: fixed; }
|
|
116
|
-
th, td { border-bottom: 1px solid rgba(255,255,255,.12); padding: 10px; text-align: left; vertical-align: top; overflow-wrap: anywhere; }
|
|
117
|
-
th { color: #cbd5e1; font-weight: 600; }
|
|
118
|
-
tbody tr:nth-child(even) { background: rgba(255,255,255,.03); }
|
|
119
|
-
tbody tr:hover { background: rgba(96,165,250,.08); }
|
|
120
|
-
code { color: #bfdbfe; word-break: break-all; }
|
|
121
|
-
.ins { color: #86efac; font-weight: 600; }
|
|
122
|
-
.del { color: #fca5a5; font-weight: 600; }
|
|
123
|
-
.badge { display: inline-block; padding: 4px 8px; border-radius: 999px; font-weight: 700; font-size: 12px; }
|
|
124
|
-
.badge.ok { background: rgba(34,197,94,.16); color: #86efac; }
|
|
125
|
-
.badge.warn { background: rgba(245,158,11,.18); color: #fcd34d; }
|
|
126
|
-
.badge.bad { background: rgba(239,68,68,.18); color: #fca5a5; }
|
|
127
|
-
.badge.info { background: rgba(59,130,246,.18); color: #93c5fd; }
|
|
128
|
-
.grid { display: grid; grid-template-columns: repeat(auto-fit,minmax(200px,1fr)); gap: 16px; }
|
|
129
|
-
.kpi { font-size: 18px; font-weight: 700; margin-top: 4px; word-break: break-all; }
|
|
130
|
-
.hero { border-color: rgba(96,165,250,.35); background: linear-gradient(135deg, rgba(59,130,246,.12), rgba(255,255,255,.04)); }
|
|
131
|
-
ul { margin-top: 8px; }
|
|
132
|
-
.bar-track { background: rgba(255,255,255,.08); border-radius: 999px; height: 8px; overflow: hidden; margin: 4px 0; }
|
|
133
|
-
.bar-fill { height: 100%; border-radius: 999px; }
|
|
134
|
-
.bar-fill.ok { background: #22c55e; }
|
|
135
|
-
.bar-fill.warn { background: #f59e0b; }
|
|
136
|
-
.bar-fill.bad { background: #ef4444; }
|
|
137
|
-
.bar-fill.info { background: #3b82f6; }
|
|
138
|
-
.rate { display: flex; flex-direction: column; gap: 2px; min-width: 160px; }
|
|
139
|
-
.dur-row { display: grid; grid-template-columns: 150px 1fr 50px; align-items: center; gap: 10px; padding: 6px 0; }
|
|
140
|
-
.dur-label { color: #93c5fd; font-size: 13px; }
|
|
141
|
-
.dur-track { background: rgba(255,255,255,.06); border-radius: 4px; height: 18px; overflow: hidden; }
|
|
142
|
-
.dur-bar { height: 100%; background: linear-gradient(90deg, #3b82f6, #60a5fa); border-radius: 4px; }
|
|
143
|
-
.dur-min { color: #94a3b8; font-size: 13px; text-align: right; }
|
|
144
|
-
@media (max-width: 640px) { main { padding: 16px; } .grid { grid-template-columns: 1fr; } .dur-row { grid-template-columns: 100px 1fr 40px; } }
|
|
145
|
-
</style>
|
|
146
|
-
</head>
|
|
147
|
-
<body><main>
|
|
148
|
-
<div class="card hero">
|
|
149
|
-
<h1>变更最终报告:${esc(data.changeName || '')}</h1>
|
|
150
|
-
<p class="muted">${esc(data.businessGoal || '未记录业务目标')}</p>
|
|
151
|
-
</div>
|
|
152
|
-
<div class="grid">
|
|
153
|
-
<div class="card"><div class="muted">🎯 最终状态</div><div class="kpi">${badge(data.finalStatus || data.overallStatus || 'N/A')}</div></div>
|
|
154
|
-
<div class="card"><div class="muted">🔒 Final Commit</div><div class="kpi"><code>${esc(data.finalCommit || 'N/A')}</code></div><div class="muted small">${esc(data.finalCommitBranch || '')}</div></div>
|
|
155
|
-
<div class="card"><div class="muted">📊 代码变更</div><div class="kpi">${esc(diffStat.filesChanged ?? 'N/A')} 文件</div><div class="muted small"><span class="ins">+${esc(diffStat.insertions ?? 0)}</span> <span class="del">-${esc(diffStat.deletions ?? 0)}</span></div></div>
|
|
156
|
-
<div class="card"><div class="muted">⏱️ 总耗时</div><div class="kpi">${esc(totalLabel)}</div><div class="muted small">${esc(durStages.length)} 个阶段</div></div>
|
|
157
|
-
<div class="card"><div class="muted">📝 Review</div><div class="kpi">${esc(review.status || 'N/A')}</div><div class="muted small">RED ${esc(review.red ?? 0)}(修${esc(review.redFixed ?? 0)}/确${esc(review.redConfirmed ?? 0)}) · YEL ${esc(review.yellow ?? 0)}(修${esc(review.yellowFixed ?? 0)}/留${esc(review.yellowDeferred ?? 0)})</div></div>
|
|
158
|
-
<div class="card"><div class="muted">📦 Archive</div><div class="kpi">${esc(manifest.totalArchiveFiles ?? 'N/A')} 文件</div><div class="muted small">${esc(manifest.checksumStatus || 'N/A')}</div></div>
|
|
159
|
-
</div>
|
|
160
|
-
<div class="card"><h2>📋 阶段状态</h2><table><tr><th>阶段</th><th>状态</th></tr>${stageRows}</table></div>
|
|
161
|
-
${durCard}
|
|
162
|
-
<div class="card"><h2>🛡️ 验证明细</h2><table><tr><th>类型</th><th>结果</th></tr>${verificationRows}</table></div>
|
|
163
|
-
<div class="card"><h2>📁 变更文件</h2><table><tr><th>文件</th><th>说明</th><th>+</th><th>-</th></tr>${changedRows}</table></div>
|
|
164
|
-
${artifactCard}
|
|
165
|
-
${uncommittedCard}
|
|
166
|
-
<div class="card"><h2>👨🔧 给后续维护者</h2><ul>${notes}</ul></div>
|
|
167
|
-
<div class="card"><h2>⚠️ 已知风险 / 人工确认项</h2><ul>${risks}</ul><h3>人工后续动作</h3><ul>${manual}</ul></div>
|
|
77
|
+
const html = `<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
|
78
|
+
<title>Harness 最终报告 · ${esc(data.changeName)}</title><style>
|
|
79
|
+
:root{color-scheme:light dark;--bg:#f3f6fa;--card:#fff;--soft:#f7f9fc;--text:#182132;--muted:#667085;--line:#dce3ed;--blue:#2563eb;--good:#087443;--warn:#9b5a00;--bad:#b42318;--shadow:0 8px 24px rgba(18,35,70,.07)}
|
|
80
|
+
@media(prefers-color-scheme:dark){:root{--bg:#0b1018;--card:#121925;--soft:#182130;--text:#eef3fb;--muted:#9aa8bc;--line:#29364a;--blue:#7aa9ff;--good:#55d99d;--warn:#f0bc63;--bad:#ff8b83;--shadow:0 12px 30px rgba(0,0,0,.25)}}
|
|
81
|
+
*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--text);font:14px/1.5 Inter,"Segoe UI","Microsoft YaHei",sans-serif}main{width:min(1140px,calc(100% - 28px));margin:22px auto 44px}.hero,.card,.metric,details{background:var(--card);border:1px solid var(--line);border-radius:14px;box-shadow:var(--shadow)}.hero{padding:23px 25px;border-top:3px solid var(--blue)}.eyebrow{color:var(--blue);font-weight:750;letter-spacing:.08em}h1{font-size:27px;line-height:1.2;margin:5px 0 7px;overflow-wrap:anywhere}.goal,.empty,small{color:var(--muted)}.status{display:flex;align-items:center;gap:10px;margin-top:16px}.metrics{display:grid;grid-template-columns:repeat(4,1fr);gap:11px;margin:11px 0}.metric{padding:14px 15px}.metric strong{display:block;font-size:19px;margin:4px 0}.grid{display:grid;grid-template-columns:1.12fr .88fr;gap:11px}.card{padding:17px;margin-bottom:11px}h2{font-size:16px;margin:0 0 10px}.row{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:9px 0;border-bottom:1px solid var(--line)}.row:last-child{border:0}.row small{display:block;margin-top:2px}.pill{display:inline-flex;border-radius:999px;padding:3px 9px;font-size:11px;font-weight:750;white-space:nowrap}.pill.success{color:var(--good);background:color-mix(in srgb,var(--good) 13%,transparent)}.pill.warning{color:var(--warn);background:color-mix(in srgb,var(--warn) 14%,transparent)}.pill.danger{color:var(--bad);background:color-mix(in srgb,var(--bad) 13%,transparent)}.pill.neutral{color:var(--blue);background:color-mix(in srgb,var(--blue) 12%,transparent)}.duration{margin:11px 0}.duration>div{display:flex;justify-content:space-between;color:var(--muted);font-size:12px}.duration>div span:first-child{color:var(--text);font-weight:650}.duration i{display:block;height:7px;background:var(--soft);border-radius:99px;overflow:hidden;margin-top:6px}.duration b{display:block;height:100%;background:linear-gradient(90deg,var(--blue),#7c70ff)}.risk{display:grid;grid-template-columns:1fr 1fr;gap:10px}.risk>div{background:var(--soft);border-radius:10px;padding:11px}.risk h3{font-size:13px;margin:0 0 5px}.risk ul{padding-left:18px;margin:0;color:var(--muted)}details{margin:9px 0}summary{cursor:pointer;padding:13px 15px;font-weight:650}details>div{padding:0 15px 15px;overflow:auto}table{width:100%;border-collapse:collapse}th,td{text-align:left;padding:8px 9px;border-bottom:1px solid var(--line);vertical-align:top}th{color:var(--muted);font-size:11px;letter-spacing:.04em}code{font-family:"Cascadia Code",Consolas,monospace;color:var(--blue);overflow-wrap:anywhere}.plus{color:var(--good)}.minus{color:var(--bad)}dl{display:grid;grid-template-columns:145px 1fr;gap:7px 11px}dt{color:var(--muted)}dd{margin:0;overflow-wrap:anywhere}
|
|
82
|
+
@media(max-width:800px){.metrics{grid-template-columns:1fr 1fr}.grid,.risk{grid-template-columns:1fr}}@media(max-width:480px){main{width:calc(100% - 18px);margin-top:9px}.metrics{grid-template-columns:1fr}.hero{padding:19px}h1{font-size:23px}dl{grid-template-columns:1fr}}
|
|
83
|
+
</style></head><body><main>
|
|
84
|
+
<section class="hero"><div class="eyebrow">HARNESS EXECUTION REPORT</div><h1>${esc(data.changeName || "未命名变更")}</h1><p class="goal">${esc(data.businessGoal || "未记录业务目标")}</p><div class="status">${pill(data.finalStatus)}<span>基于事件、验证账本与 Git 证据生成</span></div></section>
|
|
85
|
+
<section class="metrics"><article class="metric"><small>任务提交</small><strong><code title="${esc(data.finalCommit)}">${esc(shortHash(data.finalCommit))}</code></strong><small>${esc(data.finalCommitBranch || "未记录分支")}</small></article><article class="metric"><small>代码范围</small><strong>${number(diff.filesChanged)} 个文件</strong><small><span class="plus">+${number(diff.insertions)}</span> · <span class="minus">-${number(diff.deletions)}</span></small></article><article class="metric"><small>总耗时</small><strong>${esc(durations.totalLabel || duration(durations.totalMinutes))}</strong><small>${durationStages.length} 个阶段</small></article><article class="metric"><small>归档完整性</small><strong>${esc(record(data.archiveManifest).checksumStatus || "UNKNOWN")}</strong><small>${number(record(data.archiveManifest).totalArchiveFiles)} 个归档文件</small></article></section>
|
|
86
|
+
<section class="grid"><div><article class="card"><h2>验证结论</h2>${verificationHtml}</article><article class="card"><h2>阶段耗时</h2>${durationHtml}</article></div><div><article class="card"><h2>阶段状态</h2>${stageHtml}</article><article class="card"><h2>风险与后续</h2><div class="risk"><div><h3>已知风险</h3><ul>${riskHtml}</ul></div><div><h3>人工动作</h3><ul>${actionHtml}</ul></div></div></article></div></section>
|
|
87
|
+
<details><summary>变更文件(${files.length})</summary><div><table><thead><tr><th>文件</th><th>新增</th><th>删除</th></tr></thead><tbody>${fileRows}</tbody></table></div></details>
|
|
88
|
+
<details><summary>执行时间线与工具交接(${timeline.length})</summary><div><table><thead><tr><th>阶段</th><th>尝试</th><th>状态</th><th>来源 / 摘要</th></tr></thead><tbody>${timelineRows}</tbody></table></div></details>
|
|
89
|
+
<details><summary>命令证据(${commands.length})</summary><div><table><thead><tr><th>阶段</th><th>命令</th><th>结果</th></tr></thead><tbody>${commandRows}</tbody></table></div></details>
|
|
90
|
+
<details><summary>技术元数据</summary><div><dl><dt>完整提交</dt><dd><code>${esc(data.finalCommit || "N/A")}</code></dd><dt>基线提交</dt><dd><code>${esc(data.baseCommit || "N/A")}</code></dd><dt>Git 范围</dt><dd><code>${esc(diff.range || "N/A")}</code></dd><dt>报告数据版本</dt><dd>${esc(data.schemaVersion || "N/A")}</dd><dt>事实来源</dt><dd>${esc(list(record(data.reportPipeline).sources).join(" · ") || "N/A")}</dd></dl></div></details>
|
|
168
91
|
</main></body></html>`;
|
|
169
92
|
|
|
170
93
|
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
171
|
-
fs.writeFileSync(outPath, html,
|
|
94
|
+
fs.writeFileSync(outPath, html, "utf8");
|
|
@@ -3,7 +3,7 @@ name: harness-codebase-map
|
|
|
3
3
|
description: 分析现有代码库并生成 .harness/codebase/map/
|
|
4
4
|
下的结构化代码库地图。用于陌生项目理解、重大改造前梳理、harness-sync 提示 map 过期后的人工刷新。
|
|
5
5
|
---
|
|
6
|
-
<!-- generated by harness_deploy.py; core=
|
|
6
|
+
<!-- generated by harness_deploy.py; core=7e031c982caba51d; overlay=none; agent=codex; do not edit -->
|
|
7
7
|
# harness-codebase-map — 代码库地图
|
|
8
8
|
|
|
9
9
|
## Purpose
|
|
@@ -85,7 +85,7 @@ description: 分析现有代码库并生成 .harness/codebase/map/
|
|
|
85
85
|
|
|
86
86
|
## 执行日志
|
|
87
87
|
|
|
88
|
-
`events.ndjson` 为唯一事实源(schema_version
|
|
88
|
+
`events.ndjson` 为唯一事实源(schema_version 3,兼容读取 v1/v2;`note` 承载人类可读摘要);`logs/execution-log.md` 由 `harness_events.py` 渲染,**禁止用 Write/Edit 直接维护**。直接修改的内容会在 `phase.end` 或 finalize 时被完整重建覆盖,属于数据丢失;需要保留的详情必须进入事件 `note`。结构 → [[../protocols/report-pipeline-protocol.md|report-pipeline-protocol]]
|
|
89
89
|
|
|
90
90
|
```powershell
|
|
91
91
|
python <skills-root>/scripts/harness_events.py append --change-dir ".harness/changes/<change-name>" --phase <phase> --type phase.start --note "<触发指令>"
|
|
@@ -94,3 +94,5 @@ python <skills-root>/scripts/harness_events.py append --change-dir ".harness/cha
|
|
|
94
94
|
> **脚本接线**:`harness_events.py append`;`harness_archive.py finalize`;`harness_preflight.py check`;`harness_ledger.py can-reuse`;`harness_service.py ensure/stop`(须 `--files`/`serviceStart.inputFiles`)。JSON 输出按 D13 护栏解读。
|
|
95
95
|
|
|
96
96
|
> **Task 4 §6.1 写入契约**:普通 `append` = 加锁 -> 追加一行 -> fsync -> 解锁,**不 load 历史、不渲染**(O(1),跨进程锁 `events.ndjson.lock`,UUID 用完整 `uuid4().hex` 无需去重扫描)。仅 `--type phase.end` append 在追加成功后渲染一次 `execution-log.md`;显式 `harness_events.py render` 随时从完整 events 重建;`harness_archive.py finalize` 在 collect 前强制 render 一次。高频 command append 期间 log 可能滞后,phase 边界保持最新。
|
|
97
|
+
|
|
98
|
+
每次阶段结束必须写 `--status OK|WARN|FAIL|BLOCKED`;重试同一阶段时增加 `--attempt <n>`,不得覆盖或伪装成一次执行。跨工具继续执行时写 `--executor-tool <codex|claude-code|codebuddy|cursor>`,并在接棒事件写 `--handoff-from-tool` / `--handoff-reason`;也可由 `HUNTER_HARNESS_TOOL/AGENT/MODEL/RUN_ID` 环境变量统一注入。
|