@hunter-harness/workflow-harness 0.2.38 → 0.2.39
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 +10 -7
- package/harness/bundles/general/claude-code/harness-archive/templates/render-summary.mjs +24 -4
- 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-ingest/scripts/harness_knowledge.py +283 -28
- 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-review/SKILL.md +2 -2
- package/harness/bundles/general/claude-code/harness-review/reference.md +20 -5
- package/harness/bundles/general/claude-code/harness-run/SKILL.md +1 -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/scripts/harness_archive.py +446 -20
- package/harness/bundles/general/claude-code/scripts/harness_events.py +273 -21
- package/harness/bundles/general/claude-code/scripts/harness_gate.py +56 -32
- package/harness/bundles/general/claude-code/scripts/harness_integration.py +197 -3
- package/harness/bundles/general/claude-code/scripts/harness_ledger.py +126 -2
- package/harness/bundles/general/claude-code/scripts/harness_report_model.py +28 -6
- package/harness/bundles/general/claude-code/scripts/harness_review.py +118 -17
- package/harness/bundles/general/claude-code/scripts/harness_service.py +219 -0
- package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +10 -7
- package/harness/bundles/general/codebuddy/harness-archive/templates/render-summary.mjs +24 -4
- 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-ingest/scripts/harness_knowledge.py +283 -28
- 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-review/SKILL.md +2 -2
- package/harness/bundles/general/codebuddy/harness-review/reference.md +20 -5
- package/harness/bundles/general/codebuddy/harness-run/SKILL.md +1 -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/scripts/harness_archive.py +446 -20
- package/harness/bundles/general/codebuddy/scripts/harness_events.py +273 -21
- package/harness/bundles/general/codebuddy/scripts/harness_gate.py +56 -32
- package/harness/bundles/general/codebuddy/scripts/harness_integration.py +197 -3
- package/harness/bundles/general/codebuddy/scripts/harness_ledger.py +126 -2
- package/harness/bundles/general/codebuddy/scripts/harness_report_model.py +28 -6
- package/harness/bundles/general/codebuddy/scripts/harness_review.py +118 -17
- package/harness/bundles/general/codebuddy/scripts/harness_service.py +219 -0
- package/harness/bundles/general/codex/.harness-build.json +1 -1
- package/harness/bundles/general/codex/harness-archive/SKILL.md +10 -7
- package/harness/bundles/general/codex/harness-archive/templates/render-summary.mjs +24 -4
- 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-ingest/scripts/harness_knowledge.py +283 -28
- 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-review/SKILL.md +2 -2
- package/harness/bundles/general/codex/harness-review/reference.md +20 -5
- package/harness/bundles/general/codex/harness-run/SKILL.md +1 -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/scripts/harness_archive.py +446 -20
- package/harness/bundles/general/codex/scripts/harness_events.py +273 -21
- package/harness/bundles/general/codex/scripts/harness_gate.py +56 -32
- package/harness/bundles/general/codex/scripts/harness_integration.py +197 -3
- package/harness/bundles/general/codex/scripts/harness_ledger.py +126 -2
- package/harness/bundles/general/codex/scripts/harness_report_model.py +28 -6
- package/harness/bundles/general/codex/scripts/harness_review.py +118 -17
- package/harness/bundles/general/codex/scripts/harness_service.py +219 -0
- package/harness/bundles/general/cursor/.harness-build.json +1 -1
- package/harness/bundles/general/cursor/harness-archive/SKILL.md +10 -7
- package/harness/bundles/general/cursor/harness-archive/templates/render-summary.mjs +24 -4
- 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-ingest/scripts/harness_knowledge.py +283 -28
- 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-review/SKILL.md +2 -2
- package/harness/bundles/general/cursor/harness-review/reference.md +20 -5
- package/harness/bundles/general/cursor/harness-run/SKILL.md +1 -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/scripts/harness_archive.py +446 -20
- package/harness/bundles/general/cursor/scripts/harness_events.py +273 -21
- package/harness/bundles/general/cursor/scripts/harness_gate.py +56 -32
- package/harness/bundles/general/cursor/scripts/harness_integration.py +197 -3
- package/harness/bundles/general/cursor/scripts/harness_ledger.py +126 -2
- package/harness/bundles/general/cursor/scripts/harness_report_model.py +28 -6
- package/harness/bundles/general/cursor/scripts/harness_review.py +118 -17
- package/harness/bundles/general/cursor/scripts/harness_service.py +219 -0
- package/harness/bundles/java/claude-code/.harness-build.json +1 -1
- package/harness/bundles/java/claude-code/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-archive/SKILL.md +10 -7
- package/harness/bundles/java/claude-code/harness-archive/templates/render-summary.mjs +24 -4
- 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-ingest/scripts/harness_knowledge.py +283 -28
- 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-review/SKILL.md +2 -2
- package/harness/bundles/java/claude-code/harness-review/reference.md +20 -5
- 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/scripts/harness_archive.py +446 -20
- package/harness/bundles/java/claude-code/scripts/harness_events.py +273 -21
- package/harness/bundles/java/claude-code/scripts/harness_gate.py +56 -32
- package/harness/bundles/java/claude-code/scripts/harness_integration.py +197 -3
- package/harness/bundles/java/claude-code/scripts/harness_ledger.py +126 -2
- package/harness/bundles/java/claude-code/scripts/harness_report_model.py +28 -6
- package/harness/bundles/java/claude-code/scripts/harness_review.py +118 -17
- package/harness/bundles/java/claude-code/scripts/harness_service.py +219 -0
- package/harness/bundles/java/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-archive/SKILL.md +10 -7
- package/harness/bundles/java/codebuddy/harness-archive/templates/render-summary.mjs +24 -4
- 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-ingest/scripts/harness_knowledge.py +283 -28
- 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-review/SKILL.md +2 -2
- package/harness/bundles/java/codebuddy/harness-review/reference.md +20 -5
- 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/scripts/harness_archive.py +446 -20
- package/harness/bundles/java/codebuddy/scripts/harness_events.py +273 -21
- package/harness/bundles/java/codebuddy/scripts/harness_gate.py +56 -32
- package/harness/bundles/java/codebuddy/scripts/harness_integration.py +197 -3
- package/harness/bundles/java/codebuddy/scripts/harness_ledger.py +126 -2
- package/harness/bundles/java/codebuddy/scripts/harness_report_model.py +28 -6
- package/harness/bundles/java/codebuddy/scripts/harness_review.py +118 -17
- package/harness/bundles/java/codebuddy/scripts/harness_service.py +219 -0
- package/harness/bundles/java/codex/.harness-build.json +1 -1
- package/harness/bundles/java/codex/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-archive/SKILL.md +10 -7
- package/harness/bundles/java/codex/harness-archive/templates/render-summary.mjs +24 -4
- 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-ingest/scripts/harness_knowledge.py +283 -28
- 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-review/SKILL.md +2 -2
- package/harness/bundles/java/codex/harness-review/reference.md +20 -5
- 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/scripts/harness_archive.py +446 -20
- package/harness/bundles/java/codex/scripts/harness_events.py +273 -21
- package/harness/bundles/java/codex/scripts/harness_gate.py +56 -32
- package/harness/bundles/java/codex/scripts/harness_integration.py +197 -3
- package/harness/bundles/java/codex/scripts/harness_ledger.py +126 -2
- package/harness/bundles/java/codex/scripts/harness_report_model.py +28 -6
- package/harness/bundles/java/codex/scripts/harness_review.py +118 -17
- package/harness/bundles/java/codex/scripts/harness_service.py +219 -0
- package/harness/bundles/java/cursor/.harness-build.json +1 -1
- package/harness/bundles/java/cursor/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-archive/SKILL.md +10 -7
- package/harness/bundles/java/cursor/harness-archive/templates/render-summary.mjs +24 -4
- 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-ingest/scripts/harness_knowledge.py +283 -28
- 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-review/SKILL.md +2 -2
- package/harness/bundles/java/cursor/harness-review/reference.md +20 -5
- 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/scripts/harness_archive.py +446 -20
- package/harness/bundles/java/cursor/scripts/harness_events.py +273 -21
- package/harness/bundles/java/cursor/scripts/harness_gate.py +56 -32
- package/harness/bundles/java/cursor/scripts/harness_integration.py +197 -3
- package/harness/bundles/java/cursor/scripts/harness_ledger.py +126 -2
- package/harness/bundles/java/cursor/scripts/harness_report_model.py +28 -6
- package/harness/bundles/java/cursor/scripts/harness_review.py +118 -17
- package/harness/bundles/java/cursor/scripts/harness_service.py +219 -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 +4 -4
- package/package.json +1 -1
|
@@ -2654,6 +2654,55 @@ def build_snapshot(project: Path) -> KnowledgeSnapshot:
|
|
|
2654
2654
|
)
|
|
2655
2655
|
|
|
2656
2656
|
|
|
2657
|
+
def archive_delta(
|
|
2658
|
+
archive_records: list[dict[str, Any]],
|
|
2659
|
+
previous_index: dict[str, Any] | None,
|
|
2660
|
+
) -> dict[str, set[str]]:
|
|
2661
|
+
"""Classify archive summaries without treating the whole corpus as dirty."""
|
|
2662
|
+
previous_items = (
|
|
2663
|
+
previous_index.get("archives", {}).get("items", [])
|
|
2664
|
+
if isinstance(previous_index, dict)
|
|
2665
|
+
else []
|
|
2666
|
+
)
|
|
2667
|
+
previous = {
|
|
2668
|
+
str(item.get("summaryData")): str(item.get("summarySha256") or "")
|
|
2669
|
+
for item in previous_items
|
|
2670
|
+
if isinstance(item, dict) and item.get("summaryData")
|
|
2671
|
+
}
|
|
2672
|
+
current = {
|
|
2673
|
+
str(item.get("summaryData")): str(item.get("summarySha256") or "")
|
|
2674
|
+
for item in archive_records
|
|
2675
|
+
if item.get("summaryData")
|
|
2676
|
+
}
|
|
2677
|
+
added = set(current) - set(previous)
|
|
2678
|
+
changed = {
|
|
2679
|
+
path for path in set(current) & set(previous)
|
|
2680
|
+
if current[path] != previous[path]
|
|
2681
|
+
}
|
|
2682
|
+
return {
|
|
2683
|
+
"added": added,
|
|
2684
|
+
"changed": changed,
|
|
2685
|
+
"removed": set(previous) - set(current),
|
|
2686
|
+
"dirty": added | changed,
|
|
2687
|
+
}
|
|
2688
|
+
|
|
2689
|
+
|
|
2690
|
+
def restore_unchanged_entry_state(
|
|
2691
|
+
entry: dict[str, Any],
|
|
2692
|
+
previous_entries: dict[str, dict[str, Any]],
|
|
2693
|
+
*,
|
|
2694
|
+
is_dirty: bool,
|
|
2695
|
+
) -> None:
|
|
2696
|
+
"""Keep historical lifecycle decisions out of a warm ingest hot path."""
|
|
2697
|
+
if is_dirty:
|
|
2698
|
+
return
|
|
2699
|
+
previous = previous_entries.get(str(entry.get("id") or ""))
|
|
2700
|
+
if previous is None:
|
|
2701
|
+
return
|
|
2702
|
+
entry["status"] = previous.get("status", entry.get("status"))
|
|
2703
|
+
entry["lifecycle"] = json_clone(previous.get("lifecycle", entry.get("lifecycle", {})))
|
|
2704
|
+
|
|
2705
|
+
|
|
2657
2706
|
def build_index(
|
|
2658
2707
|
project: Path,
|
|
2659
2708
|
incremental: bool = True,
|
|
@@ -2684,19 +2733,21 @@ def build_index(
|
|
|
2684
2733
|
ensure_knowledge_dirs(knowledge)
|
|
2685
2734
|
reporter.emit("snapshot", len(summary_paths), len(summary_paths), force=True)
|
|
2686
2735
|
|
|
2736
|
+
old_index: dict[str, Any] | None = None
|
|
2737
|
+
index_path = knowledge / "index.json"
|
|
2738
|
+
if index_path.exists():
|
|
2739
|
+
try:
|
|
2740
|
+
old_index = read_json(index_path)
|
|
2741
|
+
except (OSError, json.JSONDecodeError):
|
|
2742
|
+
old_index = None
|
|
2743
|
+
delta = archive_delta(archive_records, old_index)
|
|
2744
|
+
|
|
2687
2745
|
# No-op fast path: inputs (archive checksums + config + schema) are unchanged.
|
|
2688
2746
|
# Write nothing — entries, sqlite, index and views all stay byte-identical,
|
|
2689
2747
|
# so a repeated ingest is a true no-op (design §3.5, cluster 6, UT-025).
|
|
2690
2748
|
# Also require index.sqlite to exist so a query never sees a stale index.json
|
|
2691
2749
|
# pointing at a missing sqlite (API-009 single ensure-current).
|
|
2692
2750
|
if incremental:
|
|
2693
|
-
old_index: dict[str, Any] | None = None
|
|
2694
|
-
index_path = knowledge / "index.json"
|
|
2695
|
-
if index_path.exists():
|
|
2696
|
-
try:
|
|
2697
|
-
old_index = read_json(index_path)
|
|
2698
|
-
except (OSError, json.JSONDecodeError):
|
|
2699
|
-
old_index = None
|
|
2700
2751
|
if (
|
|
2701
2752
|
isinstance(old_index, dict)
|
|
2702
2753
|
and old_index.get("inputsHash") == inputs_hash
|
|
@@ -2716,6 +2767,10 @@ def build_index(
|
|
|
2716
2767
|
"sqliteRebuild": 0,
|
|
2717
2768
|
"sqliteUpsert": 0,
|
|
2718
2769
|
"sqliteDelete": 0,
|
|
2770
|
+
"archivesAdded": 0,
|
|
2771
|
+
"archivesChanged": 0,
|
|
2772
|
+
"archivesRemoved": 0,
|
|
2773
|
+
"archivesDelta": 0,
|
|
2719
2774
|
}
|
|
2720
2775
|
)
|
|
2721
2776
|
result = dict(old_index)
|
|
@@ -2751,7 +2806,17 @@ def build_index(
|
|
|
2751
2806
|
"sqliteDelete": 0,
|
|
2752
2807
|
"summaryHashesReused": 0,
|
|
2753
2808
|
"summaryHashesComputed": 0,
|
|
2809
|
+
"archivesAdded": len(delta["added"]),
|
|
2810
|
+
"archivesChanged": len(delta["changed"]),
|
|
2811
|
+
"archivesRemoved": len(delta["removed"]),
|
|
2812
|
+
"archivesDelta": len(delta["dirty"]) + len(delta["removed"]),
|
|
2813
|
+
}
|
|
2814
|
+
previous_entries = {
|
|
2815
|
+
str(entry.get("id")): entry
|
|
2816
|
+
for _, entry in load_entry_files(knowledge)
|
|
2817
|
+
if entry.get("id")
|
|
2754
2818
|
}
|
|
2819
|
+
dirty_entry_ids: set[str] = set()
|
|
2755
2820
|
|
|
2756
2821
|
for summary_index, summary_path in enumerate(summary_paths, start=1):
|
|
2757
2822
|
summary_rel = rel_to_project(project, summary_path)
|
|
@@ -2790,6 +2855,15 @@ def build_index(
|
|
|
2790
2855
|
archive_entries = rebind_cached_archive_entries(
|
|
2791
2856
|
project, summary_path, summary_hash, archive_entries
|
|
2792
2857
|
)
|
|
2858
|
+
is_dirty = summary_rel in delta["dirty"] or not incremental
|
|
2859
|
+
for entry in archive_entries:
|
|
2860
|
+
restore_unchanged_entry_state(
|
|
2861
|
+
entry,
|
|
2862
|
+
previous_entries,
|
|
2863
|
+
is_dirty=is_dirty,
|
|
2864
|
+
)
|
|
2865
|
+
if is_dirty and entry.get("id"):
|
|
2866
|
+
dirty_entry_ids.add(str(entry["id"]))
|
|
2793
2867
|
# API-006/RET-40: entries from archives failing the publication
|
|
2794
2868
|
# gate stay quarantined (candidate only, never active/promoted).
|
|
2795
2869
|
archive_rel = rel_to_project(project, archive_dir_from_summary(summary_path))
|
|
@@ -2833,9 +2907,24 @@ def build_index(
|
|
|
2833
2907
|
seen.add(fp)
|
|
2834
2908
|
deduped.append(entry)
|
|
2835
2909
|
|
|
2836
|
-
|
|
2910
|
+
freshness_entries = [
|
|
2911
|
+
entry
|
|
2912
|
+
for entry in deduped
|
|
2913
|
+
if (
|
|
2914
|
+
str(entry.get("id") or "") in dirty_entry_ids
|
|
2915
|
+
or (
|
|
2916
|
+
entry.get("status") in {"active", "candidate"}
|
|
2917
|
+
and str(entry.get("id") or "") not in previous_entries
|
|
2918
|
+
)
|
|
2919
|
+
)
|
|
2920
|
+
]
|
|
2921
|
+
ingest_mode.update(apply_batch_git_freshness(project, freshness_entries))
|
|
2837
2922
|
reporter.emit("near-deduplicate", 0, len(deduped), force=True)
|
|
2838
|
-
near_dedupe = dedupe_near_duplicates(
|
|
2923
|
+
near_dedupe = dedupe_near_duplicates(
|
|
2924
|
+
deduped,
|
|
2925
|
+
progress=reporter,
|
|
2926
|
+
dirty_entry_ids=dirty_entry_ids if incremental else None,
|
|
2927
|
+
)
|
|
2839
2928
|
ingest_mode["nearDuplicatesMerged"] = near_dedupe["merged"]
|
|
2840
2929
|
ingest_mode["similarityCandidates"] = near_dedupe["similarityCandidates"]
|
|
2841
2930
|
ingest_mode["exactSimilarityComparisons"] = near_dedupe["exactSimilarityComparisons"]
|
|
@@ -2920,7 +3009,22 @@ def build_index(
|
|
|
2920
3009
|
)
|
|
2921
3010
|
|
|
2922
3011
|
reporter.emit("sqlite", 0, len(indexed_entries), force=True)
|
|
2923
|
-
|
|
3012
|
+
sqlite_dirty_entries = [
|
|
3013
|
+
entry
|
|
3014
|
+
for entry in indexed_entries
|
|
3015
|
+
if (
|
|
3016
|
+
str(entry.get("id") or "") in dirty_entry_ids
|
|
3017
|
+
or (
|
|
3018
|
+
entry.get("status") in {"active", "candidate"}
|
|
3019
|
+
and str(entry.get("id") or "") not in previous_entries
|
|
3020
|
+
)
|
|
3021
|
+
)
|
|
3022
|
+
]
|
|
3023
|
+
sqlite_stats = write_sqlite(
|
|
3024
|
+
knowledge / "index.sqlite",
|
|
3025
|
+
indexed_entries,
|
|
3026
|
+
dirty_entries=sqlite_dirty_entries if incremental else None,
|
|
3027
|
+
)
|
|
2924
3028
|
ingest_mode.update(sqlite_stats)
|
|
2925
3029
|
ingest_mode["sqliteRowsTouched"] = (
|
|
2926
3030
|
sqlite_stats.get("sqliteUpsert", 0) + sqlite_stats.get("sqliteDelete", 0)
|
|
@@ -3288,13 +3392,19 @@ def assert_persistence_invariants(
|
|
|
3288
3392
|
)
|
|
3289
3393
|
|
|
3290
3394
|
|
|
3291
|
-
def write_sqlite(
|
|
3395
|
+
def write_sqlite(
|
|
3396
|
+
path: Path,
|
|
3397
|
+
entries: list[dict[str, Any]],
|
|
3398
|
+
*,
|
|
3399
|
+
dirty_entries: list[dict[str, Any]] | None = None,
|
|
3400
|
+
) -> dict[str, Any]:
|
|
3292
3401
|
"""Persist entries to SQLite using a transactional dirty-set.
|
|
3293
3402
|
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3403
|
+
The normal path compares existing row payloads. Incremental callers may
|
|
3404
|
+
provide the known dirty entries instead, avoiding a full JSON digest of
|
|
3405
|
+
unchanged historical rows. Both paths delete rows absent from the current
|
|
3406
|
+
closure in one transaction. When the schema version changes or the table
|
|
3407
|
+
is missing, performs a full rebuild.
|
|
3298
3408
|
"""
|
|
3299
3409
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
3300
3410
|
stats: dict[str, Any] = {
|
|
@@ -3315,18 +3425,52 @@ def write_sqlite(path: Path, entries: list[dict[str, Any]]) -> dict[str, Any]:
|
|
|
3315
3425
|
need_rebuild = (not table_exists) or existing_version != SQLITE_SCHEMA_VERSION
|
|
3316
3426
|
|
|
3317
3427
|
existing_map: dict[str, str] = {}
|
|
3318
|
-
|
|
3428
|
+
existing_status: dict[str, str] = {}
|
|
3429
|
+
dirty_mode = dirty_entries is not None and not need_rebuild
|
|
3430
|
+
if not need_rebuild and not dirty_mode:
|
|
3319
3431
|
rows = con.execute("select id, entry_json from entries").fetchall()
|
|
3320
3432
|
existing_map = {str(row[0]): str(row[1]) for row in rows}
|
|
3433
|
+
elif dirty_mode:
|
|
3434
|
+
rows = con.execute("select id, status from entries").fetchall()
|
|
3435
|
+
existing_map = {str(row[0]): "" for row in rows}
|
|
3436
|
+
existing_status = {str(row[0]): str(row[1] or "") for row in rows}
|
|
3321
3437
|
|
|
3322
3438
|
new_map: dict[str, str] = {}
|
|
3323
|
-
|
|
3439
|
+
entries_to_serialize = list(dirty_entries or []) if dirty_mode else list(entries)
|
|
3440
|
+
if dirty_mode:
|
|
3441
|
+
# HH-KNOW-20260730-002: archive-delta dirty sets miss lifecycle-only
|
|
3442
|
+
# transitions (promote/demote/conflict/supersede) on unchanged
|
|
3443
|
+
# archives. Reconcile those by status without digesting every row.
|
|
3444
|
+
seen_serialize_ids = {
|
|
3445
|
+
str(entry.get("id") or "")
|
|
3446
|
+
for entry in entries_to_serialize
|
|
3447
|
+
if isinstance(entry, dict) and entry.get("id")
|
|
3448
|
+
}
|
|
3449
|
+
for entry in entries:
|
|
3450
|
+
if not isinstance(entry, dict) or not entry.get("id"):
|
|
3451
|
+
continue
|
|
3452
|
+
eid = str(entry["id"])
|
|
3453
|
+
if eid in seen_serialize_ids:
|
|
3454
|
+
continue
|
|
3455
|
+
if existing_status.get(eid) != str(entry.get("status") or ""):
|
|
3456
|
+
entries_to_serialize.append(entry)
|
|
3457
|
+
seen_serialize_ids.add(eid)
|
|
3458
|
+
for entry in entries_to_serialize:
|
|
3324
3459
|
if isinstance(entry, dict) and entry.get("id"):
|
|
3325
3460
|
new_map[str(entry["id"])] = json.dumps(entry, ensure_ascii=False)
|
|
3326
3461
|
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3462
|
+
current_ids = {
|
|
3463
|
+
str(entry["id"])
|
|
3464
|
+
for entry in entries
|
|
3465
|
+
if isinstance(entry, dict) and entry.get("id")
|
|
3466
|
+
}
|
|
3467
|
+
to_upsert = (
|
|
3468
|
+
list(new_map)
|
|
3469
|
+
if dirty_mode
|
|
3470
|
+
else [eid for eid in new_map if new_map[eid] != existing_map.get(eid)]
|
|
3471
|
+
)
|
|
3472
|
+
to_delete = [eid for eid in existing_map if eid not in current_ids]
|
|
3473
|
+
stats["sqliteUnchanged"] = len(entries) - len(to_upsert)
|
|
3330
3474
|
|
|
3331
3475
|
if not need_rebuild and not to_upsert and not to_delete:
|
|
3332
3476
|
# nothing to do; leave the file (and its mtime) untouched
|
|
@@ -3372,6 +3516,11 @@ def write_sqlite(path: Path, entries: list[dict[str, Any]]) -> dict[str, Any]:
|
|
|
3372
3516
|
con.execute(f"pragma user_version = {SQLITE_SCHEMA_VERSION}")
|
|
3373
3517
|
stats["sqliteRebuild"] = 1
|
|
3374
3518
|
# full rebuild => every entry must be (re)inserted
|
|
3519
|
+
new_map = {
|
|
3520
|
+
str(entry["id"]): json.dumps(entry, ensure_ascii=False)
|
|
3521
|
+
for entry in entries
|
|
3522
|
+
if isinstance(entry, dict) and entry.get("id")
|
|
3523
|
+
}
|
|
3375
3524
|
to_upsert = list(new_map.keys())
|
|
3376
3525
|
to_delete = []
|
|
3377
3526
|
|
|
@@ -4121,6 +4270,62 @@ def sync_status(
|
|
|
4121
4270
|
action = "ingested"
|
|
4122
4271
|
reasons = []
|
|
4123
4272
|
|
|
4273
|
+
# HH-KNOW-20260730-001: an up-to-date index is not the whole story --
|
|
4274
|
+
# maintenance-outbox items enqueued by `harness archive` still need to be
|
|
4275
|
+
# drained (auto-supersede/reverify-stale/judge-export) before the project
|
|
4276
|
+
# is truly caught up. Drain here whenever the index is current: either it
|
|
4277
|
+
# was just refreshed above, or it was already current on entry. When the
|
|
4278
|
+
# caller did not request an update and the index is stale, skip the drain
|
|
4279
|
+
# -- maintain_knowledge() would otherwise trigger an implicit rebuild via
|
|
4280
|
+
# its own build_index(incremental=True) call, contradicting "don't force
|
|
4281
|
+
# rebuild" for a plain status check.
|
|
4282
|
+
index_current = not reasons
|
|
4283
|
+
outbox_root = _outbox_root(project)
|
|
4284
|
+
maintenance: dict[str, Any]
|
|
4285
|
+
next_action: str | None = None
|
|
4286
|
+
maintain_hint = (
|
|
4287
|
+
"python harness/harness-knowledge-ingest/scripts/harness_knowledge.py "
|
|
4288
|
+
"maintain --project <project> --drain"
|
|
4289
|
+
)
|
|
4290
|
+
|
|
4291
|
+
if index_current:
|
|
4292
|
+
drain_result = drain_maintenance_outbox(project)
|
|
4293
|
+
outbox_ok = bool(drain_result.get("ok")) and drain_result.get("remaining", 0) == 0
|
|
4294
|
+
maintenance = {
|
|
4295
|
+
"attempted": True,
|
|
4296
|
+
"skipped": False,
|
|
4297
|
+
"ok": outbox_ok,
|
|
4298
|
+
"processed": drain_result.get("processed", 0),
|
|
4299
|
+
"remaining": drain_result.get("remaining", 0),
|
|
4300
|
+
"staleRunningRecovered": drain_result.get("staleRunningRecovered", 0),
|
|
4301
|
+
"results": drain_result.get("results", []),
|
|
4302
|
+
}
|
|
4303
|
+
if not outbox_ok:
|
|
4304
|
+
next_action = (
|
|
4305
|
+
"Maintenance outbox is not fully drained. Retry `hunter-harness sync`, "
|
|
4306
|
+
f"or run `{maintain_hint}` to process the remaining items."
|
|
4307
|
+
)
|
|
4308
|
+
else:
|
|
4309
|
+
pending_count = len(list((outbox_root / "pending").glob("*.json"))) if outbox_root.is_dir() else 0
|
|
4310
|
+
failed_count = len(list((outbox_root / "failed").glob("*.json"))) if outbox_root.is_dir() else 0
|
|
4311
|
+
running_count = len(list((outbox_root / "running").glob("*.json"))) if outbox_root.is_dir() else 0
|
|
4312
|
+
outbox_ok = pending_count == 0 and failed_count == 0
|
|
4313
|
+
maintenance = {
|
|
4314
|
+
"attempted": False,
|
|
4315
|
+
"skipped": True,
|
|
4316
|
+
"skippedReason": "index out of date; rerun with --update to rebuild and drain",
|
|
4317
|
+
"ok": outbox_ok,
|
|
4318
|
+
"pending": pending_count,
|
|
4319
|
+
"failed": failed_count,
|
|
4320
|
+
"running": running_count,
|
|
4321
|
+
}
|
|
4322
|
+
next_action = (
|
|
4323
|
+
"Run `hunter-harness sync` (or sync --update) to rebuild the index; "
|
|
4324
|
+
"the maintenance outbox drain is skipped while the index is stale."
|
|
4325
|
+
)
|
|
4326
|
+
|
|
4327
|
+
ok = index_current and bool(maintenance.get("ok"))
|
|
4328
|
+
|
|
4124
4329
|
result = {
|
|
4125
4330
|
"project": str(project),
|
|
4126
4331
|
"upToDate": not reasons,
|
|
@@ -4131,6 +4336,9 @@ def sync_status(
|
|
|
4131
4336
|
"index": str(index_path),
|
|
4132
4337
|
"sqlite": str(sqlite_path),
|
|
4133
4338
|
},
|
|
4339
|
+
"maintenance": maintenance,
|
|
4340
|
+
"ok": ok,
|
|
4341
|
+
"nextAction": next_action,
|
|
4134
4342
|
}
|
|
4135
4343
|
if refreshed is not None:
|
|
4136
4344
|
result["index"] = summarize_index(refreshed)
|
|
@@ -4522,16 +4730,33 @@ def dedupe_near_duplicates(
|
|
|
4522
4730
|
threshold: float = NEAR_DUPLICATE_THRESHOLD,
|
|
4523
4731
|
*,
|
|
4524
4732
|
progress: ProgressReporter | None = None,
|
|
4733
|
+
dirty_entry_ids: set[str] | None = None,
|
|
4525
4734
|
max_exact_comparisons: int = 50_000,
|
|
4526
4735
|
time_budget_ms: int = 2_000,
|
|
4527
4736
|
) -> dict[str, Any]:
|
|
4528
|
-
"""Merge near duplicates through sparse buckets and an explicit work budget.
|
|
4737
|
+
"""Merge near duplicates through sparse buckets and an explicit work budget.
|
|
4738
|
+
|
|
4739
|
+
Full / offline path (``dirty_entry_ids is None``): keep historical semantics —
|
|
4740
|
+
every non-superseded status participates, and pairs stay scoped to the same
|
|
4741
|
+
``(archive, type)`` group.
|
|
4742
|
+
|
|
4743
|
+
Online hot path (``dirty_entry_ids`` provided): exclude stale/superseded from
|
|
4744
|
+
the comparison corpus, keep archive scoping, and only emit pairs that touch
|
|
4745
|
+
at least one dirty entry (HH-KNOW-20260730-002).
|
|
4746
|
+
"""
|
|
4529
4747
|
started = time.monotonic()
|
|
4530
4748
|
deadline = started + max(0, time_budget_ms) / 1_000
|
|
4531
4749
|
merges: list[dict[str, Any]] = []
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4750
|
+
if dirty_entry_ids is None:
|
|
4751
|
+
active_entries = [
|
|
4752
|
+
entry for entry in entries if entry.get("status") != "superseded"
|
|
4753
|
+
]
|
|
4754
|
+
else:
|
|
4755
|
+
active_entries = [
|
|
4756
|
+
entry
|
|
4757
|
+
for entry in entries
|
|
4758
|
+
if entry.get("status") in {"active", "candidate"}
|
|
4759
|
+
]
|
|
4535
4760
|
normalized = {id(entry): entry_compare_text(entry) for entry in active_entries}
|
|
4536
4761
|
similarity_cache: dict[tuple[int, int], float] = {}
|
|
4537
4762
|
similarity_candidates = 0
|
|
@@ -4540,6 +4765,8 @@ def dedupe_near_duplicates(
|
|
|
4540
4765
|
|
|
4541
4766
|
grouped: dict[tuple[str, str], list[dict[str, Any]]] = {}
|
|
4542
4767
|
for entry in active_entries:
|
|
4768
|
+
# Same-archive scoping is intentional: near-dup merges are intra-archive.
|
|
4769
|
+
# Cross-archive evolution/conflict is handled by supersede/review paths.
|
|
4543
4770
|
key = (
|
|
4544
4771
|
str(entry.get("source", {}).get("archive") or ""),
|
|
4545
4772
|
str(entry.get("type") or ""),
|
|
@@ -4580,6 +4807,13 @@ def dedupe_near_duplicates(
|
|
|
4580
4807
|
for right_identity in members[left_index + 1 :]:
|
|
4581
4808
|
if left_identity == right_identity:
|
|
4582
4809
|
continue
|
|
4810
|
+
if dirty_entry_ids is not None and not (
|
|
4811
|
+
str(entry_by_identity[left_identity].get("id") or "")
|
|
4812
|
+
in dirty_entry_ids
|
|
4813
|
+
or str(entry_by_identity[right_identity].get("id") or "")
|
|
4814
|
+
in dirty_entry_ids
|
|
4815
|
+
):
|
|
4816
|
+
continue
|
|
4583
4817
|
candidate_pairs.add(tuple(sorted((left_identity, right_identity))))
|
|
4584
4818
|
|
|
4585
4819
|
def similarity(left: dict[str, Any], right: dict[str, Any]) -> float:
|
|
@@ -5451,15 +5685,35 @@ def maintain_knowledge(project: Path, archive_id: str) -> dict[str, Any]:
|
|
|
5451
5685
|
}
|
|
5452
5686
|
|
|
5453
5687
|
|
|
5454
|
-
def drain_maintenance_outbox(
|
|
5455
|
-
|
|
5688
|
+
def drain_maintenance_outbox(
|
|
5689
|
+
project: Path, limit: int = 50, *, stale_running_seconds: float = 300.0
|
|
5690
|
+
) -> dict[str, Any]:
|
|
5691
|
+
"""Advance all retryable maintenance items in one bounded invocation.
|
|
5692
|
+
|
|
5693
|
+
Crash recovery: a `running` item left behind by a process that died
|
|
5694
|
+
mid-maintenance never returns to `pending`/`failed` on its own, so it
|
|
5695
|
+
would stall forever. Once a `running` item is older than
|
|
5696
|
+
`stale_running_seconds` it is treated as stuck and re-included here.
|
|
5697
|
+
`claim_outbox` returns a `running` item as-is (it only claims from
|
|
5698
|
+
pending/failed/pending-judge), and `maintain_knowledge` does NOT early
|
|
5699
|
+
return for `status == "running"` (only for `"completed"`) -- it falls
|
|
5700
|
+
through to the try block and retries the maintenance steps in place, so
|
|
5701
|
+
simply including the stale id in the drain batch is sufficient recovery.
|
|
5702
|
+
"""
|
|
5456
5703
|
project = project.resolve()
|
|
5457
5704
|
root = _outbox_root(project)
|
|
5458
|
-
|
|
5705
|
+
now = time.time()
|
|
5706
|
+
retryable_ids = {
|
|
5459
5707
|
path.stem
|
|
5460
5708
|
for status in ("pending", "failed")
|
|
5461
5709
|
for path in (root / status).glob("*.json")
|
|
5462
|
-
}
|
|
5710
|
+
}
|
|
5711
|
+
stale_running_ids = {
|
|
5712
|
+
path.stem
|
|
5713
|
+
for path in (root / "running").glob("*.json")
|
|
5714
|
+
if now - path.stat().st_mtime >= stale_running_seconds
|
|
5715
|
+
}
|
|
5716
|
+
all_archive_ids = sorted(retryable_ids | stale_running_ids)
|
|
5463
5717
|
archive_ids = all_archive_ids[:max(0, limit)]
|
|
5464
5718
|
results = [maintain_knowledge(project, archive_id) for archive_id in archive_ids]
|
|
5465
5719
|
return {
|
|
@@ -5467,6 +5721,7 @@ def drain_maintenance_outbox(project: Path, limit: int = 50) -> dict[str, Any]:
|
|
|
5467
5721
|
"project": str(project),
|
|
5468
5722
|
"processed": len(results),
|
|
5469
5723
|
"remaining": max(0, len(all_archive_ids) - len(results)),
|
|
5724
|
+
"staleRunningRecovered": len(stale_running_ids & set(archive_ids)),
|
|
5470
5725
|
"results": results,
|
|
5471
5726
|
}
|
|
5472
5727
|
|
|
@@ -3,7 +3,7 @@ name: harness-knowledge-query
|
|
|
3
3
|
description: 根据新需求、设计任务、代码修改请求或问题排查,在规划和编码前查询 .harness/knowledge
|
|
4
4
|
历史上下文。适用场景:query knowledge、查找历史需求、根据归档理解需求、继续之前类似开发、生成需求上下文包。
|
|
5
5
|
---
|
|
6
|
-
<!-- generated by harness_deploy.py; core=
|
|
6
|
+
<!-- generated by harness_deploy.py; core=96a3184db7f7f75e; overlay=java; agent=cursor; do not edit -->
|
|
7
7
|
# harness-knowledge-query
|
|
8
8
|
|
|
9
9
|
并行 change 中查询必须传 `--change <change-id>`;成功后以该 change 的 `meta/knowledge-context.json` 为稳定指针。全局 `context-packs/latest.json` 仅向后兼容,不得用于判断多个 active change 的上下文归属。
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: harness-package
|
|
3
3
|
description: 增量模块打包:拉取最新→编译验证→mvn package→汇总 jar/war。使用场景:打包、package、发版准备
|
|
4
4
|
---
|
|
5
|
-
<!-- generated by harness_deploy.py; core=
|
|
5
|
+
<!-- generated by harness_deploy.py; core=96a3184db7f7f75e; overlay=java; agent=cursor; do not edit -->
|
|
6
6
|
# harness-package — 增量模块打包(Java overlay)
|
|
7
7
|
|
|
8
8
|
## Purpose
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: harness-plan
|
|
3
3
|
description: 将需求转化为设计文档+实施计划+完整测试场景表,必须在编码前完成。使用场景:需求分析、feature plan、技术方案设计、实现方案规划
|
|
4
4
|
---
|
|
5
|
-
<!-- generated by harness_deploy.py; core=
|
|
5
|
+
<!-- generated by harness_deploy.py; core=96a3184db7f7f75e; overlay=java; agent=cursor; do not edit -->
|
|
6
6
|
# harness-plan — 需求规划
|
|
7
7
|
|
|
8
8
|
## Purpose
|
|
@@ -3,7 +3,7 @@ name: harness-review
|
|
|
3
3
|
description: 6维度代码审查(架构/安全/规范/兼容/测试/性能),对照项目规则(见
|
|
4
4
|
.harness/context-index.json)和测试场景表,在隔离上下文运行。使用场景:代码审查、提交前检查、合并评审
|
|
5
5
|
---
|
|
6
|
-
<!-- generated by harness_deploy.py; core=
|
|
6
|
+
<!-- generated by harness_deploy.py; core=96a3184db7f7f75e; overlay=java; agent=cursor; do not edit -->
|
|
7
7
|
# harness-review — 代码审查
|
|
8
8
|
|
|
9
9
|
## Purpose
|
|
@@ -123,7 +123,7 @@ review 结果默认只作参考,不阻塞 submit/archive;报告措辞遵循
|
|
|
123
123
|
|
|
124
124
|
### 四、Shell 安全 / 敏感信息 / 证据化报告 / CodeGraph 探索
|
|
125
125
|
|
|
126
|
-
git diff/log 命令通过 `powershell.exe -Command "..."` 执行;review-report 中如发现明文 token/密码/密钥,必须列入 RED 问题并在报告中以 `<TOKEN_REDACTED>` 等占位符引用;RED/YELLOW/OK 结论必须基于实际 diff 内容,不得凭印象判断。代码探索必须优先使用 CodeGraph MCP 工具(`mcp__codegraph__codegraph_explore`),不允许通过普通 Bash 调 codegraph 命令(已列入 `disallowed-tools
|
|
126
|
+
git diff/log 命令通过 `powershell.exe -Command "..."` 执行;review-report 中如发现明文 token/密码/密钥,必须列入 RED 问题并在报告中以 `<TOKEN_REDACTED>` 等占位符引用;RED/YELLOW/OK 结论必须基于实际 diff 内容,不得凭印象判断。代码探索必须优先使用 CodeGraph MCP 工具(`mcp__codegraph__codegraph_explore`),不允许通过普通 Bash 调 codegraph 命令(已列入 `disallowed-tools`);采纳任何返回源码前,按 `reference.md` 的 C12 合同校验 executionRoot/rootPath、worktreeId、repositoryId、HEAD 与 index snapshot,`IDENTITY_MISMATCH` 必须读取实际 worktree,不得当作 stale 忽略;MCP 不可用时降级为 Grep/Glob + Read,并在执行日志记录降级原因。遵循 `../protocols/powershell-protocol.md` / `sensitive-info-protocol.md` / `evidence-based-reporting-protocol.md`。
|
|
127
127
|
|
|
128
128
|
### 五、state snapshot 与源码重验
|
|
129
129
|
|
|
@@ -87,13 +87,28 @@ fixback 是给后续修复循环使用的结构化清单,不替代 review-repo
|
|
|
87
87
|
|
|
88
88
|
## CodeGraph Identity 合同(C12)
|
|
89
89
|
|
|
90
|
-
|
|
90
|
+
发出 `codegraph_explore` 前,必须从实际 `executionRoot` 构造并随查询记录以下
|
|
91
|
+
expected identity:`rootPath`、`worktreeId`(主 checkout 为 `null`)、`repositoryId`、`head`。
|
|
92
|
+
采纳返回源码前,必须校验响应中的:
|
|
91
93
|
|
|
94
|
+
- `rootPath` 必须是当前 `executionRoot`(规范化后比较);不可只比较 repositoryId
|
|
95
|
+
- `worktreeId` 必须与当前 worktree 相同;主 checkout 必须为 `null`
|
|
92
96
|
- `repositoryId` 必须与 `harness_paths.repository_identity(project_root)` 一致
|
|
93
|
-
- `indexedHead
|
|
94
|
-
- `indexedAt
|
|
97
|
+
- `head`(兼容 `indexedHead`)必须与当前 HEAD 一致
|
|
98
|
+
- `indexSnapshotAt`(兼容 `indexedAt`)必须存在
|
|
95
99
|
|
|
96
|
-
identity
|
|
100
|
+
identity 不匹配时必须返回 `IDENTITY_MISMATCH`,包含 expected/actual 的完整
|
|
101
|
+
identity,且**不得采纳任何 CodeGraph 源码**;降级为 Grep/Glob + Read 实际
|
|
102
|
+
worktree。该状态不同于 index stale,不能以 stale warning 代替。
|
|
97
103
|
identity 匹配时正常使用 codegraph 结果。
|
|
98
104
|
|
|
99
|
-
|
|
105
|
+
校验函数:
|
|
106
|
+
`harness_review.validate_codegraph_identity(response, expected_repository_id, expected_head, expected_root, expected_worktree_id)`。
|
|
107
|
+
|
|
108
|
+
Review 通过脚本入口执行该校验,例如:
|
|
109
|
+
|
|
110
|
+
```text
|
|
111
|
+
python harness_review.py validate-codegraph-identity --input <codegraph-response.json> \
|
|
112
|
+
--execution-root <actual-worktree-root> --repository-id <repository-id> \
|
|
113
|
+
--head <HEAD> [--worktree-id <worktree-id>]
|
|
114
|
+
```
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: harness-run
|
|
3
3
|
description: 按变更簇执行 TDD 编码循环(RED→GREEN→REFACTOR→编译验证),逐变更簇实现计划中的任务。使用场景:开始编码、实现功能、写代码、TDD 编码
|
|
4
4
|
---
|
|
5
|
-
<!-- generated by harness_deploy.py; core=
|
|
5
|
+
<!-- generated by harness_deploy.py; core=96a3184db7f7f75e; overlay=java; agent=cursor; do not edit -->
|
|
6
6
|
# harness-run — 需求编码
|
|
7
7
|
|
|
8
8
|
## Purpose
|
|
@@ -3,7 +3,7 @@ name: harness-submit
|
|
|
3
3
|
description: 最终提交封装:验证→中文 commit→提交/推送;worktree 模式含 --no-ff
|
|
4
4
|
合并回主分支。使用场景:提交代码、commit、push、合并分支、merge to main、完成开发
|
|
5
5
|
---
|
|
6
|
-
<!-- generated by harness_deploy.py; core=
|
|
6
|
+
<!-- generated by harness_deploy.py; core=96a3184db7f7f75e; overlay=java; agent=cursor; do not edit -->
|
|
7
7
|
# harness-submit — 最终提交(含 worktree 合并)
|
|
8
8
|
|
|
9
9
|
## Purpose
|
|
@@ -4,7 +4,7 @@ description: Use when the user asks to synchronize, refresh, or validate Harness
|
|
|
4
4
|
metadata, adapters, knowledge, rules, instruction entrypoints, config origins,
|
|
5
5
|
or CodeGraph status.
|
|
6
6
|
---
|
|
7
|
-
<!-- generated by harness_deploy.py; core=
|
|
7
|
+
<!-- generated by harness_deploy.py; core=96a3184db7f7f75e; overlay=java; agent=cursor; do not edit -->
|
|
8
8
|
# harness-sync
|
|
9
9
|
|
|
10
10
|
## Purpose
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: harness-test
|
|
3
3
|
description: 测试执行:读取场景表,执行单元测试+API接口测试+数据兼容验证,输出测试报告。当用户说'跑测试/验证/跑用例/接口测试/单元测试'时使用
|
|
4
4
|
---
|
|
5
|
-
<!-- generated by harness_deploy.py; core=
|
|
5
|
+
<!-- generated by harness_deploy.py; core=96a3184db7f7f75e; overlay=java; agent=cursor; do not edit -->
|
|
6
6
|
# harness-test — 测试执行
|
|
7
7
|
|
|
8
8
|
## Purpose
|