@hunter-harness/workflow-harness 0.2.55 → 0.2.56
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/README.md +3 -3
- package/harness/bundles/general/claude-code/contracts/workflow-policy.json +1 -1
- package/harness/bundles/general/claude-code/harness-archive/SKILL.md +21 -33
- package/harness/bundles/general/claude-code/harness-archive/checklist.md +6 -10
- 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 +7 -3
- package/harness/bundles/general/claude-code/harness-review/SKILL.md +6 -4
- package/harness/bundles/general/claude-code/harness-run/SKILL.md +7 -5
- package/harness/bundles/general/claude-code/harness-run/checklist.md +1 -1
- package/harness/bundles/general/claude-code/harness-run/reference.md +2 -2
- package/harness/bundles/general/claude-code/harness-submit/SKILL.md +6 -6
- package/harness/bundles/general/claude-code/harness-submit/checklist.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 +6 -6
- package/harness/bundles/general/claude-code/harness-test/checklist.md +1 -1
- package/harness/bundles/general/claude-code/harness-test/reference.md +2 -2
- package/harness/bundles/general/claude-code/protocols/ledger-protocol.md +3 -3
- package/harness/bundles/general/claude-code/scripts/harness_archive.py +276 -67
- package/harness/bundles/general/claude-code/scripts/harness_context.py +165 -41
- package/harness/bundles/general/claude-code/scripts/harness_events.py +46 -4
- package/harness/bundles/general/claude-code/scripts/harness_events_sync.py +108 -3
- package/harness/bundles/general/claude-code/scripts/harness_fixback.py +189 -1
- package/harness/bundles/general/claude-code/scripts/harness_gate.py +188 -2
- package/harness/bundles/general/claude-code/scripts/harness_ledger.py +193 -11
- package/harness/bundles/general/claude-code/scripts/harness_plan_finalize.py +80 -0
- package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/general/codebuddy/README.md +3 -3
- package/harness/bundles/general/codebuddy/contracts/workflow-policy.json +1 -1
- package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +21 -33
- package/harness/bundles/general/codebuddy/harness-archive/checklist.md +6 -10
- 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 +7 -3
- package/harness/bundles/general/codebuddy/harness-review/SKILL.md +6 -4
- package/harness/bundles/general/codebuddy/harness-run/SKILL.md +7 -5
- package/harness/bundles/general/codebuddy/harness-run/checklist.md +1 -1
- package/harness/bundles/general/codebuddy/harness-run/reference.md +2 -2
- package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +6 -6
- package/harness/bundles/general/codebuddy/harness-submit/checklist.md +1 -1
- package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-test/SKILL.md +6 -6
- package/harness/bundles/general/codebuddy/harness-test/checklist.md +1 -1
- package/harness/bundles/general/codebuddy/harness-test/reference.md +2 -2
- package/harness/bundles/general/codebuddy/protocols/ledger-protocol.md +3 -3
- package/harness/bundles/general/codebuddy/scripts/harness_archive.py +276 -67
- package/harness/bundles/general/codebuddy/scripts/harness_context.py +165 -41
- package/harness/bundles/general/codebuddy/scripts/harness_events.py +46 -4
- package/harness/bundles/general/codebuddy/scripts/harness_events_sync.py +108 -3
- package/harness/bundles/general/codebuddy/scripts/harness_fixback.py +189 -1
- package/harness/bundles/general/codebuddy/scripts/harness_gate.py +188 -2
- package/harness/bundles/general/codebuddy/scripts/harness_ledger.py +193 -11
- package/harness/bundles/general/codebuddy/scripts/harness_plan_finalize.py +80 -0
- package/harness/bundles/general/codex/.harness-build.json +1 -1
- package/harness/bundles/general/codex/README.md +3 -3
- package/harness/bundles/general/codex/contracts/workflow-policy.json +1 -1
- package/harness/bundles/general/codex/harness-archive/SKILL.md +21 -33
- package/harness/bundles/general/codex/harness-archive/checklist.md +6 -10
- 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 +7 -3
- package/harness/bundles/general/codex/harness-review/SKILL.md +4 -4
- package/harness/bundles/general/codex/harness-run/SKILL.md +7 -5
- package/harness/bundles/general/codex/harness-run/checklist.md +1 -1
- package/harness/bundles/general/codex/harness-run/reference.md +2 -2
- package/harness/bundles/general/codex/harness-submit/SKILL.md +6 -6
- package/harness/bundles/general/codex/harness-submit/checklist.md +1 -1
- package/harness/bundles/general/codex/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-test/SKILL.md +6 -6
- package/harness/bundles/general/codex/harness-test/checklist.md +1 -1
- package/harness/bundles/general/codex/harness-test/reference.md +2 -2
- package/harness/bundles/general/codex/protocols/ledger-protocol.md +3 -3
- package/harness/bundles/general/codex/scripts/harness_archive.py +276 -67
- package/harness/bundles/general/codex/scripts/harness_context.py +165 -41
- package/harness/bundles/general/codex/scripts/harness_events.py +46 -4
- package/harness/bundles/general/codex/scripts/harness_events_sync.py +108 -3
- package/harness/bundles/general/codex/scripts/harness_fixback.py +189 -1
- package/harness/bundles/general/codex/scripts/harness_gate.py +188 -2
- package/harness/bundles/general/codex/scripts/harness_ledger.py +193 -11
- package/harness/bundles/general/codex/scripts/harness_plan_finalize.py +80 -0
- package/harness/bundles/general/cursor/.harness-build.json +1 -1
- package/harness/bundles/general/cursor/README.md +3 -3
- package/harness/bundles/general/cursor/contracts/workflow-policy.json +1 -1
- package/harness/bundles/general/cursor/harness-archive/SKILL.md +21 -33
- package/harness/bundles/general/cursor/harness-archive/checklist.md +6 -10
- 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 +7 -3
- package/harness/bundles/general/cursor/harness-review/SKILL.md +4 -4
- package/harness/bundles/general/cursor/harness-run/SKILL.md +7 -5
- package/harness/bundles/general/cursor/harness-run/checklist.md +1 -1
- package/harness/bundles/general/cursor/harness-run/reference.md +2 -2
- package/harness/bundles/general/cursor/harness-submit/SKILL.md +6 -6
- package/harness/bundles/general/cursor/harness-submit/checklist.md +1 -1
- package/harness/bundles/general/cursor/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-test/SKILL.md +6 -6
- package/harness/bundles/general/cursor/harness-test/checklist.md +1 -1
- package/harness/bundles/general/cursor/harness-test/reference.md +2 -2
- package/harness/bundles/general/cursor/protocols/ledger-protocol.md +3 -3
- package/harness/bundles/general/cursor/scripts/harness_archive.py +276 -67
- package/harness/bundles/general/cursor/scripts/harness_context.py +165 -41
- package/harness/bundles/general/cursor/scripts/harness_events.py +46 -4
- package/harness/bundles/general/cursor/scripts/harness_events_sync.py +108 -3
- package/harness/bundles/general/cursor/scripts/harness_fixback.py +189 -1
- package/harness/bundles/general/cursor/scripts/harness_gate.py +188 -2
- package/harness/bundles/general/cursor/scripts/harness_ledger.py +193 -11
- package/harness/bundles/general/cursor/scripts/harness_plan_finalize.py +80 -0
- package/harness/bundles/java/claude-code/.harness-build.json +1 -1
- package/harness/bundles/java/claude-code/README.md +3 -3
- package/harness/bundles/java/claude-code/contracts/workflow-policy.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 +21 -33
- package/harness/bundles/java/claude-code/harness-archive/checklist.md +6 -10
- 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-package/checklist.md +1 -1
- package/harness/bundles/java/claude-code/harness-plan/SKILL.md +7 -3
- package/harness/bundles/java/claude-code/harness-review/SKILL.md +6 -4
- package/harness/bundles/java/claude-code/harness-run/SKILL.md +7 -5
- package/harness/bundles/java/claude-code/harness-run/checklist.md +1 -1
- package/harness/bundles/java/claude-code/harness-run/reference.md +2 -2
- package/harness/bundles/java/claude-code/harness-submit/SKILL.md +6 -6
- package/harness/bundles/java/claude-code/harness-submit/checklist.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 +6 -6
- package/harness/bundles/java/claude-code/harness-test/checklist.md +1 -1
- package/harness/bundles/java/claude-code/harness-test/reference.md +1 -1
- package/harness/bundles/java/claude-code/protocols/ledger-protocol.md +3 -3
- package/harness/bundles/java/claude-code/scripts/harness_archive.py +276 -67
- package/harness/bundles/java/claude-code/scripts/harness_context.py +165 -41
- package/harness/bundles/java/claude-code/scripts/harness_events.py +46 -4
- package/harness/bundles/java/claude-code/scripts/harness_events_sync.py +108 -3
- package/harness/bundles/java/claude-code/scripts/harness_fixback.py +189 -1
- package/harness/bundles/java/claude-code/scripts/harness_gate.py +188 -2
- package/harness/bundles/java/claude-code/scripts/harness_ledger.py +193 -11
- package/harness/bundles/java/claude-code/scripts/harness_plan_finalize.py +80 -0
- package/harness/bundles/java/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/java/codebuddy/README.md +3 -3
- package/harness/bundles/java/codebuddy/contracts/workflow-policy.json +1 -1
- package/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-archive/SKILL.md +21 -33
- package/harness/bundles/java/codebuddy/harness-archive/checklist.md +6 -10
- 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-package/checklist.md +1 -1
- package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +7 -3
- package/harness/bundles/java/codebuddy/harness-review/SKILL.md +6 -4
- package/harness/bundles/java/codebuddy/harness-run/SKILL.md +7 -5
- package/harness/bundles/java/codebuddy/harness-run/checklist.md +1 -1
- package/harness/bundles/java/codebuddy/harness-run/reference.md +2 -2
- package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +6 -6
- package/harness/bundles/java/codebuddy/harness-submit/checklist.md +1 -1
- package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-test/SKILL.md +6 -6
- package/harness/bundles/java/codebuddy/harness-test/checklist.md +1 -1
- package/harness/bundles/java/codebuddy/harness-test/reference.md +1 -1
- package/harness/bundles/java/codebuddy/protocols/ledger-protocol.md +3 -3
- package/harness/bundles/java/codebuddy/scripts/harness_archive.py +276 -67
- package/harness/bundles/java/codebuddy/scripts/harness_context.py +165 -41
- package/harness/bundles/java/codebuddy/scripts/harness_events.py +46 -4
- package/harness/bundles/java/codebuddy/scripts/harness_events_sync.py +108 -3
- package/harness/bundles/java/codebuddy/scripts/harness_fixback.py +189 -1
- package/harness/bundles/java/codebuddy/scripts/harness_gate.py +188 -2
- package/harness/bundles/java/codebuddy/scripts/harness_ledger.py +193 -11
- package/harness/bundles/java/codebuddy/scripts/harness_plan_finalize.py +80 -0
- package/harness/bundles/java/codex/.harness-build.json +1 -1
- package/harness/bundles/java/codex/README.md +3 -3
- package/harness/bundles/java/codex/contracts/workflow-policy.json +1 -1
- package/harness/bundles/java/codex/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-archive/SKILL.md +21 -33
- package/harness/bundles/java/codex/harness-archive/checklist.md +6 -10
- 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-package/checklist.md +1 -1
- package/harness/bundles/java/codex/harness-plan/SKILL.md +7 -3
- package/harness/bundles/java/codex/harness-review/SKILL.md +4 -4
- package/harness/bundles/java/codex/harness-run/SKILL.md +7 -5
- package/harness/bundles/java/codex/harness-run/checklist.md +1 -1
- package/harness/bundles/java/codex/harness-run/reference.md +2 -2
- package/harness/bundles/java/codex/harness-submit/SKILL.md +6 -6
- package/harness/bundles/java/codex/harness-submit/checklist.md +1 -1
- package/harness/bundles/java/codex/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-test/SKILL.md +6 -6
- package/harness/bundles/java/codex/harness-test/checklist.md +1 -1
- package/harness/bundles/java/codex/harness-test/reference.md +1 -1
- package/harness/bundles/java/codex/protocols/ledger-protocol.md +3 -3
- package/harness/bundles/java/codex/scripts/harness_archive.py +276 -67
- package/harness/bundles/java/codex/scripts/harness_context.py +165 -41
- package/harness/bundles/java/codex/scripts/harness_events.py +46 -4
- package/harness/bundles/java/codex/scripts/harness_events_sync.py +108 -3
- package/harness/bundles/java/codex/scripts/harness_fixback.py +189 -1
- package/harness/bundles/java/codex/scripts/harness_gate.py +188 -2
- package/harness/bundles/java/codex/scripts/harness_ledger.py +193 -11
- package/harness/bundles/java/codex/scripts/harness_plan_finalize.py +80 -0
- package/harness/bundles/java/cursor/.harness-build.json +1 -1
- package/harness/bundles/java/cursor/README.md +3 -3
- package/harness/bundles/java/cursor/contracts/workflow-policy.json +1 -1
- package/harness/bundles/java/cursor/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-archive/SKILL.md +21 -33
- package/harness/bundles/java/cursor/harness-archive/checklist.md +6 -10
- 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-package/checklist.md +1 -1
- package/harness/bundles/java/cursor/harness-plan/SKILL.md +7 -3
- package/harness/bundles/java/cursor/harness-review/SKILL.md +4 -4
- package/harness/bundles/java/cursor/harness-run/SKILL.md +7 -5
- package/harness/bundles/java/cursor/harness-run/checklist.md +1 -1
- package/harness/bundles/java/cursor/harness-run/reference.md +2 -2
- package/harness/bundles/java/cursor/harness-submit/SKILL.md +6 -6
- package/harness/bundles/java/cursor/harness-submit/checklist.md +1 -1
- package/harness/bundles/java/cursor/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-test/SKILL.md +6 -6
- package/harness/bundles/java/cursor/harness-test/checklist.md +1 -1
- package/harness/bundles/java/cursor/harness-test/reference.md +1 -1
- package/harness/bundles/java/cursor/protocols/ledger-protocol.md +3 -3
- package/harness/bundles/java/cursor/scripts/harness_archive.py +276 -67
- package/harness/bundles/java/cursor/scripts/harness_context.py +165 -41
- package/harness/bundles/java/cursor/scripts/harness_events.py +46 -4
- package/harness/bundles/java/cursor/scripts/harness_events_sync.py +108 -3
- package/harness/bundles/java/cursor/scripts/harness_fixback.py +189 -1
- package/harness/bundles/java/cursor/scripts/harness_gate.py +188 -2
- package/harness/bundles/java/cursor/scripts/harness_ledger.py +193 -11
- package/harness/bundles/java/cursor/scripts/harness_plan_finalize.py +80 -0
- package/harness/manifests/general/claude-code.json +30 -30
- package/harness/manifests/general/codebuddy.json +30 -30
- package/harness/manifests/general/codex.json +30 -30
- package/harness/manifests/general/cursor.json +30 -30
- package/harness/manifests/java/claude-code.json +33 -33
- package/harness/manifests/java/codebuddy.json +33 -33
- package/harness/manifests/java/codex.json +33 -33
- package/harness/manifests/java/cursor.json +33 -33
- package/hunter-workflow-family.json +5 -5
- package/package.json +1 -1
|
@@ -145,7 +145,91 @@ def _hash_text(value: str) -> str:
|
|
|
145
145
|
|
|
146
146
|
def command_set_hash(command: str) -> str:
|
|
147
147
|
"""Stable identity for the exact command set represented by one target."""
|
|
148
|
-
return _hash_text(
|
|
148
|
+
return _hash_text(canonical_command(command))
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def canonical_command(command: str) -> str:
|
|
152
|
+
"""Normalize supported launch wrappers without mixing runner notes into identity."""
|
|
153
|
+
value = re.sub(r"\s+", " ", str(command).strip())
|
|
154
|
+
value = re.sub(r"\s*\(safe runner\)\s*$", "", value, flags=re.I)
|
|
155
|
+
value = re.sub(r"^npx(?:\s+--no-install)?\s+", "", value, flags=re.I)
|
|
156
|
+
return value.strip()
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def default_environment_hash(project_root: Path | None = None) -> str:
|
|
160
|
+
payload = {
|
|
161
|
+
"osName": os.name,
|
|
162
|
+
"platform": sys.platform,
|
|
163
|
+
"pythonImplementation": sys.implementation.name,
|
|
164
|
+
"projectFilesystem": "windows" if os.name == "nt" else "posix",
|
|
165
|
+
}
|
|
166
|
+
return _hash_text(json.dumps(payload, sort_keys=True, separators=(",", ":")))
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def default_toolchain_hash(project_root: Path | None = None) -> str:
|
|
170
|
+
root = Path(project_root).resolve() if project_root is not None else None
|
|
171
|
+
manifests: list[dict[str, str]] = []
|
|
172
|
+
if root is not None:
|
|
173
|
+
for name in (
|
|
174
|
+
"package.json", "pyproject.toml", "pom.xml", "build.gradle",
|
|
175
|
+
"build.gradle.kts", "go.mod", "Cargo.toml",
|
|
176
|
+
):
|
|
177
|
+
path = root / name
|
|
178
|
+
if path.is_file():
|
|
179
|
+
manifests.append({
|
|
180
|
+
"name": name,
|
|
181
|
+
"sha256": hashlib.sha256(path.read_bytes()).hexdigest(),
|
|
182
|
+
})
|
|
183
|
+
payload = {
|
|
184
|
+
"python": sys.version.split()[0],
|
|
185
|
+
"implementation": sys.implementation.name,
|
|
186
|
+
"manifests": manifests,
|
|
187
|
+
}
|
|
188
|
+
return _hash_text(json.dumps(payload, sort_keys=True, separators=(",", ":")))
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def frozen_ownership_check(change_dir: Path) -> dict[str, Any]:
|
|
192
|
+
"""Reject evidence writes after a Run capsule's ownership scope changed."""
|
|
193
|
+
try:
|
|
194
|
+
state_root = Path(
|
|
195
|
+
harness_paths.resolve_state_dir_for_contract(change_dir)
|
|
196
|
+
).resolve()
|
|
197
|
+
contract = harness_paths.load_change_contract(change_dir)
|
|
198
|
+
except (OSError, ValueError, json.JSONDecodeError):
|
|
199
|
+
return {"ok": True, "code": "OWNERSHIP_FREEZE_NOT_APPLICABLE"}
|
|
200
|
+
capsule_root = state_root / "runtime" / "phase-context"
|
|
201
|
+
if not capsule_root.is_dir():
|
|
202
|
+
return {"ok": True, "code": "OWNERSHIP_FREEZE_NOT_APPLICABLE"}
|
|
203
|
+
capsules: list[dict[str, Any]] = []
|
|
204
|
+
for path in capsule_root.glob("run-*.json"):
|
|
205
|
+
try:
|
|
206
|
+
item = json.loads(path.read_text(encoding="utf-8-sig"))
|
|
207
|
+
except (OSError, json.JSONDecodeError):
|
|
208
|
+
continue
|
|
209
|
+
if (
|
|
210
|
+
isinstance(item, dict)
|
|
211
|
+
and item.get("phase") == "run"
|
|
212
|
+
and not item.get("closedAt")
|
|
213
|
+
and _nonempty_str(item.get("ownershipHash"))
|
|
214
|
+
):
|
|
215
|
+
capsules.append(item)
|
|
216
|
+
if not capsules:
|
|
217
|
+
return {"ok": True, "code": "OWNERSHIP_FREEZE_NOT_APPLICABLE"}
|
|
218
|
+
capsule = sorted(capsules, key=lambda item: str(item.get("createdAt") or ""))[-1]
|
|
219
|
+
current = ownership_hash(contract)
|
|
220
|
+
if capsule.get("ownershipHash") != current:
|
|
221
|
+
return {
|
|
222
|
+
"ok": False,
|
|
223
|
+
"code": "OWNERSHIP_CHANGED_BEFORE_VERIFICATION",
|
|
224
|
+
"message": (
|
|
225
|
+
"产品范围在 Run 启动后发生变化;请先通过受控范围更新重启 Run,"
|
|
226
|
+
"系统将只失效受影响的验证。"
|
|
227
|
+
),
|
|
228
|
+
"storedOwnershipHash": capsule.get("ownershipHash"),
|
|
229
|
+
"currentOwnershipHash": current,
|
|
230
|
+
"runId": capsule.get("runId"),
|
|
231
|
+
}
|
|
232
|
+
return {"ok": True, "code": "OWNERSHIP_FROZEN"}
|
|
149
233
|
|
|
150
234
|
|
|
151
235
|
def product_tree_hash(project_root: Path | None) -> str | None:
|
|
@@ -284,6 +368,10 @@ def parse_files_manifest(raw: str | None) -> list[str]:
|
|
|
284
368
|
]
|
|
285
369
|
|
|
286
370
|
|
|
371
|
+
class ProjectRootRequiredError(ValueError):
|
|
372
|
+
"""Raised when relative verification inputs have no stable project basis."""
|
|
373
|
+
|
|
374
|
+
|
|
287
375
|
def resolve_input_files(
|
|
288
376
|
files: list[str], project_root: Path | None
|
|
289
377
|
) -> list[str]:
|
|
@@ -313,7 +401,15 @@ def input_files_from_args(args: argparse.Namespace) -> tuple[list[str], Path | N
|
|
|
313
401
|
manifest = parse_files_manifest(getattr(args, "files_from", None))
|
|
314
402
|
if files and manifest:
|
|
315
403
|
raise ValueError("use only one of --files and --files-from")
|
|
316
|
-
|
|
404
|
+
selected = files or manifest
|
|
405
|
+
if project_root is None and any(
|
|
406
|
+
not Path(raw).expanduser().is_absolute() for raw in selected
|
|
407
|
+
):
|
|
408
|
+
raise ProjectRootRequiredError(
|
|
409
|
+
"relative verification inputs require --project so their identity "
|
|
410
|
+
"does not depend on the caller's current directory"
|
|
411
|
+
)
|
|
412
|
+
return resolve_input_files(selected, project_root), project_root
|
|
317
413
|
|
|
318
414
|
|
|
319
415
|
def sha256_file(path: Path) -> str:
|
|
@@ -1674,8 +1770,8 @@ def decide_can_reuse(
|
|
|
1674
1770
|
)
|
|
1675
1771
|
and (
|
|
1676
1772
|
not _nonempty_str(requested_command)
|
|
1677
|
-
or str(candidate.get("command") or "")
|
|
1678
|
-
== str(requested_command)
|
|
1773
|
+
or canonical_command(str(candidate.get("command") or ""))
|
|
1774
|
+
== canonical_command(str(requested_command))
|
|
1679
1775
|
)
|
|
1680
1776
|
]
|
|
1681
1777
|
entry = (
|
|
@@ -1823,7 +1919,7 @@ def decide_can_reuse(
|
|
|
1823
1919
|
}
|
|
1824
1920
|
|
|
1825
1921
|
if requested_command is not None and str(requested_command).strip():
|
|
1826
|
-
if str(command)
|
|
1922
|
+
if canonical_command(str(command)) != canonical_command(str(requested_command)):
|
|
1827
1923
|
return {
|
|
1828
1924
|
"ok": True,
|
|
1829
1925
|
"reuse": False,
|
|
@@ -2053,10 +2149,29 @@ def cmd_can_reuse(args: argparse.Namespace) -> int:
|
|
|
2053
2149
|
verbose = bool(getattr(args, "verbose", False))
|
|
2054
2150
|
verification = args.verification
|
|
2055
2151
|
change_dir = resolve_path(args.change_dir)
|
|
2152
|
+
ownership_check = frozen_ownership_check(change_dir)
|
|
2153
|
+
if not ownership_check.get("ok"):
|
|
2154
|
+
return emit_error(
|
|
2155
|
+
str(ownership_check.get("message")),
|
|
2156
|
+
as_json=as_json,
|
|
2157
|
+
error_code=str(ownership_check.get("code")),
|
|
2158
|
+
extra={
|
|
2159
|
+
key: value
|
|
2160
|
+
for key, value in ownership_check.items()
|
|
2161
|
+
if key not in {"ok", "code", "message"}
|
|
2162
|
+
},
|
|
2163
|
+
)
|
|
2056
2164
|
try:
|
|
2057
2165
|
files, project_root = input_files_from_args(args)
|
|
2166
|
+
except ProjectRootRequiredError as exc:
|
|
2167
|
+
return emit_error(
|
|
2168
|
+
str(exc),
|
|
2169
|
+
as_json=as_json,
|
|
2170
|
+
error_code="PROJECT_ROOT_REQUIRED",
|
|
2171
|
+
)
|
|
2058
2172
|
except (OSError, ValueError) as exc:
|
|
2059
2173
|
return emit_error(f"can-reuse failed: {exc}", as_json=as_json)
|
|
2174
|
+
explicit_files = list(files)
|
|
2060
2175
|
target = resolve_verification_target(verification, project_root)
|
|
2061
2176
|
if target is None:
|
|
2062
2177
|
return emit_error(
|
|
@@ -2085,7 +2200,22 @@ def cmd_can_reuse(args: argparse.Namespace) -> int:
|
|
|
2085
2200
|
compact_fn=_compact_can_reuse_payload,
|
|
2086
2201
|
)
|
|
2087
2202
|
return 0
|
|
2088
|
-
|
|
2203
|
+
profile_files = resolve_input_files(resolved_files, project_root)
|
|
2204
|
+
if explicit_files:
|
|
2205
|
+
_explicit_hash, explicit_names = compute_inputs_hash(
|
|
2206
|
+
explicit_files, project_root=project_root
|
|
2207
|
+
)
|
|
2208
|
+
_profile_hash, profile_names = compute_inputs_hash(
|
|
2209
|
+
profile_files, project_root=project_root
|
|
2210
|
+
)
|
|
2211
|
+
if explicit_names != profile_names:
|
|
2212
|
+
return emit_error(
|
|
2213
|
+
"PROFILE_INPUT_FILES_CONFLICT: --files does not match the "
|
|
2214
|
+
"declared build-profile input set",
|
|
2215
|
+
as_json=as_json,
|
|
2216
|
+
error_code="PROFILE_INPUT_FILES_CONFLICT",
|
|
2217
|
+
)
|
|
2218
|
+
files = profile_files
|
|
2089
2219
|
if not files:
|
|
2090
2220
|
return emit_error(
|
|
2091
2221
|
"can-reuse requires --files or a non-empty --profile-input file set",
|
|
@@ -2097,10 +2227,20 @@ def cmd_can_reuse(args: argparse.Namespace) -> int:
|
|
|
2097
2227
|
verification=verification,
|
|
2098
2228
|
files=files,
|
|
2099
2229
|
requested_scope=getattr(args, "scope", None),
|
|
2100
|
-
requested_command=
|
|
2101
|
-
|
|
2230
|
+
requested_command=(
|
|
2231
|
+
canonical_command(str(getattr(args, "command", "")))
|
|
2232
|
+
if _nonempty_str(getattr(args, "command", None))
|
|
2233
|
+
else None
|
|
2234
|
+
),
|
|
2235
|
+
requested_toolchain_hash=(
|
|
2236
|
+
getattr(args, "toolchain_hash", None)
|
|
2237
|
+
or default_toolchain_hash(project_root)
|
|
2238
|
+
),
|
|
2102
2239
|
requested_profile_hash=getattr(args, "profile_hash", None),
|
|
2103
|
-
requested_environment_hash=
|
|
2240
|
+
requested_environment_hash=(
|
|
2241
|
+
getattr(args, "environment_hash", None)
|
|
2242
|
+
or default_environment_hash(project_root)
|
|
2243
|
+
),
|
|
2104
2244
|
requested_db_schema_hash=getattr(args, "db_schema_hash", None),
|
|
2105
2245
|
requested_product_tree_hash=(
|
|
2106
2246
|
getattr(args, "product_tree_hash", None) or product_tree_hash(project_root)
|
|
@@ -2137,6 +2277,18 @@ def cmd_record(args: argparse.Namespace) -> int:
|
|
|
2137
2277
|
as_json = bool(args.json)
|
|
2138
2278
|
verbose = bool(getattr(args, "verbose", False))
|
|
2139
2279
|
change_dir = resolve_path(args.change_dir)
|
|
2280
|
+
ownership_check = frozen_ownership_check(change_dir)
|
|
2281
|
+
if not ownership_check.get("ok"):
|
|
2282
|
+
return emit_error(
|
|
2283
|
+
str(ownership_check.get("message")),
|
|
2284
|
+
as_json=as_json,
|
|
2285
|
+
error_code=str(ownership_check.get("code")),
|
|
2286
|
+
extra={
|
|
2287
|
+
key: value
|
|
2288
|
+
for key, value in ownership_check.items()
|
|
2289
|
+
if key not in {"ok", "code", "message"}
|
|
2290
|
+
},
|
|
2291
|
+
)
|
|
2140
2292
|
verification = args.verification
|
|
2141
2293
|
contract_v2 = _contract_is_v2(change_dir)
|
|
2142
2294
|
migration_receipt: dict[str, Any] | None = None
|
|
@@ -2144,6 +2296,7 @@ def cmd_record(args: argparse.Namespace) -> int:
|
|
|
2144
2296
|
files, project_root = input_files_from_args(args)
|
|
2145
2297
|
except (OSError, ValueError) as exc:
|
|
2146
2298
|
return emit_error(f"record failed: {exc}", as_json=as_json)
|
|
2299
|
+
explicit_files = list(files)
|
|
2147
2300
|
target = resolve_verification_target(verification, project_root)
|
|
2148
2301
|
if target is None:
|
|
2149
2302
|
return emit_error(
|
|
@@ -2160,7 +2313,22 @@ def cmd_record(args: argparse.Namespace) -> int:
|
|
|
2160
2313
|
resolved_files, err = expand_profile_input_files(project_root, profile_input)
|
|
2161
2314
|
if err:
|
|
2162
2315
|
return emit_error(f"record failed: {err}", as_json=as_json)
|
|
2163
|
-
|
|
2316
|
+
profile_files = resolve_input_files(resolved_files, project_root)
|
|
2317
|
+
if explicit_files:
|
|
2318
|
+
_explicit_hash, explicit_names = compute_inputs_hash(
|
|
2319
|
+
explicit_files, project_root=project_root
|
|
2320
|
+
)
|
|
2321
|
+
_profile_hash, profile_names = compute_inputs_hash(
|
|
2322
|
+
profile_files, project_root=project_root
|
|
2323
|
+
)
|
|
2324
|
+
if explicit_names != profile_names:
|
|
2325
|
+
return emit_error(
|
|
2326
|
+
"PROFILE_INPUT_FILES_CONFLICT: --files does not match the "
|
|
2327
|
+
"declared build-profile input set",
|
|
2328
|
+
as_json=as_json,
|
|
2329
|
+
error_code="PROFILE_INPUT_FILES_CONFLICT",
|
|
2330
|
+
)
|
|
2331
|
+
files = profile_files
|
|
2164
2332
|
if not files:
|
|
2165
2333
|
return emit_error(
|
|
2166
2334
|
"record requires --files or a non-empty --profile-input file set",
|
|
@@ -2226,7 +2394,12 @@ def cmd_record(args: argparse.Namespace) -> int:
|
|
|
2226
2394
|
entry.update(
|
|
2227
2395
|
{
|
|
2228
2396
|
"status": status,
|
|
2229
|
-
"command": args.command,
|
|
2397
|
+
"command": canonical_command(args.command),
|
|
2398
|
+
"runnerCommand": (
|
|
2399
|
+
str(getattr(args, "runner_command", None)).strip()
|
|
2400
|
+
if _nonempty_str(getattr(args, "runner_command", None))
|
|
2401
|
+
else str(args.command).strip()
|
|
2402
|
+
),
|
|
2230
2403
|
"evidence": args.evidence,
|
|
2231
2404
|
"exitCode": args.exit_code,
|
|
2232
2405
|
"durationMs": args.duration_ms,
|
|
@@ -2306,6 +2479,10 @@ def cmd_record(args: argparse.Namespace) -> int:
|
|
|
2306
2479
|
("dbSchemaHash", "db_schema_hash"),
|
|
2307
2480
|
):
|
|
2308
2481
|
val = getattr(args, attr, None)
|
|
2482
|
+
if field == "toolchainHash" and not _nonempty_str(val):
|
|
2483
|
+
val = default_toolchain_hash(project_root)
|
|
2484
|
+
elif field == "environmentHash" and not _nonempty_str(val):
|
|
2485
|
+
val = default_environment_hash(project_root)
|
|
2309
2486
|
if _nonempty_str(val):
|
|
2310
2487
|
entry[field] = str(val).strip()
|
|
2311
2488
|
entry["commandSetHash"] = command_set_hash(args.command)
|
|
@@ -2658,6 +2835,11 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2658
2835
|
p_record.add_argument("--verification", required=True)
|
|
2659
2836
|
p_record.add_argument("--status", required=True)
|
|
2660
2837
|
p_record.add_argument("--command", required=True)
|
|
2838
|
+
p_record.add_argument(
|
|
2839
|
+
"--runner-command",
|
|
2840
|
+
default=None,
|
|
2841
|
+
help="actual launcher command; stored as execution metadata, not target identity",
|
|
2842
|
+
)
|
|
2661
2843
|
p_record.add_argument("--exit-code", type=int, required=True)
|
|
2662
2844
|
p_record.add_argument("--duration-ms", type=int, required=True)
|
|
2663
2845
|
p_record_files = p_record.add_mutually_exclusive_group()
|
|
@@ -30,6 +30,18 @@ if hasattr(sys.stderr, "reconfigure"):
|
|
|
30
30
|
|
|
31
31
|
SCHEMA_VERSION = 1
|
|
32
32
|
_LINK = re.compile(r"\[[^\]]+\]\(([^)]+)\)")
|
|
33
|
+
_BACKTICK = re.compile(r"`([^`\r\n]+)`")
|
|
34
|
+
_CODE_PATH_SUFFIXES = {
|
|
35
|
+
".c", ".cc", ".cpp", ".cs", ".css", ".go", ".h", ".hpp", ".html",
|
|
36
|
+
".java", ".js", ".jsx", ".kt", ".kts", ".mjs", ".php", ".py", ".rb",
|
|
37
|
+
".rs", ".scss", ".sh", ".svelte", ".swift", ".ts", ".tsx", ".vue",
|
|
38
|
+
}
|
|
39
|
+
_BUILD_FILE_NAMES = {
|
|
40
|
+
"build.gradle", "build.gradle.kts", "cargo.toml", "composer.json", "go.mod",
|
|
41
|
+
"gradle.properties", "package-lock.json", "package.json", "pnpm-lock.yaml",
|
|
42
|
+
"pom.xml", "pyproject.toml", "requirements.txt", "settings.gradle",
|
|
43
|
+
"settings.gradle.kts", "tsconfig.json", "yarn.lock",
|
|
44
|
+
}
|
|
33
45
|
|
|
34
46
|
# C8: valid ownerPhase values (lifecycle phases that can own tasks).
|
|
35
47
|
VALID_OWNER_PHASES = {"plan", "run", "test", "review", "submit"}
|
|
@@ -615,6 +627,71 @@ def validate_staging(staging: Path, change_name: str) -> dict[str, Any]:
|
|
|
615
627
|
}
|
|
616
628
|
|
|
617
629
|
|
|
630
|
+
def validate_product_ownership(change_dir: Path, staging: Path) -> dict[str, Any]:
|
|
631
|
+
"""Ensure Plan declares stable prefix ownership for explicit code/test/build paths."""
|
|
632
|
+
context_path = change_dir / "meta" / "change-context.json"
|
|
633
|
+
if not context_path.is_file():
|
|
634
|
+
return {"ok": True, "code": "PLAN_PRODUCT_PATHS_LEGACY_UNDECLARED"}
|
|
635
|
+
try:
|
|
636
|
+
context = json.loads(context_path.read_text(encoding="utf-8-sig"))
|
|
637
|
+
except (OSError, json.JSONDecodeError) as exc:
|
|
638
|
+
return _result_error("PLAN_CHANGE_CONTEXT_INVALID", str(exc))
|
|
639
|
+
ownership = context.get("ownership") if isinstance(context, dict) else None
|
|
640
|
+
raw_paths = ownership.get("productPaths") if isinstance(ownership, dict) else None
|
|
641
|
+
product_paths = [
|
|
642
|
+
str(item).strip().replace("\\", "/").removeprefix("./")
|
|
643
|
+
for item in raw_paths or []
|
|
644
|
+
if isinstance(item, str) and item.strip()
|
|
645
|
+
]
|
|
646
|
+
unsupported = sorted(path for path in product_paths if any(char in path for char in "*?[]"))
|
|
647
|
+
if unsupported:
|
|
648
|
+
result = _result_error(
|
|
649
|
+
"PLAN_PRODUCT_PATHS_GLOB_UNSUPPORTED",
|
|
650
|
+
"productPaths 只接受精确文件或目录前缀,不支持通配:" + ", ".join(unsupported),
|
|
651
|
+
)
|
|
652
|
+
result["unsupportedPaths"] = unsupported
|
|
653
|
+
return result
|
|
654
|
+
|
|
655
|
+
planned_paths: set[str] = set()
|
|
656
|
+
for document in sorted(staging.rglob("*.md")):
|
|
657
|
+
text = document.read_text(encoding="utf-8-sig")
|
|
658
|
+
for raw in _BACKTICK.findall(text):
|
|
659
|
+
candidate = raw.strip().strip("'\"").replace("\\", "/")
|
|
660
|
+
if not candidate or " " in candidate or "://" in candidate or "<" in candidate:
|
|
661
|
+
continue
|
|
662
|
+
candidate = re.sub(r":\d+(?::\d+)?$", "", candidate)
|
|
663
|
+
candidate = candidate.removeprefix("./").strip("/.,;,;")
|
|
664
|
+
if not candidate or candidate.startswith("../") or candidate.startswith(".harness/"):
|
|
665
|
+
continue
|
|
666
|
+
pure = PurePosixPath(candidate)
|
|
667
|
+
if pure.name.lower() in _BUILD_FILE_NAMES or pure.suffix.lower() in _CODE_PATH_SUFFIXES:
|
|
668
|
+
planned_paths.add(candidate)
|
|
669
|
+
|
|
670
|
+
def covered(path: str) -> bool:
|
|
671
|
+
return any(
|
|
672
|
+
path == owned.rstrip("/")
|
|
673
|
+
or path.startswith(owned.rstrip("/") + "/")
|
|
674
|
+
for owned in product_paths
|
|
675
|
+
if owned.rstrip("/")
|
|
676
|
+
)
|
|
677
|
+
|
|
678
|
+
missing = sorted(path for path in planned_paths if not covered(path))
|
|
679
|
+
if missing:
|
|
680
|
+
result = _result_error(
|
|
681
|
+
"PLAN_PRODUCT_PATHS_INCOMPLETE",
|
|
682
|
+
"productPaths 未覆盖计划明确引用的产品文件:" + ", ".join(missing),
|
|
683
|
+
)
|
|
684
|
+
result["missingPaths"] = missing
|
|
685
|
+
result["recoveryAction"] = "在 Plan 阶段补充精确文件或目录前缀后重新 finalize。"
|
|
686
|
+
return result
|
|
687
|
+
return {
|
|
688
|
+
"ok": True,
|
|
689
|
+
"code": "PLAN_PRODUCT_PATHS_COMPLETE",
|
|
690
|
+
"plannedPaths": sorted(planned_paths),
|
|
691
|
+
"productPaths": product_paths,
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
|
|
618
695
|
def _terminal_exists(change_dir: Path, run_id: str, attempt: int) -> bool:
|
|
619
696
|
try:
|
|
620
697
|
events = harness_events.load_events(harness_events.events_path(change_dir))
|
|
@@ -1125,6 +1202,9 @@ def finalize_plan(
|
|
|
1125
1202
|
validation = validate_staging(staging, change_name)
|
|
1126
1203
|
if not validation["ok"]:
|
|
1127
1204
|
return validation
|
|
1205
|
+
ownership_validation = validate_product_ownership(change_dir, staging)
|
|
1206
|
+
if not ownership_validation["ok"]:
|
|
1207
|
+
return ownership_validation
|
|
1128
1208
|
start_validation = _validate_plan_start(change_dir, run_id, attempt)
|
|
1129
1209
|
if not start_validation["ok"]:
|
|
1130
1210
|
return start_validation
|
|
@@ -278,7 +278,7 @@ harness-skills/
|
|
|
278
278
|
| **submit 步骤 4** | 提交前必须展示四项:实际 staged 文件列表、diff stat、commit message、是否 push |
|
|
279
279
|
| **submit push 前** | `git fetch` 后检查远程是否有新提交;有则**不得直接 pull 后 push**,必须 pull/rebase + 重新 compile/test |
|
|
280
280
|
| **submit hash 记录** | pre-pull local hash + final pushed hash 双标注(主目录);worktree 模式 submit 段只本地 commit,合并段产生 `mergeFinalHash`,archive 以 `mergeFinalHash` 为准(无则回退 final pushed hash) |
|
|
281
|
-
| **archive 阶段 1** |
|
|
281
|
+
| **archive 阶段 1** | 归档前确认实际计划阶段的证据与结局;有 Git 时核对最终提交身份,无 Git、无 upstream 或跳过 submit 时使用内容清单身份。发布候选资格与本地封存分开判断;阶段边界由 finalize 单进程维护,调用者不额外 append |
|
|
282
282
|
| **archive 文件移动** | 只用 PowerShell 或 Read+Write+验证,**禁止 Bash mv/cp/rm**;移动失败时不删除原目录 |
|
|
283
283
|
| **archive final-summary** | 必须运行 `harness_archive.py finalize` 生成权威 `summary-data.json` 并完成 validate;`final-summary.html` 为可选展示投影(默认渲染,失败只记 warning 不回滚;`finalize --no-html` 可跳过)。无测试或无 review 时必须在 JSON 中标记 `NOT_RUN` / `ADVISORY_NOT_RUN`,禁止伪造 100% 通过率。状态演进须真实展示(✅OK / 🟡WARN / 🔁REUSED / 🔁RETESTED / 📝ADVISORY / 🧹NON_BEHAVIORAL_CLEANUP) |
|
|
284
284
|
|
|
@@ -339,9 +339,9 @@ harness-skills/
|
|
|
339
339
|
|
|
340
340
|
> **harness-review 是参考性审查阶段**:默认只生成参考性审查报告和按需 fixback,不影响后续 submit、archive。如果用户选择处理 fixback,回到 `/harness-run --fixback`,再执行 `/harness-test` 和 `/harness-review`;如果团队希望 review RED 阻塞提交,可在 `.harness/config/harness-test-config.md` 中设置 `review.strict-review-gate: true`。
|
|
341
341
|
|
|
342
|
-
>
|
|
342
|
+
> **阶段顺序以计划为准**:Plan 会把本次 `plannedPhases` 写入上下文,后续阶段只消费该计划,不再假定 test、review、submit 或 merge 必经。快速流程默认可用 `plan → run → archive`;需要完整验证、评审或提交时再把对应阶段加入计划。无 Git、无 upstream、仅本地提交或主动跳过 submit 都不妨碍事实封存;只有“发布候选”需要额外满足发布策略。
|
|
343
343
|
|
|
344
|
-
> **verification-ledger
|
|
344
|
+
> **verification-ledger 驱动跨阶段复用**:先通过共享状态目录解析器定位变更,再读写其 `evidence/verification-ledger.json`,其中记录 compile/unitTest/apiTest 的结果、证据、diffHash 和作用范围。run/test/submit 执行验证前先读 ledger,满足复用条件(diffHash 一致、module/profile 一致、范围更严格、有证据、无行为性 post-test 修改)则跳过重跑并标记 🔁REUSED;post-test 小改动按 7 类分类,非行为性清理可复用 API 测试。详见 `protocols/ledger-protocol.md`。
|
|
345
345
|
|
|
346
346
|
## 外部方法论吸收(已内化)
|
|
347
347
|
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"riskTiers": {
|
|
35
35
|
"fast": {
|
|
36
36
|
"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).",
|
|
37
|
-
"defaultPhases": ["plan", "run", "
|
|
37
|
+
"defaultPhases": ["plan", "run", "archive"],
|
|
38
38
|
"requiredValidations": ["unitTest"],
|
|
39
39
|
"conditionalStages": ["review", "package", "apidoc"],
|
|
40
40
|
"upgradeTriggers": ["api-change", "security", "migration", "concurrency", "breaking-contract"]
|
|
@@ -3,7 +3,7 @@ name: harness-apidoc
|
|
|
3
3
|
description: 从 Java 后端代码生成结构化前端 API 文档(Controller/VO/ErrorCode)。仅当用户显式调用
|
|
4
4
|
/harness-apidoc 时使用,不得自动触发。
|
|
5
5
|
---
|
|
6
|
-
<!-- generated by harness_deploy.py; core=
|
|
6
|
+
<!-- generated by harness_deploy.py; core=ea74ea84e6cc3617; overlay=java; agent=cursor; do not edit -->
|
|
7
7
|
# harness-apidoc — 前端接口文档(Java overlay)
|
|
8
8
|
|
|
9
9
|
## Purpose
|
|
@@ -3,50 +3,38 @@ name: harness-archive
|
|
|
3
3
|
description: 归档所有变更产出(计划/测试报告/审查/SQL/API文档)到 .harness/archive/,含归档元数据。仅当用户显式调用
|
|
4
4
|
/harness-archive 时使用;不得被其他阶段 skill 自动接续触发。
|
|
5
5
|
---
|
|
6
|
-
<!-- generated by harness_deploy.py; core=
|
|
6
|
+
<!-- generated by harness_deploy.py; core=ea74ea84e6cc3617; overlay=java; agent=cursor; do not edit -->
|
|
7
7
|
# harness-archive — 变更归档
|
|
8
8
|
|
|
9
9
|
## Purpose
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
把变更事实安全封存、生成确定性 ZIP,并在已连接 Hunter Platform 时上传保存。归档结局、发布资格、Git 远端和平台上传分别建模,互不冒充前置条件。
|
|
12
12
|
|
|
13
13
|
## When to Use
|
|
14
14
|
|
|
15
15
|
仅当用户显式调用 `/harness-archive` 时执行。submit/merge 完成后**不自动**进入本阶段;用户口头说"归档/收尾"而未调用本 skill 时,先确认。
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
跳过场景:
|
|
20
|
-
|
|
21
|
-
- 变更尚未 submit 推送(归档前 commit 必须已 push)。
|
|
22
|
-
- 仍存在未结清的 test/review 验证(除非已明确标记 CONDITIONAL_OK)。
|
|
17
|
+
前置只看本次 `plannedPhases` 与用户选择的生命周期结局。Submit、Test 或 Review 未在计划中时记录为“未执行”,不得临时补跑;无 Git、无 upstream、仅本地 commit 都允许归档和平台上传,只是不能声明为发布候选。
|
|
23
18
|
|
|
24
19
|
自动调用边界:
|
|
25
20
|
|
|
26
21
|
- 归档涉及移动/删除原变更目录,**禁止被其他 skill 自动调用**;只接受用户显式 `/harness-archive` 调用。
|
|
27
|
-
- `harness_archive.py auto-gate`
|
|
22
|
+
- `harness_archive.py auto-gate` 只影响**本次显式调用内**是否需要归档确认对话:实际计划中的最后阶段已终态、archive-boundary/content snapshot 存在且保存门禁满足时可跳过确认。auto-gate 不构成跨阶段自动触发的授权。
|
|
28
23
|
|
|
29
24
|
## 前置条件
|
|
30
25
|
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
- **最小必备集(H-4 / `harness_archive.py status` blockers)**:
|
|
35
|
-
- `plans/*-plan.md`
|
|
36
|
-
- 非空 `events.ndjson`
|
|
37
|
-
- `evidence/verification-ledger.json`
|
|
38
|
-
- 至少一个 test 或 review 报告/证据
|
|
39
|
-
- 缺失任一项 → `archivable=false`;可从 `.harness/cache/change-snapshots/<change>/` 恢复正式层后再重试
|
|
26
|
+
- **最终产品身份**:Git 项目优先使用 Submit 最终收据/当前 HEAD;提交前验证而内容未变化时由 `certify-local` 自动重绑定 commit。非 Git 项目使用确定性内容清单与产品树散列,并记录 `sourceControl=none`。
|
|
27
|
+
- **正常完成**按 `plannedPhases` 检查已规划阶段;被省略阶段记录 `NOT_RUN` / `NOT_APPLICABLE`。**结束未完成变更**只要求足以识别和安全封存变更的最小设计、事件与内容清单,不要求补齐测试、评审或提交。
|
|
28
|
+
- 所有 events、ledger、reports、receipts 和 upload state 都通过 state layout 解析器读取;split-v1 的权威状态位于 `.harness/state/changes/<change>`,禁止复制到旧目录。
|
|
40
29
|
- **产品候选验证**:
|
|
41
30
|
- 有远端 CI:保留 `product-candidate-ci.json` 兼容证据;归档会把有效的 legacy schema v1 证据迁移为 `remote-claimed` 的 schema v2 `product-candidate-verification.json`,不会冒充 `remote-attested`。
|
|
42
31
|
- `remote-attested` 必须同时带远端运行 URL 与 `verification.attestationDigest`;只有 URL、旧 JSON 哈希或 ledger 哈希只能达到 `remote-claimed`。
|
|
43
32
|
- 无 CI:先运行 `harness_archive.py certify-local --change-dir ... --project . --json`,只复用身份一致且完整的 `unitTestFull` ledger 证据,不重复执行测试。
|
|
44
|
-
- `release-candidate`
|
|
33
|
+
- `release-candidate` 要求候选验证通过;正常本地完成或结束未完成变更使用 `--intent record-only`,固定 `releaseEligible=false`,但仍生成 ZIP 并上传平台。
|
|
45
34
|
|
|
46
35
|
## Inputs
|
|
47
36
|
|
|
48
|
-
- `$ARGUMENTS
|
|
49
|
-
- 相关文件:`.harness/changes/<change-name>/logs/execution-log.md`、`.harness/changes/<change-name>/events.ndjson`、`evidence/verification-ledger.json`、各阶段 reports/
|
|
37
|
+
- `$ARGUMENTS`:变更名(可选);必须由 `harness_change.py resolve` 解析 contract/state 双层目录,禁止按 mtime 或固定旧路径猜测。
|
|
50
38
|
|
|
51
39
|
## 统一读取协议
|
|
52
40
|
|
|
@@ -78,29 +66,29 @@ description: 归档所有变更产出(计划/测试报告/审查/SQL/API文档
|
|
|
78
66
|
- `../protocols/sensitive-info-protocol.md`
|
|
79
67
|
- `../protocols/evidence-based-reporting-protocol.md`
|
|
80
68
|
- `../protocols/report-pipeline-protocol.md`
|
|
81
|
-
4. 解析 `$ARGUMENTS
|
|
69
|
+
4. 用 `harness_change.py resolve [--change] --json` 解析 `$ARGUMENTS`;多个 active change 时让用户选择。
|
|
82
70
|
|
|
83
71
|
### Phase 1:确认归档对象(扫描未归档变更)
|
|
84
72
|
|
|
85
|
-
|
|
73
|
+
使用 resolver 返回的 active changes 展示变更概要;同时读取 `plannedPhases`、已完成阶段、Git 能力、平台连接和待上传 ZIP 状态。
|
|
86
74
|
|
|
87
75
|
- **Read `checklist.md`** — 归档前检查项
|
|
88
76
|
- 发现多个未归档变更 → 让用户选择或终止
|
|
89
77
|
|
|
90
78
|
### Phase 2:确认归档 / 自动门禁
|
|
91
79
|
|
|
92
|
-
先运行 `harness_archive.py auto-gate --change-dir "
|
|
80
|
+
先运行 `harness_archive.py auto-gate --change-dir "<executionRoot>" --intent <intent> --json`。
|
|
93
81
|
|
|
94
82
|
- `autoArchiveAllowed=true`:记录 gate receipt,直接进入 Phase 3;无需 AskQuestion。
|
|
95
|
-
-
|
|
83
|
+
- 否则用中文提供:①正常完成并归档(本地验证、不可发布);②作为发布候选归档;③结束未完成变更;④取消。选择③后继续选择“主动废弃”或“被其他方案替代”,并填写中文原因。不要把无 upstream 显示为阻止保存。
|
|
96
84
|
|
|
97
85
|
- **Read `reference.md`** — 确认对话框的内容格式
|
|
98
86
|
|
|
99
87
|
### Phase 3:执行归档
|
|
100
88
|
|
|
101
|
-
1.
|
|
89
|
+
1. 对“发布候选”使用 `--intent release-candidate --closure completed`;对正常本地完成使用 `--intent record-only --closure completed`;主动废弃或被替代分别使用 `--intent record-only --closure abandoned|superseded --closure-reason "<中文原因>"`。先运行 `status`;有可复用全量 ledger 时可运行 `certify-local`,不得重复测试。
|
|
102
90
|
2. `meta/archive-meta.md` **由 `harness_archive.py finalize` 自动生成**(与 summary-data `finalStatus` 同源);**禁止 agent 手写**该文件,手写视为数据丢失。维护者结论写入 events(decision/issue)即可,finalize 会汇总到 summary / archive-meta。
|
|
103
|
-
3. 运行 `
|
|
91
|
+
3. 运行 `harness_archive.py finalize --change-dir "<executionRoot>" --archive-root ".harness/archive" --intent <...> --closure <...> [--closure-reason "..."] --json`;读 JSON(cleanup、事件、移动、collect、render、validate、manifest、archive-meta、ZIP、上传与服务端知识状态)。finalize 内部只负责一次 `phase.start` / `phase.end`,调用者不得重复追加。**本地不执行知识 ingest**;服务端在 ZIP 持久保存并解包后 ingest。失败时保留原目录、ZIP 和回执。
|
|
104
92
|
- **归档包上传**:始终先生成一个确定性 ZIP;有远程凭据时再调用 `npx hunter-harness archive upload`。ZIP 仅包含 `summary-data.json`、`spec/**/*.md`、`plans/**/*.md`、`archive-meta.md`、`change-context.json` 和稳定 manifest;明确排除 logs、review/test 报告、HTML、缓存、备份、凭据和临时文件。
|
|
105
93
|
- **失败可恢复**:无论远端凭据是否齐全,都先生成 ZIP 与 `<change-key>.upload.json`;上传或服务端 ingest 失败不破坏本地归档。待上传 ZIP 与逐 change 回执保留在 `.harness/state/local/archive-packages/`,可枚举独立重试。只有 CLI 核验 package hash 且服务端同时返回 `archive_status=durable`、`knowledge_status=ready` 后,才清理对应 ZIP 与回执;`indexing` 记为 pending,不记为失败。
|
|
106
94
|
- **监控终态(C3)**:auto-upload 之后自动 `events-sync`,用归档前 change 路径派生的 `run_id` + 原 `change_key` 上报;失败只记 warning。
|
|
@@ -137,7 +125,7 @@ description: 归档所有变更产出(计划/测试报告/审查/SQL/API文档
|
|
|
137
125
|
|
|
138
126
|
### 二、归档前确认或自动门禁
|
|
139
127
|
|
|
140
|
-
`auto-gate`
|
|
128
|
+
`auto-gate` 只有在实际计划最后阶段终态、边界快照存在且 `status.archivable=true` 时才允许无确认执行。未满足时按 Phase 2 的四个中文选项确认;用户取消则不执行移动。
|
|
141
129
|
|
|
142
130
|
### 三、文件移动只用内置工具或 PowerShell
|
|
143
131
|
|
|
@@ -151,12 +139,12 @@ description: 归档所有变更产出(计划/测试报告/审查/SQL/API文档
|
|
|
151
139
|
|
|
152
140
|
归档前后生成 `evidence/archive-manifest-before.json` / `archive-manifest-after.json`(path/size/sha256),before/after 不一致时**不得删除原目录**。复杂 PowerShell 写入 `scripts/*.ps1` 后 `-File` 执行,禁止内联 `$` / `$_` / `@{}`。详见 `reference.md`。
|
|
153
141
|
|
|
154
|
-
###
|
|
142
|
+
### 六、归档前确认四项
|
|
155
143
|
|
|
156
|
-
-
|
|
157
|
-
-
|
|
158
|
-
-
|
|
159
|
-
-
|
|
144
|
+
- 生命周期结局与中文原因已确认;正常完成、主动废弃、被其他方案替代不得混写
|
|
145
|
+
- Git 项目记录当前 HEAD 与 upstream 状态;无 upstream 只影响发布资格,不阻止保存。非 Git 项目记录内容身份
|
|
146
|
+
- Test/Review 只按 `plannedPhases` 核对;未规划或提前结束时如实标记未执行
|
|
147
|
+
- 平台连接与 Git remote 分开检查;无 Git remote 仍生成 ZIP,并在平台已连接时上传
|
|
160
148
|
|
|
161
149
|
### 七、verification-ledger 汇总状态
|
|
162
150
|
|
|
@@ -37,19 +37,15 @@ description: harness-archive 的归档前检查项和归档后验证项。仅在
|
|
|
37
37
|
|
|
38
38
|
- [ ] 未在调用 finalize 前手工追加 archive 阶段边界
|
|
39
39
|
- [ ] 只有一个未归档变更目录(多个时终止或让用户选择)
|
|
40
|
-
- [ ]
|
|
41
|
-
|
|
42
|
-
- [ ] `events.ndjson` 存在且非空
|
|
43
|
-
- [ ] `evidence/verification-ledger.json` 存在
|
|
44
|
-
- [ ] 至少一个 test 或 review 报告/证据
|
|
45
|
-
- [ ] 若缺失:优先从 `.harness/cache/change-snapshots/<change>/` 恢复,禁止空壳归档
|
|
40
|
+
- [ ] 已确认 `closure`:正常完成 / 主动废弃 / 被其他方案替代;后两者有中文 `closure-reason`
|
|
41
|
+
- [ ] 正常完成按 `plannedPhases` 核对;提前结束只要求可识别变更与安全封存的最小材料
|
|
46
42
|
- [ ] 变更目录下有 plans/ 子目录(至少有计划文件)
|
|
47
43
|
- [ ] `events.ndjson` 存在;执行日志允许由 finalize 从事件流重新渲染(旧 archive 才兼容根目录 `execution-log.md`)
|
|
48
44
|
- [ ] 准备生成 `archive-manifest-before.json`(path/size/sha256)
|
|
49
45
|
- [ ] 准备生成 `summary-data.json`(业务目标、阶段状态、验证、产物、维护者结论)
|
|
50
|
-
- [ ]
|
|
51
|
-
- [ ]
|
|
52
|
-
- [ ]
|
|
46
|
+
- [ ] Git 项目记录 dirty/HEAD/upstream;无 upstream 或仅本地 commit 允许归档,但 `releaseEligible=false`
|
|
47
|
+
- [ ] 非 Git 项目生成确定性内容清单与产品树散列,并记录 `sourceControl=none`
|
|
48
|
+
- [ ] 最终产品身份来自 Submit 收据或当前内容;提交前候选只允许在验证输入未变化时由 `certify-local` 自动重绑定
|
|
53
49
|
- [ ] **test/review 报告状态确认**:
|
|
54
50
|
- ✅ `.harness/changes/<change-name>/tests/test-report-*.md` 存在 → 归档正常
|
|
55
51
|
- 🟡 不存在 → 必须在 archive-meta.md 和 final-summary.html 中标记"跳过测试"或"未运行测试",不得伪造通过率
|
|
@@ -63,7 +59,7 @@ description: harness-archive 的归档前检查项和归档后验证项。仅在
|
|
|
63
59
|
- [ ] `.harness/archive/YYYY-MM-DD-<change-name>/` 目录存在(通过 Glob 实际扫描确认)
|
|
64
60
|
- [ ] 所有子目录(plans/, tests/, reviews/, sqls/)已完整移入(通过 Glob 实际扫描确认,不仅看预期路径)
|
|
65
61
|
- [ ] before/after manifest 校验通过(排除 `logs/execution-log.md`——归档追加结束记录预期 sha256 变化;其他 moved 文件 sha256 必须一致,missing/mismatch=0)
|
|
66
|
-
- [ ]
|
|
62
|
+
- [ ] archive-meta.md 由 finalize 自动生成且字段完整;调用者未手写或补改
|
|
67
63
|
- [ ] summary-data.json 已生成,且为合法 JSON
|
|
68
64
|
- [ ] final-summary.html 已由 `templates/render-summary.mjs` 渲染生成
|
|
69
65
|
- [ ] **final-summary.html 真实性检查**:
|
|
@@ -3,7 +3,7 @@ name: harness-codebase-map
|
|
|
3
3
|
description: 分析现有代码库并生成 .harness/codebase/map/
|
|
4
4
|
下的结构化代码库地图。用于陌生项目理解、重大改造前梳理、harness-sync 提示 map 过期后的人工刷新。
|
|
5
5
|
---
|
|
6
|
-
<!-- generated by harness_deploy.py; core=
|
|
6
|
+
<!-- generated by harness_deploy.py; core=ea74ea84e6cc3617; overlay=java; agent=cursor; do not edit -->
|
|
7
7
|
# harness-codebase-map — 代码库地图
|
|
8
8
|
|
|
9
9
|
## Purpose
|