@hunter-harness/workflow-harness 0.2.4 → 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 +19 -4
- package/harness/bundles/general/claude-code/harness-run/checklist.md +7 -3
- package/harness/bundles/general/claude-code/harness-run/reference.md +4 -10
- package/harness/bundles/general/claude-code/harness-submit/SKILL.md +9 -4
- package/harness/bundles/general/claude-code/harness-submit/checklist.md +10 -1
- package/harness/bundles/general/claude-code/harness-sync/SKILL.md +5 -3
- package/harness/bundles/general/claude-code/harness-test/SKILL.md +18 -3
- package/harness/bundles/general/claude-code/harness-test/checklist.md +4 -0
- package/harness/bundles/general/claude-code/harness-test/reference.md +5 -5
- package/harness/bundles/general/claude-code/protocols/archive-report-protocol.md +2 -4
- package/harness/bundles/general/claude-code/protocols/ledger-protocol.md +8 -16
- 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_ledger.py +111 -5
- package/harness/bundles/general/claude-code/scripts/harness_preflight.py +46 -3
- package/harness/bundles/general/claude-code/scripts/harness_profile.py +31 -3
- package/harness/bundles/general/claude-code/scripts/harness_state.py +148 -0
- package/harness/bundles/general/claude-code/scripts/harness_test_guard.py +502 -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 +19 -4
- package/harness/bundles/general/codebuddy/harness-run/checklist.md +7 -3
- package/harness/bundles/general/codebuddy/harness-run/reference.md +4 -10
- package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +9 -4
- package/harness/bundles/general/codebuddy/harness-submit/checklist.md +10 -1
- package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +5 -3
- package/harness/bundles/general/codebuddy/harness-test/SKILL.md +18 -3
- package/harness/bundles/general/codebuddy/harness-test/checklist.md +4 -0
- package/harness/bundles/general/codebuddy/harness-test/reference.md +5 -5
- package/harness/bundles/general/codebuddy/protocols/archive-report-protocol.md +2 -4
- package/harness/bundles/general/codebuddy/protocols/ledger-protocol.md +8 -16
- 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_ledger.py +111 -5
- package/harness/bundles/general/codebuddy/scripts/harness_preflight.py +46 -3
- package/harness/bundles/general/codebuddy/scripts/harness_profile.py +31 -3
- package/harness/bundles/general/codebuddy/scripts/harness_state.py +148 -0
- package/harness/bundles/general/codebuddy/scripts/harness_test_guard.py +502 -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 +19 -4
- package/harness/bundles/general/codex/harness-run/checklist.md +7 -3
- package/harness/bundles/general/codex/harness-run/reference.md +4 -10
- package/harness/bundles/general/codex/harness-submit/SKILL.md +9 -4
- package/harness/bundles/general/codex/harness-submit/checklist.md +10 -1
- package/harness/bundles/general/codex/harness-sync/SKILL.md +5 -3
- package/harness/bundles/general/codex/harness-test/SKILL.md +18 -3
- package/harness/bundles/general/codex/harness-test/checklist.md +4 -0
- package/harness/bundles/general/codex/harness-test/reference.md +5 -5
- package/harness/bundles/general/codex/protocols/archive-report-protocol.md +2 -4
- package/harness/bundles/general/codex/protocols/ledger-protocol.md +8 -16
- 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_ledger.py +111 -5
- package/harness/bundles/general/codex/scripts/harness_preflight.py +46 -3
- package/harness/bundles/general/codex/scripts/harness_profile.py +31 -3
- package/harness/bundles/general/codex/scripts/harness_state.py +148 -0
- package/harness/bundles/general/codex/scripts/harness_test_guard.py +502 -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 +19 -4
- package/harness/bundles/general/cursor/harness-run/checklist.md +7 -3
- package/harness/bundles/general/cursor/harness-run/reference.md +4 -10
- package/harness/bundles/general/cursor/harness-submit/SKILL.md +9 -4
- package/harness/bundles/general/cursor/harness-submit/checklist.md +10 -1
- package/harness/bundles/general/cursor/harness-sync/SKILL.md +5 -3
- package/harness/bundles/general/cursor/harness-test/SKILL.md +18 -3
- package/harness/bundles/general/cursor/harness-test/checklist.md +4 -0
- package/harness/bundles/general/cursor/harness-test/reference.md +5 -5
- package/harness/bundles/general/cursor/protocols/archive-report-protocol.md +2 -4
- package/harness/bundles/general/cursor/protocols/ledger-protocol.md +8 -16
- 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_ledger.py +111 -5
- package/harness/bundles/general/cursor/scripts/harness_preflight.py +46 -3
- package/harness/bundles/general/cursor/scripts/harness_profile.py +31 -3
- package/harness/bundles/general/cursor/scripts/harness_state.py +148 -0
- package/harness/bundles/general/cursor/scripts/harness_test_guard.py +502 -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 +22 -5
- package/harness/bundles/java/claude-code/harness-run/reference.md +1 -1
- package/harness/bundles/java/claude-code/harness-submit/SKILL.md +9 -4
- package/harness/bundles/java/claude-code/harness-submit/checklist.md +10 -1
- package/harness/bundles/java/claude-code/harness-sync/SKILL.md +5 -3
- package/harness/bundles/java/claude-code/harness-test/SKILL.md +20 -3
- package/harness/bundles/java/claude-code/harness-test/reference.md +1 -1
- package/harness/bundles/java/claude-code/protocols/archive-report-protocol.md +2 -4
- package/harness/bundles/java/claude-code/protocols/ledger-protocol.md +8 -16
- 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_ledger.py +111 -5
- package/harness/bundles/java/claude-code/scripts/harness_preflight.py +46 -3
- package/harness/bundles/java/claude-code/scripts/harness_profile.py +31 -3
- package/harness/bundles/java/claude-code/scripts/harness_state.py +148 -0
- package/harness/bundles/java/claude-code/scripts/harness_test_guard.py +502 -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 +22 -5
- package/harness/bundles/java/codebuddy/harness-run/reference.md +1 -1
- package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +9 -4
- package/harness/bundles/java/codebuddy/harness-submit/checklist.md +10 -1
- package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +5 -3
- package/harness/bundles/java/codebuddy/harness-test/SKILL.md +20 -3
- package/harness/bundles/java/codebuddy/harness-test/reference.md +1 -1
- package/harness/bundles/java/codebuddy/protocols/archive-report-protocol.md +2 -4
- package/harness/bundles/java/codebuddy/protocols/ledger-protocol.md +8 -16
- 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_ledger.py +111 -5
- package/harness/bundles/java/codebuddy/scripts/harness_preflight.py +46 -3
- package/harness/bundles/java/codebuddy/scripts/harness_profile.py +31 -3
- package/harness/bundles/java/codebuddy/scripts/harness_state.py +148 -0
- package/harness/bundles/java/codebuddy/scripts/harness_test_guard.py +502 -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 +22 -5
- package/harness/bundles/java/codex/harness-run/reference.md +1 -1
- package/harness/bundles/java/codex/harness-submit/SKILL.md +9 -4
- package/harness/bundles/java/codex/harness-submit/checklist.md +10 -1
- package/harness/bundles/java/codex/harness-sync/SKILL.md +5 -3
- package/harness/bundles/java/codex/harness-test/SKILL.md +20 -3
- package/harness/bundles/java/codex/harness-test/reference.md +1 -1
- package/harness/bundles/java/codex/protocols/archive-report-protocol.md +2 -4
- package/harness/bundles/java/codex/protocols/ledger-protocol.md +8 -16
- 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_ledger.py +111 -5
- package/harness/bundles/java/codex/scripts/harness_preflight.py +46 -3
- package/harness/bundles/java/codex/scripts/harness_profile.py +31 -3
- package/harness/bundles/java/codex/scripts/harness_state.py +148 -0
- package/harness/bundles/java/codex/scripts/harness_test_guard.py +502 -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 +22 -5
- package/harness/bundles/java/cursor/harness-run/reference.md +1 -1
- package/harness/bundles/java/cursor/harness-submit/SKILL.md +9 -4
- package/harness/bundles/java/cursor/harness-submit/checklist.md +10 -1
- package/harness/bundles/java/cursor/harness-sync/SKILL.md +5 -3
- package/harness/bundles/java/cursor/harness-test/SKILL.md +20 -3
- package/harness/bundles/java/cursor/harness-test/reference.md +1 -1
- package/harness/bundles/java/cursor/protocols/archive-report-protocol.md +2 -4
- package/harness/bundles/java/cursor/protocols/ledger-protocol.md +8 -16
- 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_ledger.py +111 -5
- package/harness/bundles/java/cursor/scripts/harness_preflight.py +46 -3
- package/harness/bundles/java/cursor/scripts/harness_profile.py +31 -3
- package/harness/bundles/java/cursor/scripts/harness_state.py +148 -0
- package/harness/bundles/java/cursor/scripts/harness_test_guard.py +502 -0
- package/harness/manifests/general/claude-code.json +33 -29
- package/harness/manifests/general/codebuddy.json +33 -29
- package/harness/manifests/general/codex.json +33 -29
- package/harness/manifests/general/cursor.json +33 -29
- package/harness/manifests/java/claude-code.json +33 -29
- package/harness/manifests/java/codebuddy.json +33 -29
- package/harness/manifests/java/codex.json +33 -29
- package/harness/manifests/java/cursor.json +33 -29
- package/hunter-workflow-family.json +2 -2
- package/package.json +1 -1
|
@@ -40,7 +40,9 @@ BROAD_SCOPES = frozenset({"module", "module-am", "full"})
|
|
|
40
40
|
|
|
41
41
|
# --- Ledger v2 (cluster 2) ---
|
|
42
42
|
LEDGER_VERSION = "harness-ledger-2"
|
|
43
|
-
DIFF_HASH_VERSION = "content-changeset-
|
|
43
|
+
DIFF_HASH_VERSION = "content-changeset-2"
|
|
44
|
+
TEST_TRACKING_REL = Path("evidence") / "test-tracking.json"
|
|
45
|
+
TEST_TRACKING_REASONS = frozenset({"tdd-created", "stale-test-repair", "test-updated"})
|
|
44
46
|
# Coverage lattice: a recorded verification's coverage rank must meet the
|
|
45
47
|
# verification's required rank. Prevents incremental evidence from satisfying
|
|
46
48
|
# a module/full gate (UT-015 / API-005).
|
|
@@ -168,7 +170,88 @@ def _changed_paths(repo_root: Path, base: str | None) -> tuple[str, list[str]]:
|
|
|
168
170
|
return base, sorted(paths)
|
|
169
171
|
|
|
170
172
|
|
|
171
|
-
def
|
|
173
|
+
def _inside(path: Path, root: Path) -> bool:
|
|
174
|
+
try:
|
|
175
|
+
path.relative_to(root)
|
|
176
|
+
return True
|
|
177
|
+
except ValueError:
|
|
178
|
+
return False
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _tracked_test_contents(
|
|
182
|
+
repo_root: Path,
|
|
183
|
+
change_dir: Path | str | None,
|
|
184
|
+
) -> tuple[dict[str, bytes], str | None]:
|
|
185
|
+
"""Load exact test paths recorded by harness_test_guard.
|
|
186
|
+
|
|
187
|
+
The ledger validates the same security-critical manifest fields again so a
|
|
188
|
+
hand-edited manifest cannot silently widen the fingerprint or reuse stale
|
|
189
|
+
evidence. Missing manifests remain backward-compatible and contribute no
|
|
190
|
+
additional paths.
|
|
191
|
+
"""
|
|
192
|
+
if change_dir is None:
|
|
193
|
+
return {}, None
|
|
194
|
+
candidate = Path(change_dir)
|
|
195
|
+
if not candidate.is_absolute():
|
|
196
|
+
candidate = repo_root / candidate
|
|
197
|
+
change_root = candidate.resolve()
|
|
198
|
+
if not _inside(change_root, repo_root):
|
|
199
|
+
raise ValueError("TEST_TRACKING_CHANGE_DIR_OUTSIDE_PROJECT")
|
|
200
|
+
manifest_path = (change_root / TEST_TRACKING_REL).resolve()
|
|
201
|
+
if not _inside(manifest_path, change_root):
|
|
202
|
+
raise ValueError("TEST_TRACKING_MANIFEST_OUTSIDE_CHANGE")
|
|
203
|
+
if not manifest_path.is_file():
|
|
204
|
+
return {}, None
|
|
205
|
+
try:
|
|
206
|
+
manifest = json.loads(manifest_path.read_text(encoding="utf-8-sig"))
|
|
207
|
+
except (OSError, json.JSONDecodeError) as exc:
|
|
208
|
+
raise ValueError(f"TEST_TRACKING_MANIFEST_INVALID: {exc}") from exc
|
|
209
|
+
if (
|
|
210
|
+
not isinstance(manifest, dict)
|
|
211
|
+
or manifest.get("schemaVersion") != 1
|
|
212
|
+
or manifest.get("mode") != "force-track-touched"
|
|
213
|
+
or manifest.get("projectRoot") != str(repo_root)
|
|
214
|
+
):
|
|
215
|
+
raise ValueError("TEST_TRACKING_MANIFEST_INVALID")
|
|
216
|
+
entries = manifest.get("files")
|
|
217
|
+
if not isinstance(entries, list) or not entries:
|
|
218
|
+
raise ValueError("TEST_TRACKING_MANIFEST_INVALID: EMPTY_FILES")
|
|
219
|
+
|
|
220
|
+
contents: dict[str, bytes] = {}
|
|
221
|
+
for item in entries:
|
|
222
|
+
if not isinstance(item, dict):
|
|
223
|
+
raise ValueError("TEST_TRACKING_MANIFEST_INVALID")
|
|
224
|
+
rel = item.get("path")
|
|
225
|
+
expected_hash = item.get("sha256")
|
|
226
|
+
if (
|
|
227
|
+
not isinstance(rel, str)
|
|
228
|
+
or not rel
|
|
229
|
+
or not isinstance(expected_hash, str)
|
|
230
|
+
or item.get("reason") not in TEST_TRACKING_REASONS
|
|
231
|
+
or type(item.get("ignored")) is not bool
|
|
232
|
+
or type(item.get("trackedBefore")) is not bool
|
|
233
|
+
):
|
|
234
|
+
raise ValueError("TEST_TRACKING_MANIFEST_INVALID")
|
|
235
|
+
raw_path = Path(rel)
|
|
236
|
+
resolved = (repo_root / raw_path).resolve()
|
|
237
|
+
if raw_path.is_absolute() or not _inside(resolved, repo_root):
|
|
238
|
+
raise ValueError(f"TEST_TRACKING_PATH_OUTSIDE_PROJECT: {rel}")
|
|
239
|
+
normalized = resolved.relative_to(repo_root).as_posix()
|
|
240
|
+
if normalized != rel or not resolved.is_file():
|
|
241
|
+
raise ValueError(f"TEST_TRACKING_FILE_INVALID: {rel}")
|
|
242
|
+
content = resolved.read_bytes()
|
|
243
|
+
actual_hash = "sha256:" + hashlib.sha256(content).hexdigest()
|
|
244
|
+
if actual_hash != expected_hash:
|
|
245
|
+
raise ValueError(f"TEST_TRACKING_HASH_DRIFT: {rel}")
|
|
246
|
+
contents[rel] = content
|
|
247
|
+
return {rel: contents[rel] for rel in sorted(contents)}, str(manifest_path)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def compute_diff_hash(
|
|
251
|
+
repo_root: Path,
|
|
252
|
+
base: str | None = None,
|
|
253
|
+
change_dir: Path | str | None = None,
|
|
254
|
+
) -> tuple[str, dict[str, Any]]:
|
|
172
255
|
"""Byte-level, commit-invariant diff hash (cluster 2).
|
|
173
256
|
|
|
174
257
|
Content-based change set: every file whose working-tree content differs
|
|
@@ -179,6 +262,8 @@ def compute_diff_hash(repo_root: Path, base: str | None = None) -> tuple[str, di
|
|
|
179
262
|
"""
|
|
180
263
|
repo_root = Path(repo_root).resolve()
|
|
181
264
|
base, paths = _changed_paths(repo_root, base)
|
|
265
|
+
tracked_test_contents, manifest_path = _tracked_test_contents(repo_root, change_dir)
|
|
266
|
+
paths = sorted(set(paths).union(tracked_test_contents))
|
|
182
267
|
digest = hashlib.sha256()
|
|
183
268
|
digest.update(DIFF_HASH_VERSION.encode("utf-8"))
|
|
184
269
|
digest.update(b"\x00")
|
|
@@ -187,7 +272,12 @@ def compute_diff_hash(repo_root: Path, base: str | None = None) -> tuple[str, di
|
|
|
187
272
|
digest.update(len(path_bytes).to_bytes(4, "big"))
|
|
188
273
|
digest.update(path_bytes)
|
|
189
274
|
abs_path = repo_root / rel
|
|
190
|
-
if
|
|
275
|
+
if rel in tracked_test_contents:
|
|
276
|
+
content = tracked_test_contents[rel]
|
|
277
|
+
digest.update(b"\x01")
|
|
278
|
+
digest.update(len(content).to_bytes(8, "big"))
|
|
279
|
+
digest.update(content)
|
|
280
|
+
elif abs_path.is_file():
|
|
191
281
|
content = abs_path.read_bytes()
|
|
192
282
|
digest.update(b"\x01")
|
|
193
283
|
digest.update(len(content).to_bytes(8, "big"))
|
|
@@ -196,6 +286,14 @@ def compute_diff_hash(repo_root: Path, base: str | None = None) -> tuple[str, di
|
|
|
196
286
|
# Deleted since base: record absence with no content.
|
|
197
287
|
digest.update(b"\x00")
|
|
198
288
|
digest.update((0).to_bytes(8, "big"))
|
|
289
|
+
for rel, verified_content in tracked_test_contents.items():
|
|
290
|
+
path = repo_root / rel
|
|
291
|
+
try:
|
|
292
|
+
current_content = path.read_bytes()
|
|
293
|
+
except OSError as exc:
|
|
294
|
+
raise ValueError(f"TEST_TRACKING_HASH_DRIFT: {rel}") from exc
|
|
295
|
+
if current_content != verified_content:
|
|
296
|
+
raise ValueError(f"TEST_TRACKING_HASH_DRIFT: {rel}")
|
|
199
297
|
try:
|
|
200
298
|
head = _git_bytes(["rev-parse", "HEAD"], repo_root).decode("utf-8", "replace").strip()
|
|
201
299
|
except RuntimeError:
|
|
@@ -205,6 +303,8 @@ def compute_diff_hash(repo_root: Path, base: str | None = None) -> tuple[str, di
|
|
|
205
303
|
"fileCount": len(paths),
|
|
206
304
|
"base": base,
|
|
207
305
|
"head": head or None,
|
|
306
|
+
"trackedTestFileCount": len(tracked_test_contents),
|
|
307
|
+
"testTrackingManifest": manifest_path,
|
|
208
308
|
}
|
|
209
309
|
return f"sha256:{digest.hexdigest()}", meta
|
|
210
310
|
|
|
@@ -778,9 +878,10 @@ def cmd_diff_hash(args: argparse.Namespace) -> int:
|
|
|
778
878
|
repo_raw = getattr(args, "repo", None)
|
|
779
879
|
repo = Path(repo_raw).expanduser().resolve() if repo_raw else Path.cwd().resolve()
|
|
780
880
|
base = getattr(args, "base", None)
|
|
881
|
+
change_dir = getattr(args, "change_dir", None)
|
|
781
882
|
try:
|
|
782
|
-
diff_hash, meta = compute_diff_hash(repo, base=base)
|
|
783
|
-
except (OSError, RuntimeError) as exc:
|
|
883
|
+
diff_hash, meta = compute_diff_hash(repo, base=base, change_dir=change_dir)
|
|
884
|
+
except (OSError, RuntimeError, ValueError) as exc:
|
|
784
885
|
return emit_error(str(exc), as_json=as_json)
|
|
785
886
|
payload = {"ok": True, "action": "diff-hash", "diffHash": diff_hash}
|
|
786
887
|
payload.update(meta)
|
|
@@ -922,6 +1023,11 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
922
1023
|
)
|
|
923
1024
|
p_diff.add_argument("--repo", default=None, help="repo root (default: cwd)")
|
|
924
1025
|
p_diff.add_argument("--base", default=None, help="base commit (default: root commit)")
|
|
1026
|
+
p_diff.add_argument(
|
|
1027
|
+
"--change-dir",
|
|
1028
|
+
default=None,
|
|
1029
|
+
help="change directory whose evidence/test-tracking.json contributes ignored tests",
|
|
1030
|
+
)
|
|
925
1031
|
p_diff.set_defaults(func=cmd_diff_hash)
|
|
926
1032
|
|
|
927
1033
|
return parser
|
|
@@ -313,7 +313,23 @@ def _parse_scalar(val: str) -> Any:
|
|
|
313
313
|
return val
|
|
314
314
|
|
|
315
315
|
|
|
316
|
-
def
|
|
316
|
+
def _resolve_agents_root(skills_root: Path, agents_root: Path | None) -> tuple[Path | None, str | None]:
|
|
317
|
+
"""Resolve agent definitions without confusing a skills root with its sibling."""
|
|
318
|
+
if agents_root is not None:
|
|
319
|
+
return agents_root.resolve(), None
|
|
320
|
+
if skills_root.name == "skills" and skills_root.parent.name in (".claude", ".codebuddy"):
|
|
321
|
+
return (skills_root.parent / "agents").resolve(), None
|
|
322
|
+
if skills_root.name == "skills" and skills_root.parent.name in (".agents", ".cursor"):
|
|
323
|
+
return None, "CUSTOM_AGENTS_UNSUPPORTED"
|
|
324
|
+
return (skills_root / "agents").resolve(), None
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def cmd_check_agents(
|
|
328
|
+
skills_root: Path,
|
|
329
|
+
agent: str,
|
|
330
|
+
*,
|
|
331
|
+
agents_root: Path | None = None,
|
|
332
|
+
) -> dict[str, Any]:
|
|
317
333
|
skills_root = skills_root.resolve()
|
|
318
334
|
agent_name = agent.strip()
|
|
319
335
|
if not agent_name:
|
|
@@ -325,9 +341,23 @@ def cmd_check_agents(skills_root: Path, agent: str) -> dict[str, Any]:
|
|
|
325
341
|
"reason": "agent name is empty",
|
|
326
342
|
}
|
|
327
343
|
|
|
344
|
+
resolved_agents_root, capability_error = _resolve_agents_root(skills_root, agents_root)
|
|
345
|
+
if capability_error is not None:
|
|
346
|
+
return {
|
|
347
|
+
"ok": True,
|
|
348
|
+
"action": "check-agents",
|
|
349
|
+
"agent": agent_name,
|
|
350
|
+
"usable": False,
|
|
351
|
+
"definitionValid": False,
|
|
352
|
+
"runtimeSupported": False,
|
|
353
|
+
"reasonCode": capability_error,
|
|
354
|
+
"reason": "current adapter runtime does not support custom agent definitions; use main-session fallback",
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
assert resolved_agents_root is not None
|
|
328
358
|
# Accept bare name or with .md
|
|
329
359
|
filename = agent_name if agent_name.endswith(".md") else f"{agent_name}.md"
|
|
330
|
-
agent_path =
|
|
360
|
+
agent_path = resolved_agents_root / filename
|
|
331
361
|
|
|
332
362
|
if not agent_path.is_file():
|
|
333
363
|
return {
|
|
@@ -335,7 +365,11 @@ def cmd_check_agents(skills_root: Path, agent: str) -> dict[str, Any]:
|
|
|
335
365
|
"action": "check-agents",
|
|
336
366
|
"agent": agent_name,
|
|
337
367
|
"path": str(agent_path),
|
|
368
|
+
"agentsRoot": str(resolved_agents_root),
|
|
338
369
|
"usable": False,
|
|
370
|
+
"definitionValid": False,
|
|
371
|
+
"runtimeSupported": True,
|
|
372
|
+
"reasonCode": "AGENT_DEFINITION_NOT_FOUND",
|
|
339
373
|
"reason": f"agent file not found: {agent_path}",
|
|
340
374
|
}
|
|
341
375
|
|
|
@@ -390,7 +424,11 @@ def cmd_check_agents(skills_root: Path, agent: str) -> dict[str, Any]:
|
|
|
390
424
|
"action": "check-agents",
|
|
391
425
|
"agent": agent_name,
|
|
392
426
|
"path": str(agent_path),
|
|
427
|
+
"agentsRoot": str(resolved_agents_root),
|
|
393
428
|
"usable": True,
|
|
429
|
+
"definitionValid": True,
|
|
430
|
+
"runtimeSupported": True,
|
|
431
|
+
"reasonCode": "READY",
|
|
394
432
|
"reason": "agent file exists; frontmatter parsed; tools declared",
|
|
395
433
|
"name": name_field,
|
|
396
434
|
"tools": tools,
|
|
@@ -431,6 +469,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
431
469
|
|
|
432
470
|
p_agents = sub.add_parser("check-agents", help="Validate agent definition usability")
|
|
433
471
|
p_agents.add_argument("--skills-root", required=True, type=Path)
|
|
472
|
+
p_agents.add_argument("--agents-root", type=Path)
|
|
434
473
|
p_agents.add_argument("--agent", required=True)
|
|
435
474
|
p_agents.add_argument("--json", action="store_true")
|
|
436
475
|
|
|
@@ -459,7 +498,11 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
459
498
|
)
|
|
460
499
|
return emit_json(result, ok=bool(result.get("ok", False)))
|
|
461
500
|
if args.command == "check-agents":
|
|
462
|
-
result = cmd_check_agents(
|
|
501
|
+
result = cmd_check_agents(
|
|
502
|
+
args.skills_root,
|
|
503
|
+
args.agent,
|
|
504
|
+
agents_root=args.agents_root,
|
|
505
|
+
)
|
|
463
506
|
return emit_json(result, ok=True)
|
|
464
507
|
|
|
465
508
|
parser.error(f"unknown command: {args.command}")
|
|
@@ -223,8 +223,8 @@ def _java_commands(reactor_modules: list[str], pom_hash: str) -> dict[str, Any]:
|
|
|
223
223
|
"basis": dict(basis),
|
|
224
224
|
},
|
|
225
225
|
"package": {
|
|
226
|
-
"command": "mvn -f pom.xml package '-Dmaven.test.skip=true'",
|
|
227
|
-
"argvTemplate": ["mvn", "-f", "pom.xml", "package", "-Dmaven.test.skip=true"],
|
|
226
|
+
"command": "mvn -f pom.xml clean package '-Dmaven.test.skip=true'",
|
|
227
|
+
"argvTemplate": ["mvn", "-f", "pom.xml", "clean", "package", "-Dmaven.test.skip=true"],
|
|
228
228
|
"scope": "module",
|
|
229
229
|
"inputs": _cmd_inputs(reactor_modules, pom=True, main=True, test=True),
|
|
230
230
|
"coverage": "package",
|
|
@@ -324,6 +324,13 @@ def empty_profile_skeleton(excluded: tuple[str, ...] | list[str]) -> dict[str, A
|
|
|
324
324
|
"knownPreexistingErrors": [],
|
|
325
325
|
"shellQuirks": [],
|
|
326
326
|
"fingerprint": {"mvnVersion": "", "nodeVersion": "", "pomHash": ""},
|
|
327
|
+
"testTracking": {
|
|
328
|
+
"source": "detected",
|
|
329
|
+
"mode": "force-track-touched",
|
|
330
|
+
"paths": [],
|
|
331
|
+
"staleTestPolicy": "safe-repair",
|
|
332
|
+
"forbidTemporaryExclusion": True,
|
|
333
|
+
},
|
|
327
334
|
}
|
|
328
335
|
|
|
329
336
|
|
|
@@ -357,6 +364,9 @@ def _merge_user_overrides(
|
|
|
357
364
|
profile["knownPreexistingErrors"] = list(existing["knownPreexistingErrors"])
|
|
358
365
|
if isinstance(existing.get("shellQuirks"), list):
|
|
359
366
|
profile["shellQuirks"] = list(existing["shellQuirks"])
|
|
367
|
+
test_tracking = existing.get("testTracking")
|
|
368
|
+
if isinstance(test_tracking, dict) and test_tracking.get("source") == "user":
|
|
369
|
+
profile["testTracking"] = dict(test_tracking)
|
|
360
370
|
# 兼容字段:用户在 v1 配置的 verificationInputs 不保留(无 provenance),
|
|
361
371
|
# 由 commands 重新派生。knownPreexistingErrors/shellQuirks 是人工标注,保留。
|
|
362
372
|
|
|
@@ -392,10 +402,28 @@ def detect(project: Path) -> dict[str, Any]:
|
|
|
392
402
|
reactor_modules = find_reactor_modules(project, excluded)
|
|
393
403
|
if reactor_modules:
|
|
394
404
|
profile["commands"] = _java_commands(reactor_modules, pom_hash)
|
|
405
|
+
profile["testTracking"]["paths"] = sorted(
|
|
406
|
+
"src/test/**" if module == "." else f"{module}/src/test/**"
|
|
407
|
+
for module in reactor_modules
|
|
408
|
+
)
|
|
395
409
|
elif project_type == "node":
|
|
396
410
|
node_cmds = _node_commands(project)
|
|
397
411
|
if node_cmds:
|
|
398
412
|
profile["commands"] = node_cmds
|
|
413
|
+
profile["testTracking"]["paths"] = [
|
|
414
|
+
pattern
|
|
415
|
+
for extension in ("js", "jsx", "ts", "tsx", "mjs", "cjs")
|
|
416
|
+
for pattern in (
|
|
417
|
+
f"apps/*/test/**/*.{extension}",
|
|
418
|
+
f"apps/*/tests/**/*.{extension}",
|
|
419
|
+
f"packages/*/test/**/*.{extension}",
|
|
420
|
+
f"packages/*/tests/**/*.{extension}",
|
|
421
|
+
f"test/**/*.{extension}",
|
|
422
|
+
f"tests/**/*.{extension}",
|
|
423
|
+
f"**/*.test.{extension}",
|
|
424
|
+
f"**/*.spec.{extension}",
|
|
425
|
+
)
|
|
426
|
+
]
|
|
399
427
|
|
|
400
428
|
_merge_user_overrides(profile, existing)
|
|
401
429
|
_derive_verification_inputs(profile)
|
|
@@ -719,7 +747,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
719
747
|
if args.command == "check":
|
|
720
748
|
result = check(args.project)
|
|
721
749
|
sys.stdout.write(json.dumps(result, ensure_ascii=False, indent=2) + "\n")
|
|
722
|
-
return 0 if not result.get("stale") else
|
|
750
|
+
return 0 if result.get("ok") and not result.get("stale") else 1
|
|
723
751
|
if args.command == "validate":
|
|
724
752
|
profile = load_profile(args.project.resolve())
|
|
725
753
|
if profile is None:
|
|
@@ -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())
|