@hunter-harness/workflow-harness 0.2.1 → 0.2.2
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 +1 -1
- package/harness/bundles/general/claude-code/harness-archive/reference.md +2 -8
- package/harness/bundles/general/claude-code/harness-codebase-map/SKILL.md +2 -1
- package/harness/bundles/general/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-knowledge-ingest/scripts/harness_knowledge.py +474 -122
- package/harness/bundles/general/claude-code/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-plan/SKILL.md +2 -1
- package/harness/bundles/general/claude-code/harness-review/SKILL.md +5 -1
- package/harness/bundles/general/claude-code/harness-run/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-run/checklist.md +1 -1
- package/harness/bundles/general/claude-code/harness-submit/SKILL.md +2 -2
- package/harness/bundles/general/claude-code/harness-submit/checklist.md +30 -6
- package/harness/bundles/general/claude-code/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-test/SKILL.md +2 -2
- package/harness/bundles/general/claude-code/harness-test/checklist.md +1 -1
- package/harness/bundles/general/claude-code/harness-test/pitfalls.md +16 -0
- package/harness/bundles/general/claude-code/harness-test/scripts/runtime-helpers.mjs +216 -0
- package/harness/bundles/general/claude-code/protocols/archive-report-protocol.md +1 -1
- package/harness/bundles/general/claude-code/protocols/ledger-protocol.md +55 -2
- package/harness/bundles/general/claude-code/protocols/report-pipeline-protocol.md +1 -8
- package/harness/bundles/general/claude-code/protocols/sensitive-info-protocol.md +13 -0
- package/harness/bundles/general/claude-code/protocols/state-layout-protocol.md +24 -0
- package/harness/bundles/general/claude-code/scripts/harness_archive.py +31 -14
- package/harness/bundles/general/claude-code/scripts/harness_check_gate.py +103 -0
- package/harness/bundles/general/claude-code/scripts/harness_deploy.py +179 -4
- package/harness/bundles/general/claude-code/scripts/harness_ledger.py +269 -3
- package/harness/bundles/general/claude-code/scripts/harness_preflight.py +72 -382
- package/harness/bundles/general/claude-code/scripts/harness_profile.py +742 -0
- package/harness/bundles/general/claude-code/scripts/harness_service.py +99 -2
- package/harness/bundles/general/claude-code/scripts/harness_state.py +179 -0
- package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-archive/reference.md +2 -8
- package/harness/bundles/general/codebuddy/harness-codebase-map/SKILL.md +2 -1
- package/harness/bundles/general/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge.py +474 -122
- package/harness/bundles/general/codebuddy/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-plan/SKILL.md +2 -1
- package/harness/bundles/general/codebuddy/harness-review/SKILL.md +5 -1
- package/harness/bundles/general/codebuddy/harness-run/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-run/checklist.md +1 -1
- package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +2 -2
- package/harness/bundles/general/codebuddy/harness-submit/checklist.md +30 -6
- package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-test/SKILL.md +2 -2
- package/harness/bundles/general/codebuddy/harness-test/checklist.md +1 -1
- package/harness/bundles/general/codebuddy/harness-test/pitfalls.md +16 -0
- package/harness/bundles/general/codebuddy/harness-test/scripts/runtime-helpers.mjs +216 -0
- package/harness/bundles/general/codebuddy/protocols/archive-report-protocol.md +1 -1
- package/harness/bundles/general/codebuddy/protocols/ledger-protocol.md +55 -2
- package/harness/bundles/general/codebuddy/protocols/report-pipeline-protocol.md +1 -8
- package/harness/bundles/general/codebuddy/protocols/sensitive-info-protocol.md +13 -0
- package/harness/bundles/general/codebuddy/protocols/state-layout-protocol.md +24 -0
- package/harness/bundles/general/codebuddy/scripts/harness_archive.py +31 -14
- package/harness/bundles/general/codebuddy/scripts/harness_check_gate.py +103 -0
- package/harness/bundles/general/codebuddy/scripts/harness_deploy.py +179 -4
- package/harness/bundles/general/codebuddy/scripts/harness_ledger.py +269 -3
- package/harness/bundles/general/codebuddy/scripts/harness_preflight.py +72 -382
- package/harness/bundles/general/codebuddy/scripts/harness_profile.py +742 -0
- package/harness/bundles/general/codebuddy/scripts/harness_service.py +99 -2
- package/harness/bundles/general/codebuddy/scripts/harness_state.py +179 -0
- package/harness/bundles/general/codex/.harness-build.json +1 -1
- package/harness/bundles/general/codex/harness-archive/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-archive/reference.md +2 -8
- package/harness/bundles/general/codex/harness-codebase-map/SKILL.md +2 -1
- package/harness/bundles/general/codex/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-knowledge-ingest/scripts/harness_knowledge.py +474 -122
- package/harness/bundles/general/codex/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-plan/SKILL.md +2 -1
- package/harness/bundles/general/codex/harness-review/SKILL.md +5 -1
- package/harness/bundles/general/codex/harness-run/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-run/checklist.md +1 -1
- package/harness/bundles/general/codex/harness-submit/SKILL.md +2 -2
- package/harness/bundles/general/codex/harness-submit/checklist.md +30 -6
- package/harness/bundles/general/codex/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-test/SKILL.md +2 -2
- package/harness/bundles/general/codex/harness-test/checklist.md +1 -1
- package/harness/bundles/general/codex/harness-test/pitfalls.md +16 -0
- package/harness/bundles/general/codex/harness-test/scripts/runtime-helpers.mjs +216 -0
- package/harness/bundles/general/codex/protocols/archive-report-protocol.md +1 -1
- package/harness/bundles/general/codex/protocols/ledger-protocol.md +55 -2
- package/harness/bundles/general/codex/protocols/report-pipeline-protocol.md +1 -8
- package/harness/bundles/general/codex/protocols/sensitive-info-protocol.md +13 -0
- package/harness/bundles/general/codex/protocols/state-layout-protocol.md +24 -0
- package/harness/bundles/general/codex/scripts/harness_archive.py +31 -14
- package/harness/bundles/general/codex/scripts/harness_check_gate.py +103 -0
- package/harness/bundles/general/codex/scripts/harness_deploy.py +179 -4
- package/harness/bundles/general/codex/scripts/harness_ledger.py +269 -3
- package/harness/bundles/general/codex/scripts/harness_preflight.py +72 -382
- package/harness/bundles/general/codex/scripts/harness_profile.py +742 -0
- package/harness/bundles/general/codex/scripts/harness_service.py +99 -2
- package/harness/bundles/general/codex/scripts/harness_state.py +179 -0
- package/harness/bundles/general/cursor/.harness-build.json +1 -1
- package/harness/bundles/general/cursor/harness-archive/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-archive/reference.md +2 -8
- package/harness/bundles/general/cursor/harness-codebase-map/SKILL.md +2 -1
- package/harness/bundles/general/cursor/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-knowledge-ingest/scripts/harness_knowledge.py +474 -122
- package/harness/bundles/general/cursor/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-plan/SKILL.md +2 -1
- package/harness/bundles/general/cursor/harness-review/SKILL.md +5 -1
- package/harness/bundles/general/cursor/harness-run/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-run/checklist.md +1 -1
- package/harness/bundles/general/cursor/harness-submit/SKILL.md +2 -2
- package/harness/bundles/general/cursor/harness-submit/checklist.md +30 -6
- package/harness/bundles/general/cursor/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-test/SKILL.md +2 -2
- package/harness/bundles/general/cursor/harness-test/checklist.md +1 -1
- package/harness/bundles/general/cursor/harness-test/pitfalls.md +16 -0
- package/harness/bundles/general/cursor/harness-test/scripts/runtime-helpers.mjs +216 -0
- package/harness/bundles/general/cursor/protocols/archive-report-protocol.md +1 -1
- package/harness/bundles/general/cursor/protocols/ledger-protocol.md +55 -2
- package/harness/bundles/general/cursor/protocols/report-pipeline-protocol.md +1 -8
- package/harness/bundles/general/cursor/protocols/sensitive-info-protocol.md +13 -0
- package/harness/bundles/general/cursor/protocols/state-layout-protocol.md +24 -0
- package/harness/bundles/general/cursor/scripts/harness_archive.py +31 -14
- package/harness/bundles/general/cursor/scripts/harness_check_gate.py +103 -0
- package/harness/bundles/general/cursor/scripts/harness_deploy.py +179 -4
- package/harness/bundles/general/cursor/scripts/harness_ledger.py +269 -3
- package/harness/bundles/general/cursor/scripts/harness_preflight.py +72 -382
- package/harness/bundles/general/cursor/scripts/harness_profile.py +742 -0
- package/harness/bundles/general/cursor/scripts/harness_service.py +99 -2
- package/harness/bundles/general/cursor/scripts/harness_state.py +179 -0
- package/harness/bundles/java/claude-code/.harness-build.json +1 -1
- package/harness/bundles/java/claude-code/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-archive/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-archive/reference.md +2 -8
- package/harness/bundles/java/claude-code/harness-codebase-map/SKILL.md +2 -1
- package/harness/bundles/java/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-knowledge-ingest/scripts/harness_knowledge.py +474 -122
- package/harness/bundles/java/claude-code/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-package/SKILL.md +3 -3
- package/harness/bundles/java/claude-code/harness-plan/SKILL.md +2 -1
- package/harness/bundles/java/claude-code/harness-review/SKILL.md +5 -1
- package/harness/bundles/java/claude-code/harness-run/SKILL.md +4 -4
- package/harness/bundles/java/claude-code/harness-run/checklist.md +1 -1
- package/harness/bundles/java/claude-code/harness-submit/SKILL.md +3 -3
- package/harness/bundles/java/claude-code/harness-submit/checklist.md +30 -6
- package/harness/bundles/java/claude-code/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-test/SKILL.md +5 -5
- package/harness/bundles/java/claude-code/harness-test/pitfalls.md +16 -0
- package/harness/bundles/java/claude-code/harness-test/scripts/runtime-helpers.mjs +216 -0
- package/harness/bundles/java/claude-code/protocols/archive-report-protocol.md +1 -1
- package/harness/bundles/java/claude-code/protocols/ledger-protocol.md +55 -2
- package/harness/bundles/java/claude-code/protocols/report-pipeline-protocol.md +1 -8
- package/harness/bundles/java/claude-code/protocols/sensitive-info-protocol.md +13 -0
- package/harness/bundles/java/claude-code/protocols/state-layout-protocol.md +24 -0
- package/harness/bundles/java/claude-code/scripts/harness_archive.py +31 -14
- package/harness/bundles/java/claude-code/scripts/harness_check_gate.py +103 -0
- package/harness/bundles/java/claude-code/scripts/harness_deploy.py +179 -4
- package/harness/bundles/java/claude-code/scripts/harness_ledger.py +269 -3
- package/harness/bundles/java/claude-code/scripts/harness_preflight.py +72 -382
- package/harness/bundles/java/claude-code/scripts/harness_profile.py +742 -0
- package/harness/bundles/java/claude-code/scripts/harness_service.py +99 -2
- package/harness/bundles/java/claude-code/scripts/harness_state.py +179 -0
- package/harness/bundles/java/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-archive/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-archive/reference.md +2 -8
- package/harness/bundles/java/codebuddy/harness-codebase-map/SKILL.md +2 -1
- package/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge.py +474 -122
- package/harness/bundles/java/codebuddy/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-package/SKILL.md +3 -3
- package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +2 -1
- package/harness/bundles/java/codebuddy/harness-review/SKILL.md +5 -1
- package/harness/bundles/java/codebuddy/harness-run/SKILL.md +4 -4
- package/harness/bundles/java/codebuddy/harness-run/checklist.md +1 -1
- package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +3 -3
- package/harness/bundles/java/codebuddy/harness-submit/checklist.md +30 -6
- package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-test/SKILL.md +5 -5
- package/harness/bundles/java/codebuddy/harness-test/pitfalls.md +16 -0
- package/harness/bundles/java/codebuddy/harness-test/scripts/runtime-helpers.mjs +216 -0
- package/harness/bundles/java/codebuddy/protocols/archive-report-protocol.md +1 -1
- package/harness/bundles/java/codebuddy/protocols/ledger-protocol.md +55 -2
- package/harness/bundles/java/codebuddy/protocols/report-pipeline-protocol.md +1 -8
- package/harness/bundles/java/codebuddy/protocols/sensitive-info-protocol.md +13 -0
- package/harness/bundles/java/codebuddy/protocols/state-layout-protocol.md +24 -0
- package/harness/bundles/java/codebuddy/scripts/harness_archive.py +31 -14
- package/harness/bundles/java/codebuddy/scripts/harness_check_gate.py +103 -0
- package/harness/bundles/java/codebuddy/scripts/harness_deploy.py +179 -4
- package/harness/bundles/java/codebuddy/scripts/harness_ledger.py +269 -3
- package/harness/bundles/java/codebuddy/scripts/harness_preflight.py +72 -382
- package/harness/bundles/java/codebuddy/scripts/harness_profile.py +742 -0
- package/harness/bundles/java/codebuddy/scripts/harness_service.py +99 -2
- package/harness/bundles/java/codebuddy/scripts/harness_state.py +179 -0
- package/harness/bundles/java/codex/.harness-build.json +1 -1
- package/harness/bundles/java/codex/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-archive/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-archive/reference.md +2 -8
- package/harness/bundles/java/codex/harness-codebase-map/SKILL.md +2 -1
- package/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-knowledge-ingest/scripts/harness_knowledge.py +474 -122
- package/harness/bundles/java/codex/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-package/SKILL.md +3 -3
- package/harness/bundles/java/codex/harness-plan/SKILL.md +2 -1
- package/harness/bundles/java/codex/harness-review/SKILL.md +5 -1
- package/harness/bundles/java/codex/harness-run/SKILL.md +4 -4
- package/harness/bundles/java/codex/harness-run/checklist.md +1 -1
- package/harness/bundles/java/codex/harness-submit/SKILL.md +3 -3
- package/harness/bundles/java/codex/harness-submit/checklist.md +30 -6
- package/harness/bundles/java/codex/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-test/SKILL.md +5 -5
- package/harness/bundles/java/codex/harness-test/pitfalls.md +16 -0
- package/harness/bundles/java/codex/harness-test/scripts/runtime-helpers.mjs +216 -0
- package/harness/bundles/java/codex/protocols/archive-report-protocol.md +1 -1
- package/harness/bundles/java/codex/protocols/ledger-protocol.md +55 -2
- package/harness/bundles/java/codex/protocols/report-pipeline-protocol.md +1 -8
- package/harness/bundles/java/codex/protocols/sensitive-info-protocol.md +13 -0
- package/harness/bundles/java/codex/protocols/state-layout-protocol.md +24 -0
- package/harness/bundles/java/codex/scripts/harness_archive.py +31 -14
- package/harness/bundles/java/codex/scripts/harness_check_gate.py +103 -0
- package/harness/bundles/java/codex/scripts/harness_deploy.py +179 -4
- package/harness/bundles/java/codex/scripts/harness_ledger.py +269 -3
- package/harness/bundles/java/codex/scripts/harness_preflight.py +72 -382
- package/harness/bundles/java/codex/scripts/harness_profile.py +742 -0
- package/harness/bundles/java/codex/scripts/harness_service.py +99 -2
- package/harness/bundles/java/codex/scripts/harness_state.py +179 -0
- package/harness/bundles/java/cursor/.harness-build.json +1 -1
- package/harness/bundles/java/cursor/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-archive/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-archive/reference.md +2 -8
- package/harness/bundles/java/cursor/harness-codebase-map/SKILL.md +2 -1
- package/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-knowledge-ingest/scripts/harness_knowledge.py +474 -122
- package/harness/bundles/java/cursor/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-package/SKILL.md +3 -3
- package/harness/bundles/java/cursor/harness-plan/SKILL.md +2 -1
- package/harness/bundles/java/cursor/harness-review/SKILL.md +5 -1
- package/harness/bundles/java/cursor/harness-run/SKILL.md +4 -4
- package/harness/bundles/java/cursor/harness-run/checklist.md +1 -1
- package/harness/bundles/java/cursor/harness-submit/SKILL.md +3 -3
- package/harness/bundles/java/cursor/harness-submit/checklist.md +30 -6
- package/harness/bundles/java/cursor/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-test/SKILL.md +5 -5
- package/harness/bundles/java/cursor/harness-test/pitfalls.md +16 -0
- package/harness/bundles/java/cursor/harness-test/scripts/runtime-helpers.mjs +216 -0
- package/harness/bundles/java/cursor/protocols/archive-report-protocol.md +1 -1
- package/harness/bundles/java/cursor/protocols/ledger-protocol.md +55 -2
- package/harness/bundles/java/cursor/protocols/report-pipeline-protocol.md +1 -8
- package/harness/bundles/java/cursor/protocols/sensitive-info-protocol.md +13 -0
- package/harness/bundles/java/cursor/protocols/state-layout-protocol.md +24 -0
- package/harness/bundles/java/cursor/scripts/harness_archive.py +31 -14
- package/harness/bundles/java/cursor/scripts/harness_check_gate.py +103 -0
- package/harness/bundles/java/cursor/scripts/harness_deploy.py +179 -4
- package/harness/bundles/java/cursor/scripts/harness_ledger.py +269 -3
- package/harness/bundles/java/cursor/scripts/harness_preflight.py +72 -382
- package/harness/bundles/java/cursor/scripts/harness_profile.py +742 -0
- package/harness/bundles/java/cursor/scripts/harness_service.py +99 -2
- package/harness/bundles/java/cursor/scripts/harness_state.py +179 -0
- package/harness/manifests/general/claude-code.json +44 -28
- package/harness/manifests/general/codebuddy.json +44 -28
- package/harness/manifests/general/codex.json +44 -28
- package/harness/manifests/general/cursor.json +44 -28
- package/harness/manifests/java/claude-code.json +45 -29
- package/harness/manifests/java/codebuddy.json +45 -29
- package/harness/manifests/java/codex.json +45 -29
- package/harness/manifests/java/cursor.json +45 -29
- package/hunter-workflow-family.json +2 -2
- package/package.json +1 -1
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""Harness preflight: build-profile detect/check + quirk recording + agent precheck.
|
|
3
3
|
|
|
4
|
+
变更簇 4:detect/check 委托 ``harness_profile``(profile v2 公共内核),不再保留
|
|
5
|
+
v1 重复探测逻辑与 ``buildCommands`` 扁平字符串。record-quirk 适配 v2 ``commands``
|
|
6
|
+
结构:``fix-command`` 写 ``source=user`` override(spec §3.1 显式保留),跨 detect
|
|
7
|
+
保留;``skip-not-block`` 仍写 ``knownPreexistingErrors``。check-agents 与 pitfalls
|
|
8
|
+
附录为本 skill 独有,保留。
|
|
9
|
+
|
|
4
10
|
Implements DESIGN.md D5 (build-profile) and D8 (subagent precheck).
|
|
5
11
|
Python 3.10+ stdlib only. UTF-8 without BOM. Windows path friendly.
|
|
6
12
|
"""
|
|
@@ -9,12 +15,9 @@ from __future__ import annotations
|
|
|
9
15
|
|
|
10
16
|
import argparse
|
|
11
17
|
import datetime as dt
|
|
12
|
-
import hashlib
|
|
13
18
|
import json
|
|
14
|
-
import os
|
|
15
19
|
import re
|
|
16
|
-
import
|
|
17
|
-
import subprocess
|
|
20
|
+
import shlex
|
|
18
21
|
import sys
|
|
19
22
|
from pathlib import Path
|
|
20
23
|
from typing import Any
|
|
@@ -26,21 +29,16 @@ if hasattr(sys.stderr, "reconfigure"):
|
|
|
26
29
|
sys.stderr.reconfigure(encoding="utf-8")
|
|
27
30
|
|
|
28
31
|
|
|
29
|
-
|
|
30
|
-
|
|
32
|
+
import harness_profile as hprof # noqa: E402
|
|
33
|
+
|
|
34
|
+
|
|
31
35
|
PITFALLS_REL = Path(".harness") / "pitfalls.md"
|
|
32
36
|
PITFALLS_APPENDIX_HEADER = "## Preflight 附录(自动追加)"
|
|
33
37
|
|
|
34
|
-
DEFAULT_BUILD_COMMANDS: dict[str, str] = {
|
|
35
|
-
"compile": "",
|
|
36
|
-
"unitTest": "",
|
|
37
|
-
"unitTestFull": "",
|
|
38
|
-
"install": "",
|
|
39
|
-
"package": "",
|
|
40
|
-
}
|
|
41
|
-
|
|
42
38
|
VALID_QUIRK_ACTIONS = {"skip-not-block", "fix-command"}
|
|
43
39
|
|
|
40
|
+
_DETECT_HINT = "python harness_preflight.py detect --project <root> --json"
|
|
41
|
+
|
|
44
42
|
|
|
45
43
|
def now_iso() -> str:
|
|
46
44
|
return dt.datetime.now().astimezone().isoformat(timespec="seconds")
|
|
@@ -51,358 +49,30 @@ def emit_json(payload: dict[str, Any], *, ok: bool = True) -> int:
|
|
|
51
49
|
return 0 if ok else 1
|
|
52
50
|
|
|
53
51
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
def write_json(path: Path, data: Any) -> None:
|
|
59
|
-
path.parent.mkdir(parents=True, exist_ok=True)
|
|
60
|
-
text = json.dumps(data, ensure_ascii=False, indent=2) + "\n"
|
|
61
|
-
path.write_text(text, encoding="utf-8")
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def sha256_file(path: Path) -> str | None:
|
|
65
|
-
if not path.is_file():
|
|
66
|
-
return None
|
|
67
|
-
h = hashlib.sha256()
|
|
68
|
-
with path.open("rb") as fh:
|
|
69
|
-
for chunk in iter(lambda: fh.read(65536), b""):
|
|
70
|
-
h.update(chunk)
|
|
71
|
-
return h.hexdigest()
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
def find_root_pom(project: Path) -> Path | None:
|
|
75
|
-
candidates = [project / "pom.xml", project / "pom.xml.example"]
|
|
76
|
-
for c in candidates:
|
|
77
|
-
if c.is_file():
|
|
78
|
-
return c
|
|
79
|
-
return None
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
def which_tool(name: str) -> str | None:
|
|
83
|
-
found = shutil.which(name)
|
|
84
|
-
if found:
|
|
85
|
-
return str(Path(found).resolve())
|
|
86
|
-
# Windows: try .cmd / .bat / .exe explicitly
|
|
87
|
-
if os.name == "nt":
|
|
88
|
-
for ext in (".cmd", ".bat", ".exe"):
|
|
89
|
-
found = shutil.which(name + ext)
|
|
90
|
-
if found:
|
|
91
|
-
return str(Path(found).resolve())
|
|
92
|
-
return None
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
def run_version(tool_path: str | None, args: list[str], timeout: float = 8.0) -> str:
|
|
96
|
-
"""Best-effort version probe. Empty string if unavailable."""
|
|
97
|
-
if not tool_path:
|
|
98
|
-
return ""
|
|
99
|
-
try:
|
|
100
|
-
completed = subprocess.run(
|
|
101
|
-
[tool_path, *args],
|
|
102
|
-
capture_output=True,
|
|
103
|
-
text=True,
|
|
104
|
-
encoding="utf-8",
|
|
105
|
-
errors="replace",
|
|
106
|
-
timeout=timeout,
|
|
107
|
-
check=False,
|
|
108
|
-
)
|
|
109
|
-
except (OSError, subprocess.TimeoutExpired):
|
|
110
|
-
return ""
|
|
111
|
-
text = (completed.stdout or "") + "\n" + (completed.stderr or "")
|
|
112
|
-
return _extract_version(text)
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
def _extract_version(text: str) -> str:
|
|
116
|
-
# node: v20.11.0 / Apache Maven 3.9.6
|
|
117
|
-
m = re.search(r"\bv?(\d+\.\d+\.\d+(?:[-+][\w.]+)?)\b", text)
|
|
118
|
-
if m:
|
|
119
|
-
return m.group(1)
|
|
120
|
-
m = re.search(r"version\s+([^\s,]+)", text, re.IGNORECASE)
|
|
121
|
-
if m:
|
|
122
|
-
return m.group(1).strip()
|
|
123
|
-
line = next((ln.strip() for ln in text.splitlines() if ln.strip()), "")
|
|
124
|
-
return line[:120]
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
def is_executable_path(path_str: str | None) -> bool:
|
|
128
|
-
if not path_str:
|
|
129
|
-
return False
|
|
130
|
-
p = Path(path_str)
|
|
131
|
-
if not p.exists():
|
|
132
|
-
return False
|
|
133
|
-
if p.is_file():
|
|
134
|
-
if os.name == "nt":
|
|
135
|
-
return True
|
|
136
|
-
return os.access(p, os.X_OK)
|
|
137
|
-
# On Windows, shutil.which may return .cmd wrappers that exist
|
|
138
|
-
return p.exists()
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
def load_existing_profile(project: Path) -> dict[str, Any] | None:
|
|
142
|
-
path = project / PROFILE_REL
|
|
143
|
-
if not path.is_file():
|
|
144
|
-
return None
|
|
145
|
-
try:
|
|
146
|
-
data = read_json(path)
|
|
147
|
-
except (OSError, json.JSONDecodeError):
|
|
148
|
-
return None
|
|
149
|
-
return data if isinstance(data, dict) else None
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
def parse_claude_hints(project: Path) -> dict[str, str]:
|
|
153
|
-
"""Extract optional build command hints from CLAUDE.md / AGENTS.md."""
|
|
154
|
-
hints: dict[str, str] = {}
|
|
155
|
-
for name in ("CLAUDE.md", "AGENTS.md", ".claude/CLAUDE.md"):
|
|
156
|
-
path = project / name
|
|
157
|
-
if not path.is_file():
|
|
158
|
-
continue
|
|
159
|
-
try:
|
|
160
|
-
text = path.read_text(encoding="utf-8")
|
|
161
|
-
except OSError:
|
|
162
|
-
continue
|
|
163
|
-
# Look for fenced or inline mvn/npm command examples
|
|
164
|
-
for key, pattern in (
|
|
165
|
-
("compile", r"(?:compile|编译)[^\n`]*`([^`]+)`"),
|
|
166
|
-
("unitTest", r"(?:unit.?test|单元测试)[^\n`]*`([^`]+)`"),
|
|
167
|
-
("package", r"(?:package|打包)[^\n`]*`([^`]+)`"),
|
|
168
|
-
("install", r"(?:install|安装依赖)[^\n`]*`([^`]+)`"),
|
|
169
|
-
):
|
|
170
|
-
m = re.search(pattern, text, re.IGNORECASE)
|
|
171
|
-
if m and key not in hints:
|
|
172
|
-
hints[key] = m.group(1).strip()
|
|
173
|
-
# Direct mvn lines
|
|
174
|
-
for m in re.finditer(r"`(mvn[^`]+)`", text):
|
|
175
|
-
cmd = m.group(1).strip()
|
|
176
|
-
if "compile" in cmd and "compile" not in hints:
|
|
177
|
-
hints["compile"] = cmd
|
|
178
|
-
elif re.search(r"\btest\b", cmd) and "unitTest" not in hints:
|
|
179
|
-
hints["unitTest"] = cmd
|
|
180
|
-
elif "package" in cmd and "package" not in hints:
|
|
181
|
-
hints["package"] = cmd
|
|
182
|
-
elif "install" in cmd and "install" not in hints:
|
|
183
|
-
hints["install"] = cmd
|
|
184
|
-
return hints
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
def empty_profile_skeleton() -> dict[str, Any]:
|
|
188
|
-
return {
|
|
189
|
-
"schemaVersion": SCHEMA_VERSION,
|
|
190
|
-
"detectedAt": "",
|
|
191
|
-
"toolPaths": {"node": "", "mvn": ""},
|
|
192
|
-
"buildCommands": dict(DEFAULT_BUILD_COMMANDS),
|
|
193
|
-
"verificationInputs": {},
|
|
194
|
-
"serviceStart": {
|
|
195
|
-
"command": "",
|
|
196
|
-
"healthUrl": "",
|
|
197
|
-
"startTimeoutSec": 120,
|
|
198
|
-
"inputFiles": [],
|
|
199
|
-
"profile": "",
|
|
200
|
-
"overlayPath": "",
|
|
201
|
-
},
|
|
202
|
-
"knownPreexistingErrors": [],
|
|
203
|
-
"shellQuirks": [],
|
|
204
|
-
"fingerprint": {"mvnVersion": "", "nodeVersion": "", "pomHash": ""},
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
def merge_preserve_quirks(
|
|
209
|
-
base: dict[str, Any],
|
|
210
|
-
existing: dict[str, Any] | None,
|
|
211
|
-
) -> dict[str, Any]:
|
|
212
|
-
"""Keep human-curated quirks/errors/commands when re-detecting."""
|
|
213
|
-
if not existing:
|
|
214
|
-
return base
|
|
215
|
-
# Preserve knownPreexistingErrors / shellQuirks / filled buildCommands / serviceStart
|
|
216
|
-
if isinstance(existing.get("knownPreexistingErrors"), list):
|
|
217
|
-
base["knownPreexistingErrors"] = list(existing["knownPreexistingErrors"])
|
|
218
|
-
if isinstance(existing.get("shellQuirks"), list):
|
|
219
|
-
base["shellQuirks"] = list(existing["shellQuirks"])
|
|
220
|
-
existing_cmds = existing.get("buildCommands")
|
|
221
|
-
if isinstance(existing_cmds, dict):
|
|
222
|
-
for k, v in existing_cmds.items():
|
|
223
|
-
if isinstance(v, str) and v.strip():
|
|
224
|
-
base["buildCommands"][k] = v
|
|
225
|
-
existing_svc = existing.get("serviceStart")
|
|
226
|
-
if isinstance(existing_svc, dict):
|
|
227
|
-
merged_svc = dict(base.get("serviceStart") or {})
|
|
228
|
-
for k, v in existing_svc.items():
|
|
229
|
-
if v not in (None, ""):
|
|
230
|
-
merged_svc[k] = v
|
|
231
|
-
base["serviceStart"] = merged_svc
|
|
232
|
-
# 保留用户已配置的 verificationInputs(可能含 module 专属 glob),不覆盖。
|
|
233
|
-
if isinstance(existing.get("verificationInputs"), dict):
|
|
234
|
-
base["verificationInputs"] = dict(existing["verificationInputs"])
|
|
235
|
-
return base
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
def build_fingerprint(project: Path, node_path: str | None, mvn_path: str | None) -> dict[str, str]:
|
|
239
|
-
pom = find_root_pom(project)
|
|
240
|
-
pom_hash = sha256_file(pom) if pom else ""
|
|
241
|
-
return {
|
|
242
|
-
"mvnVersion": run_version(mvn_path, ["--version"]) if mvn_path else "",
|
|
243
|
-
"nodeVersion": run_version(node_path, ["--version"]) if node_path else "",
|
|
244
|
-
"pomHash": pom_hash or "",
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
def current_pom_hash(project: Path) -> str:
|
|
249
|
-
pom = find_root_pom(project)
|
|
250
|
-
return (sha256_file(pom) if pom else "") or ""
|
|
251
|
-
|
|
52
|
+
# ---------------------------------------------------------------------------
|
|
53
|
+
# detect / check — 委托 harness_profile(profile v2 公共内核)
|
|
54
|
+
# ---------------------------------------------------------------------------
|
|
252
55
|
|
|
253
56
|
def cmd_detect(project: Path) -> dict[str, Any]:
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
profile = empty_profile_skeleton()
|
|
257
|
-
|
|
258
|
-
node_path = which_tool("node")
|
|
259
|
-
mvn_path = which_tool("mvn")
|
|
260
|
-
profile["toolPaths"] = {
|
|
261
|
-
"node": node_path or "",
|
|
262
|
-
"mvn": mvn_path or "",
|
|
263
|
-
}
|
|
264
|
-
profile["fingerprint"] = build_fingerprint(project, node_path, mvn_path)
|
|
265
|
-
profile["detectedAt"] = now_iso()
|
|
266
|
-
|
|
267
|
-
hints = parse_claude_hints(project)
|
|
268
|
-
cmds = dict(DEFAULT_BUILD_COMMANDS)
|
|
269
|
-
cmds.update({k: v for k, v in hints.items() if k in cmds})
|
|
270
|
-
# Sensible Java placeholders when pom exists but no hints
|
|
271
|
-
if find_root_pom(project) and not any(cmds.values()):
|
|
272
|
-
cmds["compile"] = "mvn -f pom.xml compile -o -q"
|
|
273
|
-
cmds["unitTest"] = "mvn -f pom.xml test -Dtest={testClasses} -o"
|
|
274
|
-
cmds["unitTestFull"] = "mvn -f pom.xml test -o"
|
|
275
|
-
cmds["install"] = "mvn install -pl {modules} -am -DskipTests -nsu"
|
|
276
|
-
cmds["package"] = "mvn -f pom.xml package '-Dmaven.test.skip=true'"
|
|
277
|
-
profile["buildCommands"] = cmds
|
|
278
|
-
|
|
279
|
-
# Java 项目给 verificationInputs.unitTestFull 一个根级默认闭包
|
|
280
|
-
# (多 module 项目用户可改为 module/pom.xml + module/src/**)。通用/无 pom
|
|
281
|
-
# 项目保持空 {} —— can-reuse --profile-input unitTestFull 会返回
|
|
282
|
-
# insufficient-evidence,执行全量测试但不允许缓存复用,直到 profile 配置好。
|
|
283
|
-
if find_root_pom(project):
|
|
284
|
-
# A reactor-level Maven test can run every module, so its reuse
|
|
285
|
-
# fingerprint must include every module's pom and source roots.
|
|
286
|
-
module_poms = sorted(
|
|
287
|
-
pom.relative_to(project).as_posix()
|
|
288
|
-
for pom in project.rglob("pom.xml")
|
|
289
|
-
if pom.is_file() and ".harness" not in pom.parts and ".git" not in pom.parts
|
|
290
|
-
)
|
|
291
|
-
full_inputs: list[str] = []
|
|
292
|
-
for pom in module_poms:
|
|
293
|
-
full_inputs.append(pom)
|
|
294
|
-
parent = Path(pom).parent
|
|
295
|
-
prefix = "" if str(parent) == "." else f"{parent.as_posix()}/"
|
|
296
|
-
full_inputs.extend([f"{prefix}src/main/**", f"{prefix}src/test/**"])
|
|
297
|
-
profile["verificationInputs"] = {"unitTestFull": full_inputs}
|
|
298
|
-
|
|
299
|
-
profile = merge_preserve_quirks(profile, existing)
|
|
300
|
-
|
|
301
|
-
# Idempotent fingerprint/toolPaths overwrite from fresh detect;
|
|
302
|
-
# detectedAt updates each run (tests compare ignoring it).
|
|
303
|
-
out_path = project / PROFILE_REL
|
|
304
|
-
write_json(out_path, profile)
|
|
305
|
-
|
|
306
|
-
return {
|
|
307
|
-
"ok": True,
|
|
308
|
-
"action": "detect",
|
|
309
|
-
"project": str(project),
|
|
310
|
-
"profilePath": str(out_path),
|
|
311
|
-
"profile": profile,
|
|
312
|
-
"created": existing is None,
|
|
313
|
-
"updated": True,
|
|
314
|
-
}
|
|
57
|
+
"""Full probe and write build-profile.json (v2). 委托 hprof.detect。"""
|
|
58
|
+
return hprof.detect(project)
|
|
315
59
|
|
|
316
60
|
|
|
317
61
|
def cmd_check(project: Path) -> dict[str, Any]:
|
|
318
|
-
"""
|
|
62
|
+
"""Fast stale check (missing/invalid/stale/ready). 委托 hprof.check。
|
|
319
63
|
|
|
320
|
-
|
|
64
|
+
兼容:stale 时补 ``hint``(skill canonical 读 hint 引导重新 detect)。
|
|
321
65
|
"""
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
stale = False
|
|
326
|
-
|
|
327
|
-
if not profile_path.is_file():
|
|
328
|
-
return {
|
|
329
|
-
"ok": False,
|
|
330
|
-
"hardFailure": True,
|
|
331
|
-
"action": "check",
|
|
332
|
-
"project": str(project),
|
|
333
|
-
"stale": True,
|
|
334
|
-
"issues": ["build-profile.json missing; run detect"],
|
|
335
|
-
"hint": "python harness_preflight.py detect --project <root> --json",
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
try:
|
|
339
|
-
profile = read_json(profile_path)
|
|
340
|
-
except (OSError, json.JSONDecodeError) as exc:
|
|
341
|
-
return {
|
|
342
|
-
"ok": False,
|
|
343
|
-
"hardFailure": True,
|
|
344
|
-
"action": "check",
|
|
345
|
-
"project": str(project),
|
|
346
|
-
"stale": True,
|
|
347
|
-
"issues": [f"build-profile.json unreadable: {exc}"],
|
|
348
|
-
"hint": "python harness_preflight.py detect --project <root> --json",
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
if not isinstance(profile, dict):
|
|
352
|
-
return {
|
|
353
|
-
"ok": False,
|
|
354
|
-
"hardFailure": True,
|
|
355
|
-
"action": "check",
|
|
356
|
-
"project": str(project),
|
|
357
|
-
"stale": True,
|
|
358
|
-
"issues": ["build-profile.json is not an object"],
|
|
359
|
-
"hint": "python harness_preflight.py detect --project <root> --json",
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
tool_paths = profile.get("toolPaths") or {}
|
|
363
|
-
if not isinstance(tool_paths, dict):
|
|
364
|
-
tool_paths = {}
|
|
365
|
-
|
|
366
|
-
for tool_name in ("node", "mvn"):
|
|
367
|
-
path_str = tool_paths.get(tool_name) or ""
|
|
368
|
-
if not path_str:
|
|
369
|
-
# Empty is allowed (tool not present at detect time)
|
|
370
|
-
continue
|
|
371
|
-
if not is_executable_path(path_str):
|
|
372
|
-
stale = True
|
|
373
|
-
issues.append(f"toolPaths.{tool_name} missing or not executable: {path_str}")
|
|
374
|
-
|
|
375
|
-
fp = profile.get("fingerprint") or {}
|
|
376
|
-
if not isinstance(fp, dict):
|
|
377
|
-
fp = {}
|
|
378
|
-
stored_pom = fp.get("pomHash") or ""
|
|
379
|
-
current_pom = current_pom_hash(project)
|
|
380
|
-
if stored_pom != current_pom:
|
|
381
|
-
stale = True
|
|
382
|
-
issues.append(
|
|
383
|
-
f"fingerprint.pomHash changed: stored={stored_pom or '(empty)'} "
|
|
384
|
-
f"current={current_pom or '(empty)'}"
|
|
385
|
-
)
|
|
386
|
-
|
|
387
|
-
# If profile recorded a tool version but path is now gone → already covered.
|
|
388
|
-
# If pom appeared/disappeared relative to empty hash → covered above.
|
|
389
|
-
|
|
390
|
-
result = {
|
|
391
|
-
"ok": not stale,
|
|
392
|
-
"action": "check",
|
|
393
|
-
"project": str(project),
|
|
394
|
-
"stale": stale,
|
|
395
|
-
"issues": issues,
|
|
396
|
-
"fingerprint": {
|
|
397
|
-
"stored": {"pomHash": stored_pom, "mvnVersion": fp.get("mvnVersion", ""), "nodeVersion": fp.get("nodeVersion", "")},
|
|
398
|
-
"current": {"pomHash": current_pom},
|
|
399
|
-
},
|
|
400
|
-
}
|
|
401
|
-
if stale:
|
|
402
|
-
result["hint"] = "python harness_preflight.py detect --project <root> --json"
|
|
66
|
+
result = hprof.check(project)
|
|
67
|
+
if result.get("stale") and "hint" not in result:
|
|
68
|
+
result["hint"] = _DETECT_HINT
|
|
403
69
|
return result
|
|
404
70
|
|
|
405
71
|
|
|
72
|
+
# ---------------------------------------------------------------------------
|
|
73
|
+
# record-quirk — 适配 v2 commands(source=user override)
|
|
74
|
+
# ---------------------------------------------------------------------------
|
|
75
|
+
|
|
406
76
|
def ensure_pitfalls_appendix(project: Path) -> Path:
|
|
407
77
|
path = project / PITFALLS_REL
|
|
408
78
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
@@ -428,12 +98,26 @@ def append_pitfalls_line(project: Path, line: str) -> Path:
|
|
|
428
98
|
text = path.read_text(encoding="utf-8")
|
|
429
99
|
if not text.endswith("\n"):
|
|
430
100
|
text += "\n"
|
|
431
|
-
# Append after appendix header block (end of file is fine for append-only)
|
|
432
101
|
text += line.rstrip() + "\n"
|
|
433
102
|
path.write_text(text, encoding="utf-8")
|
|
434
103
|
return path
|
|
435
104
|
|
|
436
105
|
|
|
106
|
+
def _load_or_skeleton(project: Path) -> tuple[dict[str, Any], Path]:
|
|
107
|
+
"""加载 v2 profile;缺失时返回空 skeleton(不自动 detect,避免副作用)。"""
|
|
108
|
+
profile_path = project / hprof.PROFILE_REL
|
|
109
|
+
if profile_path.is_file():
|
|
110
|
+
try:
|
|
111
|
+
profile = hprof.read_json(profile_path)
|
|
112
|
+
if isinstance(profile, dict):
|
|
113
|
+
return profile, profile_path
|
|
114
|
+
except (OSError, json.JSONDecodeError):
|
|
115
|
+
pass
|
|
116
|
+
profile = hprof.empty_profile_skeleton(hprof.DEFAULT_EXCLUDED_ROOTS)
|
|
117
|
+
profile["detectedAt"] = now_iso()
|
|
118
|
+
return profile, profile_path
|
|
119
|
+
|
|
120
|
+
|
|
437
121
|
def cmd_record_quirk(
|
|
438
122
|
project: Path,
|
|
439
123
|
pattern: str,
|
|
@@ -449,28 +133,17 @@ def cmd_record_quirk(
|
|
|
449
133
|
"issues": [f"invalid action: {action}; expected one of {sorted(VALID_QUIRK_ACTIONS)}"],
|
|
450
134
|
}
|
|
451
135
|
|
|
452
|
-
profile_path = project
|
|
453
|
-
if profile_path.is_file():
|
|
454
|
-
try:
|
|
455
|
-
profile = read_json(profile_path)
|
|
456
|
-
if not isinstance(profile, dict):
|
|
457
|
-
profile = empty_profile_skeleton()
|
|
458
|
-
except (OSError, json.JSONDecodeError):
|
|
459
|
-
profile = empty_profile_skeleton()
|
|
460
|
-
else:
|
|
461
|
-
profile = empty_profile_skeleton()
|
|
462
|
-
profile["detectedAt"] = now_iso()
|
|
136
|
+
profile, profile_path = _load_or_skeleton(project)
|
|
463
137
|
|
|
464
138
|
profile.setdefault("knownPreexistingErrors", [])
|
|
465
139
|
profile.setdefault("shellQuirks", [])
|
|
466
|
-
profile.setdefault("
|
|
467
|
-
|
|
140
|
+
profile.setdefault("commands", {})
|
|
468
141
|
if not isinstance(profile["knownPreexistingErrors"], list):
|
|
469
142
|
profile["knownPreexistingErrors"] = []
|
|
470
143
|
if not isinstance(profile["shellQuirks"], list):
|
|
471
144
|
profile["shellQuirks"] = []
|
|
472
|
-
if not isinstance(profile["
|
|
473
|
-
profile["
|
|
145
|
+
if not isinstance(profile["commands"], dict):
|
|
146
|
+
profile["commands"] = {}
|
|
474
147
|
|
|
475
148
|
changed: list[str] = []
|
|
476
149
|
|
|
@@ -494,16 +167,29 @@ def cmd_record_quirk(
|
|
|
494
167
|
else:
|
|
495
168
|
changed.append("shellQuirks(already-present)")
|
|
496
169
|
if fixed_command:
|
|
497
|
-
#
|
|
498
|
-
#
|
|
499
|
-
if pattern in
|
|
170
|
+
# v2:写 commands.<key> 为 source=user override(spec §3.1 显式保留)。
|
|
171
|
+
# 已知 verification key 覆写其 command;否则存 namespaced custom key。
|
|
172
|
+
if pattern in hprof.VERIFICATION_KEYS:
|
|
500
173
|
key = pattern
|
|
501
174
|
else:
|
|
502
175
|
key = f"custom:{pattern}"
|
|
503
|
-
profile["
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
176
|
+
existing_cmd = profile["commands"].get(key, {})
|
|
177
|
+
if not isinstance(existing_cmd, dict):
|
|
178
|
+
existing_cmd = {}
|
|
179
|
+
new_cmd = dict(existing_cmd)
|
|
180
|
+
new_cmd["command"] = fixed_command
|
|
181
|
+
try:
|
|
182
|
+
new_cmd["argvTemplate"] = shlex.split(fixed_command)
|
|
183
|
+
except ValueError:
|
|
184
|
+
# 不平衡引号回退到空格切分(best-effort;record-quirk 是人工逃逸口)
|
|
185
|
+
new_cmd["argvTemplate"] = fixed_command.split()
|
|
186
|
+
new_cmd["source"] = "user"
|
|
187
|
+
profile["commands"][key] = new_cmd
|
|
188
|
+
changed.append(f"commands.{key}")
|
|
189
|
+
# 重派 verificationInputs 兼容字段,保持与 commands 一致
|
|
190
|
+
hprof._derive_verification_inputs(profile)
|
|
191
|
+
|
|
192
|
+
hprof.write_json(profile_path, profile)
|
|
507
193
|
|
|
508
194
|
date_str = dt.date.today().isoformat()
|
|
509
195
|
pitfalls_line = f"- {date_str}: `{pattern}` — {reason} (action={action}"
|
|
@@ -528,6 +214,10 @@ def cmd_record_quirk(
|
|
|
528
214
|
}
|
|
529
215
|
|
|
530
216
|
|
|
217
|
+
# ---------------------------------------------------------------------------
|
|
218
|
+
# check-agents — agent 定义可用性校验(本 skill 独有,不涉及 profile)
|
|
219
|
+
# ---------------------------------------------------------------------------
|
|
220
|
+
|
|
531
221
|
def parse_frontmatter(text: str) -> tuple[dict[str, Any] | None, str | None]:
|
|
532
222
|
"""Parse YAML-ish frontmatter between leading --- fences.
|
|
533
223
|
|
|
@@ -756,9 +446,9 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
756
446
|
return emit_json(result, ok=bool(result.get("ok", True)))
|
|
757
447
|
if args.command == "check":
|
|
758
448
|
result = cmd_check(args.project)
|
|
759
|
-
#
|
|
760
|
-
|
|
761
|
-
return emit_json(result, ok=not
|
|
449
|
+
# missing/invalid = hard failure (exit 1); stale/ready exit 0 (callers branch on JSON)
|
|
450
|
+
hard = result.get("status") in ("missing", "invalid")
|
|
451
|
+
return emit_json(result, ok=not hard)
|
|
762
452
|
if args.command == "record-quirk":
|
|
763
453
|
result = cmd_record_quirk(
|
|
764
454
|
args.project,
|