@hunter-harness/workflow-harness 0.2.8 → 0.2.9
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/contracts/workflow-policy.json +3 -3
- package/harness/bundles/general/claude-code/harness-archive/SKILL.md +3 -3
- package/harness/bundles/general/claude-code/harness-archive/reference.md +18 -10
- package/harness/bundles/general/claude-code/harness-archive/templates/render-summary.mjs +5 -1
- package/harness/bundles/general/claude-code/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-plan/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-plan/checklist.md +6 -0
- package/harness/bundles/general/claude-code/harness-review/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-run/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-run/checklist.md +5 -0
- package/harness/bundles/general/claude-code/harness-run/reference.md +31 -1
- package/harness/bundles/general/claude-code/harness-submit/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-test/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-test/checklist.md +9 -0
- package/harness/bundles/general/claude-code/harness-test/pitfalls-java.md +14 -0
- package/harness/bundles/general/claude-code/harness-test/reference.md +36 -0
- package/harness/bundles/general/claude-code/protocols/archive-report-protocol.md +3 -1
- package/harness/bundles/general/claude-code/protocols/ledger-protocol.md +2 -0
- package/harness/bundles/general/claude-code/protocols/powershell-protocol.md +16 -0
- package/harness/bundles/general/claude-code/protocols/report-pipeline-protocol.md +17 -0
- package/harness/bundles/general/claude-code/scripts/harness_archive.py +568 -58
- package/harness/bundles/general/claude-code/scripts/harness_events.py +70 -19
- package/harness/bundles/general/claude-code/scripts/harness_gate.py +178 -25
- package/harness/bundles/general/claude-code/scripts/harness_ledger.py +17 -0
- package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/general/codebuddy/contracts/workflow-policy.json +3 -3
- package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +3 -3
- package/harness/bundles/general/codebuddy/harness-archive/reference.md +18 -10
- package/harness/bundles/general/codebuddy/harness-archive/templates/render-summary.mjs +5 -1
- package/harness/bundles/general/codebuddy/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-plan/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-plan/checklist.md +6 -0
- package/harness/bundles/general/codebuddy/harness-review/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-run/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-run/checklist.md +5 -0
- package/harness/bundles/general/codebuddy/harness-run/reference.md +31 -1
- package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-test/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-test/checklist.md +9 -0
- package/harness/bundles/general/codebuddy/harness-test/pitfalls-java.md +14 -0
- package/harness/bundles/general/codebuddy/harness-test/reference.md +36 -0
- package/harness/bundles/general/codebuddy/protocols/archive-report-protocol.md +3 -1
- package/harness/bundles/general/codebuddy/protocols/ledger-protocol.md +2 -0
- package/harness/bundles/general/codebuddy/protocols/powershell-protocol.md +16 -0
- package/harness/bundles/general/codebuddy/protocols/report-pipeline-protocol.md +17 -0
- package/harness/bundles/general/codebuddy/scripts/harness_archive.py +568 -58
- package/harness/bundles/general/codebuddy/scripts/harness_events.py +70 -19
- package/harness/bundles/general/codebuddy/scripts/harness_gate.py +178 -25
- package/harness/bundles/general/codebuddy/scripts/harness_ledger.py +17 -0
- package/harness/bundles/general/codex/.harness-build.json +1 -1
- package/harness/bundles/general/codex/contracts/workflow-policy.json +3 -3
- package/harness/bundles/general/codex/harness-archive/SKILL.md +3 -3
- package/harness/bundles/general/codex/harness-archive/reference.md +18 -10
- package/harness/bundles/general/codex/harness-archive/templates/render-summary.mjs +5 -1
- package/harness/bundles/general/codex/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-plan/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-plan/checklist.md +6 -0
- package/harness/bundles/general/codex/harness-review/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-run/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-run/checklist.md +5 -0
- package/harness/bundles/general/codex/harness-run/reference.md +31 -1
- package/harness/bundles/general/codex/harness-submit/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-test/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-test/checklist.md +9 -0
- package/harness/bundles/general/codex/harness-test/pitfalls-java.md +14 -0
- package/harness/bundles/general/codex/harness-test/reference.md +36 -0
- package/harness/bundles/general/codex/protocols/archive-report-protocol.md +3 -1
- package/harness/bundles/general/codex/protocols/ledger-protocol.md +2 -0
- package/harness/bundles/general/codex/protocols/powershell-protocol.md +16 -0
- package/harness/bundles/general/codex/protocols/report-pipeline-protocol.md +17 -0
- package/harness/bundles/general/codex/scripts/harness_archive.py +568 -58
- package/harness/bundles/general/codex/scripts/harness_events.py +70 -19
- package/harness/bundles/general/codex/scripts/harness_gate.py +178 -25
- package/harness/bundles/general/codex/scripts/harness_ledger.py +17 -0
- package/harness/bundles/general/cursor/.harness-build.json +1 -1
- package/harness/bundles/general/cursor/contracts/workflow-policy.json +3 -3
- package/harness/bundles/general/cursor/harness-archive/SKILL.md +3 -3
- package/harness/bundles/general/cursor/harness-archive/reference.md +18 -10
- package/harness/bundles/general/cursor/harness-archive/templates/render-summary.mjs +5 -1
- package/harness/bundles/general/cursor/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-plan/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-plan/checklist.md +6 -0
- package/harness/bundles/general/cursor/harness-review/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-run/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-run/checklist.md +5 -0
- package/harness/bundles/general/cursor/harness-run/reference.md +31 -1
- package/harness/bundles/general/cursor/harness-submit/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-test/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-test/checklist.md +9 -0
- package/harness/bundles/general/cursor/harness-test/pitfalls-java.md +14 -0
- package/harness/bundles/general/cursor/harness-test/reference.md +36 -0
- package/harness/bundles/general/cursor/protocols/archive-report-protocol.md +3 -1
- package/harness/bundles/general/cursor/protocols/ledger-protocol.md +2 -0
- package/harness/bundles/general/cursor/protocols/powershell-protocol.md +16 -0
- package/harness/bundles/general/cursor/protocols/report-pipeline-protocol.md +17 -0
- package/harness/bundles/general/cursor/scripts/harness_archive.py +568 -58
- package/harness/bundles/general/cursor/scripts/harness_events.py +70 -19
- package/harness/bundles/general/cursor/scripts/harness_gate.py +178 -25
- package/harness/bundles/general/cursor/scripts/harness_ledger.py +17 -0
- package/harness/bundles/java/claude-code/.harness-build.json +1 -1
- package/harness/bundles/java/claude-code/contracts/workflow-policy.json +3 -3
- package/harness/bundles/java/claude-code/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-archive/SKILL.md +3 -3
- package/harness/bundles/java/claude-code/harness-archive/reference.md +18 -10
- package/harness/bundles/java/claude-code/harness-archive/templates/render-summary.mjs +5 -1
- package/harness/bundles/java/claude-code/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-package/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-plan/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-plan/checklist.md +6 -0
- package/harness/bundles/java/claude-code/harness-review/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-run/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-submit/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-test/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/protocols/archive-report-protocol.md +3 -1
- package/harness/bundles/java/claude-code/protocols/ledger-protocol.md +2 -0
- package/harness/bundles/java/claude-code/protocols/powershell-protocol.md +16 -0
- package/harness/bundles/java/claude-code/protocols/report-pipeline-protocol.md +17 -0
- package/harness/bundles/java/claude-code/scripts/harness_archive.py +568 -58
- package/harness/bundles/java/claude-code/scripts/harness_events.py +70 -19
- package/harness/bundles/java/claude-code/scripts/harness_gate.py +178 -25
- package/harness/bundles/java/claude-code/scripts/harness_ledger.py +17 -0
- package/harness/bundles/java/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/java/codebuddy/contracts/workflow-policy.json +3 -3
- package/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-archive/SKILL.md +3 -3
- package/harness/bundles/java/codebuddy/harness-archive/reference.md +18 -10
- package/harness/bundles/java/codebuddy/harness-archive/templates/render-summary.mjs +5 -1
- package/harness/bundles/java/codebuddy/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-package/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-plan/checklist.md +6 -0
- package/harness/bundles/java/codebuddy/harness-review/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-run/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-test/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/protocols/archive-report-protocol.md +3 -1
- package/harness/bundles/java/codebuddy/protocols/ledger-protocol.md +2 -0
- package/harness/bundles/java/codebuddy/protocols/powershell-protocol.md +16 -0
- package/harness/bundles/java/codebuddy/protocols/report-pipeline-protocol.md +17 -0
- package/harness/bundles/java/codebuddy/scripts/harness_archive.py +568 -58
- package/harness/bundles/java/codebuddy/scripts/harness_events.py +70 -19
- package/harness/bundles/java/codebuddy/scripts/harness_gate.py +178 -25
- package/harness/bundles/java/codebuddy/scripts/harness_ledger.py +17 -0
- package/harness/bundles/java/codex/.harness-build.json +1 -1
- package/harness/bundles/java/codex/contracts/workflow-policy.json +3 -3
- package/harness/bundles/java/codex/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-archive/SKILL.md +3 -3
- package/harness/bundles/java/codex/harness-archive/reference.md +18 -10
- package/harness/bundles/java/codex/harness-archive/templates/render-summary.mjs +5 -1
- package/harness/bundles/java/codex/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-package/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-plan/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-plan/checklist.md +6 -0
- package/harness/bundles/java/codex/harness-review/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-run/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-submit/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-test/SKILL.md +1 -1
- package/harness/bundles/java/codex/protocols/archive-report-protocol.md +3 -1
- package/harness/bundles/java/codex/protocols/ledger-protocol.md +2 -0
- package/harness/bundles/java/codex/protocols/powershell-protocol.md +16 -0
- package/harness/bundles/java/codex/protocols/report-pipeline-protocol.md +17 -0
- package/harness/bundles/java/codex/scripts/harness_archive.py +568 -58
- package/harness/bundles/java/codex/scripts/harness_events.py +70 -19
- package/harness/bundles/java/codex/scripts/harness_gate.py +178 -25
- package/harness/bundles/java/codex/scripts/harness_ledger.py +17 -0
- package/harness/bundles/java/cursor/.harness-build.json +1 -1
- package/harness/bundles/java/cursor/contracts/workflow-policy.json +3 -3
- package/harness/bundles/java/cursor/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-archive/SKILL.md +3 -3
- package/harness/bundles/java/cursor/harness-archive/reference.md +18 -10
- package/harness/bundles/java/cursor/harness-archive/templates/render-summary.mjs +5 -1
- package/harness/bundles/java/cursor/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-package/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-plan/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-plan/checklist.md +6 -0
- package/harness/bundles/java/cursor/harness-review/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-run/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-submit/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-test/SKILL.md +1 -1
- package/harness/bundles/java/cursor/protocols/archive-report-protocol.md +3 -1
- package/harness/bundles/java/cursor/protocols/ledger-protocol.md +2 -0
- package/harness/bundles/java/cursor/protocols/powershell-protocol.md +16 -0
- package/harness/bundles/java/cursor/protocols/report-pipeline-protocol.md +17 -0
- package/harness/bundles/java/cursor/scripts/harness_archive.py +568 -58
- package/harness/bundles/java/cursor/scripts/harness_events.py +70 -19
- package/harness/bundles/java/cursor/scripts/harness_gate.py +178 -25
- package/harness/bundles/java/cursor/scripts/harness_ledger.py +17 -0
- package/harness/manifests/general/claude-code.json +32 -28
- package/harness/manifests/general/codebuddy.json +32 -28
- package/harness/manifests/general/codex.json +32 -28
- package/harness/manifests/general/cursor.json +32 -28
- 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 +2 -2
- package/package.json +1 -1
|
@@ -43,6 +43,7 @@ EVENT_TYPES = frozenset(
|
|
|
43
43
|
)
|
|
44
44
|
|
|
45
45
|
SCHEMA_VERSION = 3
|
|
46
|
+
_NOTE_FALLBACK_MAXLEN = 60
|
|
46
47
|
HEADER_LINE = (
|
|
47
48
|
"本文件由 harness_events.py 自动渲染,请勿手工编辑;事实源为 events.ndjson"
|
|
48
49
|
)
|
|
@@ -316,6 +317,8 @@ def build_event(args: argparse.Namespace, existing: list[dict[str, Any]]) -> dic
|
|
|
316
317
|
def status_symbol(status: Any, reason: Any = None) -> str:
|
|
317
318
|
text = str(status or "").strip().lower()
|
|
318
319
|
reason_text = str(reason or "").strip()
|
|
320
|
+
if not text:
|
|
321
|
+
return "—"
|
|
319
322
|
ok_set = {"ok", "passed", "pass", "success", "green", "✅", "✅ok"}
|
|
320
323
|
warn_set = {"warn", "warning", "yellow", "skipped", "skip", "🟡", "🟡warn"}
|
|
321
324
|
fail_set = {"fail", "failed", "error", "red", "blocked", "❌", "❌fail"}
|
|
@@ -327,7 +330,7 @@ def status_symbol(status: Any, reason: Any = None) -> str:
|
|
|
327
330
|
return f"❌FAIL({reason_text})" if reason_text else "❌FAIL"
|
|
328
331
|
if reason_text:
|
|
329
332
|
return f"{status}({reason_text})"
|
|
330
|
-
return str(status
|
|
333
|
+
return str(status)
|
|
331
334
|
|
|
332
335
|
|
|
333
336
|
def severity_symbol(severity: Any, message: Any = None) -> str:
|
|
@@ -339,7 +342,10 @@ def severity_symbol(severity: Any, message: Any = None) -> str:
|
|
|
339
342
|
return f"🟡WARN({msg})" if msg else "🟡WARN"
|
|
340
343
|
if text in {"info", "ok", "note"}:
|
|
341
344
|
return f"✅OK({msg})" if msg else "✅OK"
|
|
342
|
-
|
|
345
|
+
# Empty/unknown severity: never emit literal "issue"/"None".
|
|
346
|
+
if not text:
|
|
347
|
+
return ""
|
|
348
|
+
return f"{severity}: {msg}" if msg else str(severity)
|
|
343
349
|
|
|
344
350
|
|
|
345
351
|
def format_duration(ms: int | None) -> str:
|
|
@@ -440,35 +446,54 @@ def phase_duration_ms(phase_events: list[dict[str, Any]]) -> int | None:
|
|
|
440
446
|
def render_command_block(commands: list[dict[str, Any]]) -> list[str]:
|
|
441
447
|
if not commands:
|
|
442
448
|
return []
|
|
449
|
+
|
|
450
|
+
def command_display(event: dict[str, Any]) -> str:
|
|
451
|
+
cmd = str(event.get("command") or "").strip()
|
|
452
|
+
if cmd:
|
|
453
|
+
return cmd
|
|
454
|
+
return str(event.get("note") or "").strip()
|
|
455
|
+
|
|
443
456
|
if len(commands) == 1:
|
|
444
457
|
event = commands[0]
|
|
445
|
-
|
|
458
|
+
display = command_display(event)
|
|
459
|
+
if not display:
|
|
460
|
+
return []
|
|
446
461
|
exit_code = event.get("exit_code")
|
|
447
462
|
exit_text = "?" if exit_code is None else str(exit_code)
|
|
448
463
|
duration = format_duration(
|
|
449
464
|
int(event["duration_ms"]) if isinstance(event.get("duration_ms"), int) else None
|
|
450
465
|
)
|
|
451
466
|
note = str(event.get("note") or "").strip()
|
|
452
|
-
parts = [f"- command: `{
|
|
453
|
-
|
|
467
|
+
parts = [f"- command: `{display}`", f"exit={exit_text}", f"duration={duration}"]
|
|
468
|
+
# Avoid duplicating note when it was already used as the display text.
|
|
469
|
+
if note and note != display:
|
|
454
470
|
parts.append(f"note={note}")
|
|
455
471
|
return [" · ".join(parts)]
|
|
456
472
|
|
|
457
|
-
|
|
458
|
-
"",
|
|
459
|
-
"| 命令 | exit | duration | note |",
|
|
460
|
-
"| --- | ---: | ---: | --- |",
|
|
461
|
-
]
|
|
473
|
+
rows: list[str] = []
|
|
462
474
|
for event in commands:
|
|
463
|
-
|
|
475
|
+
display = command_display(event)
|
|
476
|
+
if not display:
|
|
477
|
+
continue
|
|
478
|
+
cmd = display.replace("|", "\\|")
|
|
464
479
|
exit_code = event.get("exit_code")
|
|
465
480
|
exit_text = "?" if exit_code is None else str(exit_code)
|
|
466
481
|
duration = format_duration(
|
|
467
482
|
int(event["duration_ms"]) if isinstance(event.get("duration_ms"), int) else None
|
|
468
483
|
)
|
|
469
484
|
note = str(event.get("note") or "").replace("|", "\\|")
|
|
470
|
-
|
|
471
|
-
|
|
485
|
+
# If note was promoted to the command cell, leave the note column empty.
|
|
486
|
+
note_cell = "" if note == display else note
|
|
487
|
+
rows.append(f"| `{cmd}` | {exit_text} | {duration} | {note_cell} |")
|
|
488
|
+
if not rows:
|
|
489
|
+
return []
|
|
490
|
+
lines = [
|
|
491
|
+
"",
|
|
492
|
+
"| 命令 | exit | duration | note |",
|
|
493
|
+
"| --- | ---: | ---: | --- |",
|
|
494
|
+
*rows,
|
|
495
|
+
"",
|
|
496
|
+
]
|
|
472
497
|
return lines
|
|
473
498
|
|
|
474
499
|
|
|
@@ -491,23 +516,37 @@ def render_event_line(event: dict[str, Any]) -> list[str]:
|
|
|
491
516
|
suffix = f" — {note}" if note else ""
|
|
492
517
|
return [f"- phase.end @ {event.get('timestamp', '')}{suffix}"]
|
|
493
518
|
if etype == "verification":
|
|
494
|
-
|
|
495
|
-
|
|
519
|
+
note = str(event.get("note") or "").strip()
|
|
520
|
+
name = str(event.get("name") or "").strip() or (
|
|
521
|
+
note[:_NOTE_FALLBACK_MAXLEN] if note else ""
|
|
522
|
+
)
|
|
523
|
+
if not name:
|
|
524
|
+
return []
|
|
525
|
+
symbol = status_symbol(event.get("status"), event.get("reason"))
|
|
496
526
|
return [f"- verification: {name} → {symbol}"]
|
|
497
527
|
if etype == "decision":
|
|
498
|
-
decision = event.get("decision") or ""
|
|
499
|
-
reason = event.get("reason") or event.get("note") or ""
|
|
528
|
+
decision = str(event.get("decision") or "").strip()
|
|
529
|
+
reason = str(event.get("reason") or event.get("note") or "").strip()
|
|
530
|
+
if not decision:
|
|
531
|
+
return [f"- decision: {reason}"] if reason else []
|
|
500
532
|
if reason:
|
|
501
533
|
return [f"- decision: {decision} — {reason}"]
|
|
502
534
|
return [f"- decision: {decision}"]
|
|
503
535
|
if etype == "issue":
|
|
504
|
-
|
|
536
|
+
note = str(event.get("note") or "").strip()
|
|
537
|
+
message = str(event.get("message") or "").strip() or note
|
|
538
|
+
symbol = severity_symbol(event.get("severity"), message)
|
|
539
|
+
if not symbol:
|
|
540
|
+
return [f"- issue: {note}"] if note else []
|
|
505
541
|
code = event.get("code")
|
|
506
542
|
prefix = f"[{code}] " if code else ""
|
|
507
543
|
return [f"- issue: {prefix}{symbol}"]
|
|
508
544
|
if etype == "artifact":
|
|
509
|
-
path = event.get("path") or ""
|
|
545
|
+
path = str(event.get("path") or "").strip()
|
|
546
|
+
note = str(event.get("note") or "").strip()
|
|
510
547
|
kind = event.get("kind")
|
|
548
|
+
if not path:
|
|
549
|
+
return [f"- artifact: {note}"] if note else []
|
|
511
550
|
if kind:
|
|
512
551
|
return [f"- artifact: `{path}` ({kind})"]
|
|
513
552
|
return [f"- artifact: `{path}`"]
|
|
@@ -653,6 +692,18 @@ def cmd_append(args: argparse.Namespace) -> int:
|
|
|
653
692
|
# new_event_id 用完整 uuid,无需扫描去重。锁覆盖 atomic_append_line 的
|
|
654
693
|
# open/write/flush/fsync 全过程。
|
|
655
694
|
event = build_event(args, [])
|
|
695
|
+
# Semantic hints (non-fatal): guide agents toward required fields.
|
|
696
|
+
if args.type == "issue" and not args.severity:
|
|
697
|
+
event["severity"] = "info"
|
|
698
|
+
print(
|
|
699
|
+
"warning: issue without --severity, defaulted to info",
|
|
700
|
+
file=sys.stderr,
|
|
701
|
+
)
|
|
702
|
+
if args.type == "verification" and (not args.name or not args.status):
|
|
703
|
+
print(
|
|
704
|
+
"warning: verification missing --name or --status",
|
|
705
|
+
file=sys.stderr,
|
|
706
|
+
)
|
|
656
707
|
line = json.dumps(event, ensure_ascii=False, separators=(",", ":"))
|
|
657
708
|
try:
|
|
658
709
|
with event_file_lock(lock_path):
|
|
@@ -149,7 +149,8 @@ def checkpoint_status(checkpoints: dict[str, Any] | None, checkpoint_id: str) ->
|
|
|
149
149
|
def foundation_gate_blocks(task_number: int | None, change_dir: Path) -> dict[str, Any] | None:
|
|
150
150
|
checkpoints = load_checkpoints(change_dir)
|
|
151
151
|
status = checkpoint_status(checkpoints, "foundation-gate")
|
|
152
|
-
|
|
152
|
+
# missing = checkpoint not enabled for this change (no file / no entry).
|
|
153
|
+
if status in {"approved", "missing"}:
|
|
153
154
|
return None
|
|
154
155
|
if task_number is None:
|
|
155
156
|
return {
|
|
@@ -174,8 +175,25 @@ def foundation_gate_blocks(task_number: int | None, change_dir: Path) -> dict[st
|
|
|
174
175
|
}
|
|
175
176
|
|
|
176
177
|
|
|
177
|
-
def
|
|
178
|
+
def is_degraded_ledger_entry(entry: dict[str, Any]) -> bool:
|
|
179
|
+
"""NOT_RUN + evidence starting with 'DEGRADED: <non-empty reason>'."""
|
|
180
|
+
if entry.get("status") != "NOT_RUN":
|
|
181
|
+
return False
|
|
182
|
+
evidence = str(entry.get("evidence") or "").strip()
|
|
183
|
+
if not evidence.startswith("DEGRADED:"):
|
|
184
|
+
return False
|
|
185
|
+
reason = evidence.split(":", 1)[1].strip()
|
|
186
|
+
return bool(reason)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def validate_ledger_entry_v2(entry: dict[str, Any], verification: str) -> tuple[list[str], bool]:
|
|
190
|
+
"""Internal helper for ledger close validation (not a public API).
|
|
191
|
+
|
|
192
|
+
Returns ``(missing_fields, degraded_ok)`` where ``degraded_ok`` is True only when
|
|
193
|
+
the entry is a valid DEGRADED NOT_RUN record with no other missing fields.
|
|
194
|
+
"""
|
|
178
195
|
missing: list[str] = []
|
|
196
|
+
degraded = is_degraded_ledger_entry(entry)
|
|
179
197
|
for field in LEDGER_V2_REQUIRED_ENTRY_FIELDS:
|
|
180
198
|
value = entry.get(field)
|
|
181
199
|
if field == "inputsFiles":
|
|
@@ -184,7 +202,7 @@ def validate_ledger_entry_v2(entry: dict[str, Any], verification: str) -> list[s
|
|
|
184
202
|
elif verification == "unitTestFull" and not value:
|
|
185
203
|
missing.append("inputsFiles(non-empty)")
|
|
186
204
|
elif field == "status":
|
|
187
|
-
if value != "OK":
|
|
205
|
+
if value != "OK" and not degraded:
|
|
188
206
|
missing.append("status=OK")
|
|
189
207
|
elif not (isinstance(value, str) and value.strip()):
|
|
190
208
|
missing.append(field)
|
|
@@ -192,7 +210,7 @@ def validate_ledger_entry_v2(entry: dict[str, Any], verification: str) -> list[s
|
|
|
192
210
|
missing.append("coverage(valid)")
|
|
193
211
|
elif field == "algorithmVersion" and str(value).strip() != hl.LEDGER_VERSION:
|
|
194
212
|
missing.append("algorithmVersion(harness-ledger-2)")
|
|
195
|
-
return missing
|
|
213
|
+
return missing, degraded and not missing
|
|
196
214
|
|
|
197
215
|
|
|
198
216
|
def validate_ledger_for_phase_close(
|
|
@@ -225,6 +243,7 @@ def validate_ledger_for_phase_close(
|
|
|
225
243
|
}
|
|
226
244
|
|
|
227
245
|
problems: list[dict[str, Any]] = []
|
|
246
|
+
degraded: list[str] = []
|
|
228
247
|
for verification in required:
|
|
229
248
|
entry = validations.get(verification)
|
|
230
249
|
if not isinstance(entry, dict):
|
|
@@ -236,7 +255,7 @@ def validate_ledger_for_phase_close(
|
|
|
236
255
|
}
|
|
237
256
|
)
|
|
238
257
|
continue
|
|
239
|
-
missing = validate_ledger_entry_v2(entry, verification)
|
|
258
|
+
missing, is_degraded = validate_ledger_entry_v2(entry, verification)
|
|
240
259
|
if missing:
|
|
241
260
|
code = (
|
|
242
261
|
"MISSING_V2_FIELDS"
|
|
@@ -258,6 +277,8 @@ def validate_ledger_for_phase_close(
|
|
|
258
277
|
"code": code,
|
|
259
278
|
}
|
|
260
279
|
)
|
|
280
|
+
elif is_degraded:
|
|
281
|
+
degraded.append(verification)
|
|
261
282
|
|
|
262
283
|
if problems:
|
|
263
284
|
return {
|
|
@@ -269,6 +290,15 @@ def validate_ledger_for_phase_close(
|
|
|
269
290
|
"ledgerPath": str(ledger_path) if ledger_path else None,
|
|
270
291
|
"detail": "natural-language override is not permitted",
|
|
271
292
|
}
|
|
293
|
+
if degraded:
|
|
294
|
+
return {
|
|
295
|
+
"ok": True,
|
|
296
|
+
"code": "LEDGER_OK_DEGRADED",
|
|
297
|
+
"phase": phase,
|
|
298
|
+
"validated": required,
|
|
299
|
+
"degraded": degraded,
|
|
300
|
+
"ledgerPath": str(ledger_path) if ledger_path else None,
|
|
301
|
+
}
|
|
272
302
|
return {
|
|
273
303
|
"ok": True,
|
|
274
304
|
"code": "LEDGER_OK",
|
|
@@ -417,7 +447,11 @@ def change_code_root(change_dir: Path) -> Path:
|
|
|
417
447
|
return project
|
|
418
448
|
|
|
419
449
|
|
|
420
|
-
def classify_risk(
|
|
450
|
+
def classify_risk(
|
|
451
|
+
change_dir: Path,
|
|
452
|
+
stage: str,
|
|
453
|
+
workflow: dict[str, Any] | None = None,
|
|
454
|
+
) -> dict[str, Any]:
|
|
421
455
|
tier = "full"
|
|
422
456
|
source = "default-full"
|
|
423
457
|
plan_path = change_dir / "plans"
|
|
@@ -484,7 +518,31 @@ def classify_risk(change_dir: Path, stage: str) -> dict[str, Any]:
|
|
|
484
518
|
tier = observed
|
|
485
519
|
source = f"{source}+post-run"
|
|
486
520
|
main_project = change_dir.parents[2]
|
|
487
|
-
workflow
|
|
521
|
+
if workflow is None:
|
|
522
|
+
workflow = _load_workflow_policy(project=main_project)
|
|
523
|
+
tier_policy = workflow["riskTiers"][tier]
|
|
524
|
+
unique_signals = sorted(set(signals))
|
|
525
|
+
payload = {
|
|
526
|
+
"ok": True,
|
|
527
|
+
"code": "CLASSIFIED",
|
|
528
|
+
"stage": stage,
|
|
529
|
+
"tier": tier,
|
|
530
|
+
"source": source,
|
|
531
|
+
"changeId": change_dir.name,
|
|
532
|
+
"signals": unique_signals,
|
|
533
|
+
"defaultPhases": list(tier_policy["defaultPhases"]),
|
|
534
|
+
"requiredValidations": list(tier_policy["requiredValidations"]),
|
|
535
|
+
"conditionalStages": list(tier_policy["conditionalStages"]),
|
|
536
|
+
"stageDecisions": _stage_decisions_for_tier(workflow, tier, unique_signals),
|
|
537
|
+
}
|
|
538
|
+
if stage == "post-run":
|
|
539
|
+
_write_json(change_dir / "meta" / "risk-classification.json", payload)
|
|
540
|
+
return payload
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
def _stage_decisions_for_tier(
|
|
544
|
+
workflow: dict[str, Any], tier: str, signals: list[str]
|
|
545
|
+
) -> dict[str, dict[str, Any]]:
|
|
488
546
|
tier_policy = workflow["riskTiers"][tier]
|
|
489
547
|
unique_signals = sorted(set(signals))
|
|
490
548
|
stage_decisions: dict[str, dict[str, Any]] = {}
|
|
@@ -501,22 +559,66 @@ def classify_risk(change_dir: Path, stage: str) -> dict[str, Any]:
|
|
|
501
559
|
),
|
|
502
560
|
"matchedSignals": matched,
|
|
503
561
|
}
|
|
504
|
-
|
|
562
|
+
return stage_decisions
|
|
563
|
+
|
|
564
|
+
|
|
565
|
+
def apply_tier_override(
|
|
566
|
+
payload: dict[str, Any],
|
|
567
|
+
workflow: dict[str, Any],
|
|
568
|
+
*,
|
|
569
|
+
tier: str,
|
|
570
|
+
override_by: str,
|
|
571
|
+
) -> dict[str, Any]:
|
|
572
|
+
"""Rebind payload to an explicit tier override (source=override)."""
|
|
573
|
+
tier_policy = workflow["riskTiers"][tier]
|
|
574
|
+
now = hc.now_iso()
|
|
575
|
+
signals = list(payload.get("signals") or [])
|
|
576
|
+
payload = dict(payload)
|
|
577
|
+
payload["tier"] = tier
|
|
578
|
+
payload["source"] = "override"
|
|
579
|
+
payload["defaultPhases"] = list(tier_policy["defaultPhases"])
|
|
580
|
+
payload["requiredValidations"] = list(tier_policy["requiredValidations"])
|
|
581
|
+
payload["conditionalStages"] = list(tier_policy["conditionalStages"])
|
|
582
|
+
payload["stageDecisions"] = _stage_decisions_for_tier(workflow, tier, signals)
|
|
583
|
+
payload["tierOverride"] = {"tier": tier, "by": override_by, "at": now}
|
|
584
|
+
return payload
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
def gate_policy_document(payload: dict[str, Any]) -> dict[str, Any]:
|
|
588
|
+
"""Cross-change contract: meta/gate-policy.json (schemaVersion=1)."""
|
|
589
|
+
return {
|
|
590
|
+
"schemaVersion": 1,
|
|
591
|
+
"tier": payload["tier"],
|
|
592
|
+
"source": payload["source"],
|
|
593
|
+
"defaultPhases": list(payload.get("defaultPhases") or []),
|
|
594
|
+
"requiredValidations": list(payload.get("requiredValidations") or []),
|
|
595
|
+
"classifiedAt": payload.get("classifiedAt") or hc.now_iso(),
|
|
596
|
+
"tierOverride": payload.get("tierOverride"),
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
|
|
600
|
+
def classify_defaults(
|
|
601
|
+
workflow: dict[str, Any],
|
|
602
|
+
*,
|
|
603
|
+
change_id: str,
|
|
604
|
+
stage: str,
|
|
605
|
+
) -> dict[str, Any]:
|
|
606
|
+
tier = "full"
|
|
607
|
+
source = "default-full"
|
|
608
|
+
tier_policy = workflow["riskTiers"][tier]
|
|
609
|
+
return {
|
|
505
610
|
"ok": True,
|
|
506
611
|
"code": "CLASSIFIED",
|
|
507
612
|
"stage": stage,
|
|
508
613
|
"tier": tier,
|
|
509
614
|
"source": source,
|
|
510
|
-
"changeId":
|
|
511
|
-
"signals":
|
|
615
|
+
"changeId": change_id,
|
|
616
|
+
"signals": [],
|
|
512
617
|
"defaultPhases": list(tier_policy["defaultPhases"]),
|
|
513
618
|
"requiredValidations": list(tier_policy["requiredValidations"]),
|
|
514
619
|
"conditionalStages": list(tier_policy["conditionalStages"]),
|
|
515
|
-
"stageDecisions":
|
|
620
|
+
"stageDecisions": _stage_decisions_for_tier(workflow, tier, []),
|
|
516
621
|
}
|
|
517
|
-
if stage == "post-run":
|
|
518
|
-
_write_json(change_dir / "meta" / "risk-classification.json", payload)
|
|
519
|
-
return payload
|
|
520
622
|
|
|
521
623
|
|
|
522
624
|
def lint_skill_tree(skills_root: Path) -> dict[str, Any]:
|
|
@@ -823,6 +925,14 @@ def cmd_close(args: argparse.Namespace) -> int:
|
|
|
823
925
|
extra={k: v for k, v in ledger_result.items() if k not in {"ok", "message", "code"}},
|
|
824
926
|
)
|
|
825
927
|
|
|
928
|
+
close_status = args.status
|
|
929
|
+
close_code = "PHASE_CLOSED"
|
|
930
|
+
if ledger_result.get("code") == "LEDGER_OK_DEGRADED":
|
|
931
|
+
close_code = "CLOSED_DEGRADED"
|
|
932
|
+
# Degraded close: phase.end status must not exceed WARN (OK → WARN).
|
|
933
|
+
if close_status == "OK":
|
|
934
|
+
close_status = "WARN"
|
|
935
|
+
|
|
826
936
|
guard_result = None
|
|
827
937
|
if args.phase in {"run", "test"}:
|
|
828
938
|
project_root = args.project or str(project)
|
|
@@ -839,7 +949,7 @@ def cmd_close(args: argparse.Namespace) -> int:
|
|
|
839
949
|
change_dir,
|
|
840
950
|
phase=args.phase,
|
|
841
951
|
type_="phase.end",
|
|
842
|
-
status=
|
|
952
|
+
status=close_status,
|
|
843
953
|
note=args.note or "",
|
|
844
954
|
run_id=run_id,
|
|
845
955
|
) if not _phase_event_exists(change_dir, args.phase, "phase.end", run_id) else {
|
|
@@ -862,9 +972,9 @@ def cmd_close(args: argparse.Namespace) -> int:
|
|
|
862
972
|
|
|
863
973
|
payload = {
|
|
864
974
|
"ok": True,
|
|
865
|
-
"code":
|
|
975
|
+
"code": close_code,
|
|
866
976
|
"phase": args.phase,
|
|
867
|
-
"status":
|
|
977
|
+
"status": close_status,
|
|
868
978
|
"changeId": resolved["changeId"],
|
|
869
979
|
"ledger": ledger_result,
|
|
870
980
|
"testGuard": guard_result,
|
|
@@ -878,19 +988,56 @@ def cmd_close(args: argparse.Namespace) -> int:
|
|
|
878
988
|
def cmd_classify(args: argparse.Namespace) -> int:
|
|
879
989
|
as_json = bool(args.json)
|
|
880
990
|
project = hc.resolve_main_project_root()
|
|
991
|
+
try:
|
|
992
|
+
workflow = _load_workflow_policy(project=project)
|
|
993
|
+
except (OSError, ValueError, hwp.PolicyValidationError) as exc:
|
|
994
|
+
return emit_error("POLICY_LOAD_FAILED", str(exc), as_json=as_json)
|
|
995
|
+
|
|
881
996
|
resolved = hc.resolve_change(project, args.change)
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
997
|
+
change_dir: Path | None = None
|
|
998
|
+
change_id = str(args.change or "")
|
|
999
|
+
if resolved.get("ok"):
|
|
1000
|
+
change_dir = Path(resolved["changeDir"])
|
|
1001
|
+
change_id = str(resolved.get("changeId") or change_dir.name)
|
|
1002
|
+
if not change_dir.is_dir():
|
|
1003
|
+
change_dir = None
|
|
1004
|
+
|
|
1005
|
+
if change_dir is not None:
|
|
1006
|
+
payload = classify_risk(change_dir, args.stage, workflow=workflow)
|
|
1007
|
+
else:
|
|
1008
|
+
payload = classify_defaults(workflow, change_id=change_id or "unknown", stage=args.stage)
|
|
1009
|
+
if not resolved.get("ok"):
|
|
1010
|
+
payload["resolveCode"] = resolved.get("code")
|
|
1011
|
+
|
|
1012
|
+
tier_override = getattr(args, "tier_override", None)
|
|
1013
|
+
if tier_override:
|
|
1014
|
+
payload = apply_tier_override(
|
|
1015
|
+
payload,
|
|
1016
|
+
workflow,
|
|
1017
|
+
tier=str(tier_override),
|
|
1018
|
+
override_by=str(getattr(args, "override_by", None) or "user"),
|
|
888
1019
|
)
|
|
889
|
-
|
|
1020
|
+
else:
|
|
1021
|
+
payload.setdefault("tierOverride", None)
|
|
1022
|
+
|
|
1023
|
+
classified_at = hc.now_iso()
|
|
1024
|
+
payload["classifiedAt"] = classified_at
|
|
1025
|
+
|
|
1026
|
+
if change_dir is not None and change_dir.is_dir():
|
|
1027
|
+
policy_doc = gate_policy_document(payload)
|
|
1028
|
+
policy_path = change_dir / "meta" / "gate-policy.json"
|
|
1029
|
+
_write_json(policy_path, policy_doc)
|
|
1030
|
+
payload["policyPersisted"] = True
|
|
1031
|
+
payload["policyPath"] = str(policy_path)
|
|
1032
|
+
else:
|
|
1033
|
+
payload["policyPersisted"] = False
|
|
1034
|
+
payload["warning"] = (
|
|
1035
|
+
"change directory does not exist; gate-policy.json not written"
|
|
1036
|
+
)
|
|
1037
|
+
|
|
890
1038
|
emit(payload, as_json=as_json)
|
|
891
1039
|
return 0
|
|
892
1040
|
|
|
893
|
-
|
|
894
1041
|
def cmd_checkpoint(args: argparse.Namespace) -> int:
|
|
895
1042
|
as_json = bool(args.json)
|
|
896
1043
|
project = hc.resolve_main_project_root()
|
|
@@ -1012,6 +1159,12 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
1012
1159
|
p_classify = sub.add_parser("classify", parents=[shared])
|
|
1013
1160
|
p_classify.add_argument("--change", default=None)
|
|
1014
1161
|
p_classify.add_argument("--stage", required=True, choices=["plan", "post-run"])
|
|
1162
|
+
p_classify.add_argument(
|
|
1163
|
+
"--tier-override",
|
|
1164
|
+
default=None,
|
|
1165
|
+
choices=["fast", "standard", "full"],
|
|
1166
|
+
)
|
|
1167
|
+
p_classify.add_argument("--override-by", default="user")
|
|
1015
1168
|
p_classify.set_defaults(func=cmd_classify)
|
|
1016
1169
|
|
|
1017
1170
|
p_checkpoint = sub.add_parser("checkpoint", parents=[shared])
|
|
@@ -816,6 +816,18 @@ def cmd_record(args: argparse.Namespace) -> int:
|
|
|
816
816
|
"finishedAt": now_iso(),
|
|
817
817
|
}
|
|
818
818
|
)
|
|
819
|
+
metrics_raw = getattr(args, "metrics_json", None)
|
|
820
|
+
if metrics_raw is not None and str(metrics_raw).strip() != "":
|
|
821
|
+
try:
|
|
822
|
+
metrics_obj = json.loads(str(metrics_raw))
|
|
823
|
+
except json.JSONDecodeError:
|
|
824
|
+
return emit_error("invalid --metrics-json", as_json=as_json)
|
|
825
|
+
if not isinstance(metrics_obj, dict):
|
|
826
|
+
return emit_error("invalid --metrics-json", as_json=as_json)
|
|
827
|
+
entry["metrics"] = metrics_obj
|
|
828
|
+
elif "metrics" in entry:
|
|
829
|
+
# Fresh record without metrics must not keep stale metrics from prev.
|
|
830
|
+
entry.pop("metrics", None)
|
|
819
831
|
if args.scope is not None and str(args.scope).strip():
|
|
820
832
|
entry["scope"] = str(args.scope).strip()
|
|
821
833
|
# No default scope: recording an incremental run as broad "module" scope
|
|
@@ -1014,6 +1026,11 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
1014
1026
|
default=None,
|
|
1015
1027
|
help="package: prior verifications reused when testsExecuted=false",
|
|
1016
1028
|
)
|
|
1029
|
+
p_record.add_argument(
|
|
1030
|
+
"--metrics-json",
|
|
1031
|
+
default=None,
|
|
1032
|
+
help='structured counts, e.g. \'{"run":155,"failures":0}\' or \'{"total":3,"passed":3}\'',
|
|
1033
|
+
)
|
|
1017
1034
|
p_record.set_defaults(func=cmd_record)
|
|
1018
1035
|
|
|
1019
1036
|
p_diff = sub.add_parser(
|
|
@@ -2,21 +2,21 @@
|
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"riskTiers": {
|
|
4
4
|
"fast": {
|
|
5
|
-
"description": "Low-risk docs/comments/non-behavioral config; no API/DB/security/concurrency.",
|
|
5
|
+
"description": "Low-risk docs/comments/non-behavioral config; no API/DB/security/concurrency. Policy notes: checkpointRules apply only when meta/implementation-checkpoints.json defines the entry for this change; missing file/entry = disabled (gate must not require --task).",
|
|
6
6
|
"defaultPhases": ["plan", "run", "submit", "archive"],
|
|
7
7
|
"requiredValidations": ["unitTest"],
|
|
8
8
|
"conditionalStages": ["review", "package", "apidoc"],
|
|
9
9
|
"upgradeTriggers": ["api-change", "security", "migration", "concurrency", "breaking-contract"]
|
|
10
10
|
},
|
|
11
11
|
"standard": {
|
|
12
|
-
"description": "Ordinary production code and non-breaking API/features.",
|
|
12
|
+
"description": "Ordinary production code and non-breaking API/features. Policy notes: requiredValidations may close DEGRADED when ledger status=NOT_RUN and evidence starts with 'DEGRADED: <reason>' (close code CLOSED_DEGRADED, phase status <= WARN); do not fake OK with unrelated tests.",
|
|
13
13
|
"defaultPhases": ["plan", "run", "test", "submit", "archive"],
|
|
14
14
|
"requiredValidations": ["compile", "unitTest", "unitTestFull"],
|
|
15
15
|
"conditionalStages": ["review", "package", "apidoc"],
|
|
16
16
|
"upgradeTriggers": ["auth", "security", "delete", "migration", "concurrency", "artifact-protocol", "shared-state", "breaking-api"]
|
|
17
17
|
},
|
|
18
18
|
"full": {
|
|
19
|
-
"description": "Auth/security/delete/migration/concurrency/service protocol/shared state/breaking contracts.",
|
|
19
|
+
"description": "Auth/security/delete/migration/concurrency/service protocol/shared state/breaking contracts. Policy notes: (1) DEGRADED close — ledger status=NOT_RUN and evidence 'DEGRADED: <reason>' → CLOSED_DEGRADED with phase <= WARN; never fake OK with unrelated tests. (2) checkpointRules — only when plan created meta/implementation-checkpoints.json entry; missing file/entry = not enabled, gate must not require --task.",
|
|
20
20
|
"defaultPhases": ["plan", "run", "test", "review", "submit", "archive"],
|
|
21
21
|
"requiredValidations": ["compile", "unitTest", "unitTestFull", "apiTest"],
|
|
22
22
|
"conditionalStages": ["package", "apidoc"],
|
|
@@ -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=4788a7abe108becd; overlay=none; agent=cursor; do not edit -->
|
|
7
7
|
# harness-archive — 变更归档
|
|
8
8
|
|
|
9
9
|
## Purpose
|
|
@@ -86,8 +86,8 @@ AskUserQuestion 让用户确认归档操作。**用户拒绝 → 终止流程,
|
|
|
86
86
|
### Phase 3:执行归档
|
|
87
87
|
|
|
88
88
|
1. 运行 `python <skills-root>/scripts/harness_archive.py status --change-dir ".harness/changes/<change-name>" --json` 前置检查。
|
|
89
|
-
2.
|
|
90
|
-
3. 运行 `python <skills-root>/scripts/harness_archive.py finalize --change-dir ".harness/changes/<change-name>" --archive-root ".harness/archive" --json`;读 JSON
|
|
89
|
+
2. `meta/archive-meta.md` **由 `harness_archive.py finalize` 自动生成**(与 summary-data `finalStatus` 同源);**禁止 agent 手写**该文件,手写视为数据丢失。维护者结论写入 events(decision/issue)即可,finalize 会汇总到 summary / archive-meta。
|
|
90
|
+
3. 运行 `python <skills-root>/scripts/harness_archive.py finalize --change-dir ".harness/changes/<change-name>" --archive-root ".harness/archive" --json`;读 JSON(cleanup、事件、移动、collect、render、validate、manifest 比对、archive-meta)。finalize 内部负责且仅负责一次 `phase.start` / `phase.end`,调用者不得重复追加。**finalize 不再同步执行知识维护**(§8.2):它写一个 `pending` maintenance-outbox 项即返回,`knowledgeMaintenance=QUEUED`;写 outbox 失败时 `NOT_QUEUED`(warning,不回滚 archive,总状态 CONDITIONAL)。后续由 `harness-sync` / `harness_knowledge.py maintain` 异步推进 outbox。**finalize 失败或 validate 报错时不删除原目录**。
|
|
91
91
|
|
|
92
92
|
- **Read `reference.md`** — finalize 输出字段、archive-meta 格式、CONDITIONAL_OK 规则
|
|
93
93
|
- **Read `templates/summary-data-template.json`** — summary-data 数据结构
|
|
@@ -11,7 +11,7 @@ description: harness-archive 的归档流程、manifest、summary-data、final-s
|
|
|
11
11
|
- **Phase 2 确认归档(强制阻断)**:AskUserQuestion 确认,拒绝即终止。
|
|
12
12
|
- **Phase 3 执行归档**:
|
|
13
13
|
1. 运行 `python <skills-root>/scripts/harness_archive.py status --change-dir ... --json` 做前置检查。
|
|
14
|
-
2.
|
|
14
|
+
2. `meta/archive-meta.md` 由 finalize 生成(与 summary `finalStatus` 同源);禁止手写。维护者结论写入 events 即可。finalize 在 before-manifest 前执行 cleanup(删除 lock/pid/launcher/credential,截断超大日志)。
|
|
15
15
|
3. 运行 `python <skills-root>/scripts/harness_archive.py finalize --change-dir ... --archive-root ".harness/archive" --json`;读 JSON 结果。finalize 内部负责且仅负责一次 `phase.start` / `phase.end`,调用者不得重复追加。**finalize 报错或 validate 失败时不删除原 changes 目录**。
|
|
16
16
|
- **Phase 4 验证与提示**:见 `checklist.md` 归档后验证项。
|
|
17
17
|
|
|
@@ -70,18 +70,26 @@ powershell.exe -NoProfile -Command "& '<node-path>' 'harness-skills/harness-arch
|
|
|
70
70
|
|
|
71
71
|
## archive-meta.md 模板
|
|
72
72
|
|
|
73
|
+
由 `harness_archive.py finalize` 生成(手写视为数据丢失)。frontmatter 与 summary-data 同源:
|
|
74
|
+
|
|
73
75
|
```markdown
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
-
|
|
77
|
-
-
|
|
78
|
-
-
|
|
79
|
-
-
|
|
80
|
-
-
|
|
81
|
-
|
|
82
|
-
|
|
76
|
+
---
|
|
77
|
+
archive-id: YYYY-MM-DD-<change-name>
|
|
78
|
+
change-name: <change-name>
|
|
79
|
+
archived-at: YYYY-MM-DD HH:mm
|
|
80
|
+
final-commit: <hash>
|
|
81
|
+
base-commit: <hash>
|
|
82
|
+
final-status: <OK|WARN|CONDITIONAL_OK|FAIL> # 与 summary-data.finalStatus 同源
|
|
83
|
+
source: harness-archive
|
|
84
|
+
---
|
|
85
|
+
# 归档元数据 — <change-name>
|
|
86
|
+
## 阶段状态
|
|
87
|
+
## 变更文件
|
|
88
|
+
## 已知风险
|
|
83
89
|
```
|
|
84
90
|
|
|
91
|
+
cleanup 步骤(before-manifest 前):删除 `events.ndjson.lock`、`runtime/*.pid`、launcher、credential/token/secret 文件名;截断 `logs/**/*.log` 超过 64KB 的尾部保留。
|
|
92
|
+
|
|
85
93
|
## 目录结构与最终状态规则
|
|
86
94
|
|
|
87
95
|
- 默认渲染器:`templates/render-summary.mjs`,输入 `reports/final/summary-data.json`,输出 `reports/final/final-summary.html`。
|