hunter-harness 0.2.0 → 0.2.1
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/dist/bin.js +659 -230
- package/package.json +9 -7
- package/resources/harness/bundles/general/claude-code/.harness-build.json +0 -6
- package/resources/harness/bundles/general/claude-code/CONTEXT.md +0 -65
- package/resources/harness/bundles/general/claude-code/README.md +0 -478
- package/resources/harness/bundles/general/claude-code/agents/harness-evaluator.md +0 -96
- package/resources/harness/bundles/general/claude-code/agents/harness-explorer.md +0 -72
- package/resources/harness/bundles/general/claude-code/agents/harness-reviewer.md +0 -69
- package/resources/harness/bundles/general/claude-code/harness-archive/SKILL.md +0 -215
- package/resources/harness/bundles/general/claude-code/harness-archive/checklist.md +0 -52
- package/resources/harness/bundles/general/claude-code/harness-archive/reference.md +0 -103
- package/resources/harness/bundles/general/claude-code/harness-archive/scripts/gen-manifest.ps1 +0 -31
- package/resources/harness/bundles/general/claude-code/harness-archive/templates/render-summary.mjs +0 -171
- package/resources/harness/bundles/general/claude-code/harness-archive/templates/summary-data-template.json +0 -128
- package/resources/harness/bundles/general/claude-code/harness-codebase-map/SKILL.md +0 -112
- package/resources/harness/bundles/general/claude-code/harness-codebase-map/checklist.md +0 -126
- package/resources/harness/bundles/general/claude-code/harness-codebase-map/reference.md +0 -364
- package/resources/harness/bundles/general/claude-code/harness-codebase-map/templates/ARCHITECTURE.md +0 -57
- package/resources/harness/bundles/general/claude-code/harness-codebase-map/templates/CONCERNS.md +0 -49
- package/resources/harness/bundles/general/claude-code/harness-codebase-map/templates/CONVENTIONS.md +0 -57
- package/resources/harness/bundles/general/claude-code/harness-codebase-map/templates/INTEGRATIONS.md +0 -52
- package/resources/harness/bundles/general/claude-code/harness-codebase-map/templates/STACK.md +0 -55
- package/resources/harness/bundles/general/claude-code/harness-codebase-map/templates/STRUCTURE.md +0 -69
- package/resources/harness/bundles/general/claude-code/harness-codebase-map/templates/TESTING.md +0 -64
- package/resources/harness/bundles/general/claude-code/harness-codebase-map/templates/map-manifest.schema.json +0 -73
- package/resources/harness/bundles/general/claude-code/harness-codebase-map/templates/map-summary.md +0 -64
- package/resources/harness/bundles/general/claude-code/harness-knowledge-ingest/SKILL.md +0 -246
- package/resources/harness/bundles/general/claude-code/harness-knowledge-ingest/design.md +0 -842
- package/resources/harness/bundles/general/claude-code/harness-knowledge-ingest/evaluations/harness_knowledge_evaluation.xml +0 -42
- package/resources/harness/bundles/general/claude-code/harness-knowledge-ingest/mcp-config.example.json +0 -10
- package/resources/harness/bundles/general/claude-code/harness-knowledge-ingest/reference.md +0 -309
- package/resources/harness/bundles/general/claude-code/harness-knowledge-ingest/scripts/harness_knowledge.py +0 -4082
- package/resources/harness/bundles/general/claude-code/harness-knowledge-ingest/scripts/harness_knowledge_mcp.py +0 -273
- package/resources/harness/bundles/general/claude-code/harness-knowledge-query/SKILL.md +0 -164
- package/resources/harness/bundles/general/claude-code/harness-plan/SKILL.md +0 -127
- package/resources/harness/bundles/general/claude-code/harness-plan/checklist.md +0 -243
- package/resources/harness/bundles/general/claude-code/harness-plan/protocols.md +0 -124
- package/resources/harness/bundles/general/claude-code/harness-plan/reference.md +0 -376
- package/resources/harness/bundles/general/claude-code/harness-review/SKILL.md +0 -160
- package/resources/harness/bundles/general/claude-code/harness-review/checklist.md +0 -124
- package/resources/harness/bundles/general/claude-code/harness-review/protocols.md +0 -68
- package/resources/harness/bundles/general/claude-code/harness-review/reference.md +0 -86
- package/resources/harness/bundles/general/claude-code/harness-run/SKILL.md +0 -132
- package/resources/harness/bundles/general/claude-code/harness-run/checklist.md +0 -259
- package/resources/harness/bundles/general/claude-code/harness-run/protocols.md +0 -78
- package/resources/harness/bundles/general/claude-code/harness-run/reference.md +0 -834
- package/resources/harness/bundles/general/claude-code/harness-submit/SKILL.md +0 -159
- package/resources/harness/bundles/general/claude-code/harness-submit/checklist.md +0 -407
- package/resources/harness/bundles/general/claude-code/harness-submit/reference.md +0 -152
- package/resources/harness/bundles/general/claude-code/harness-sync/SKILL.md +0 -82
- package/resources/harness/bundles/general/claude-code/harness-sync/reference.md +0 -153
- package/resources/harness/bundles/general/claude-code/harness-test/SKILL.md +0 -180
- package/resources/harness/bundles/general/claude-code/harness-test/checklist.md +0 -319
- package/resources/harness/bundles/general/claude-code/harness-test/pitfalls.md +0 -260
- package/resources/harness/bundles/general/claude-code/harness-test/reference.md +0 -791
- package/resources/harness/bundles/general/claude-code/protocols/archive-report-protocol.md +0 -175
- package/resources/harness/bundles/general/claude-code/protocols/evidence-based-reporting-protocol.md +0 -134
- package/resources/harness/bundles/general/claude-code/protocols/ledger-protocol.md +0 -270
- package/resources/harness/bundles/general/claude-code/protocols/powershell-protocol.md +0 -181
- package/resources/harness/bundles/general/claude-code/protocols/report-pipeline-protocol.md +0 -150
- package/resources/harness/bundles/general/claude-code/protocols/sensitive-info-protocol.md +0 -92
- package/resources/harness/bundles/general/claude-code/protocols/state-layout-protocol.md +0 -131
- package/resources/harness/bundles/general/claude-code/protocols/submit-protocol.md +0 -78
- package/resources/harness/bundles/general/claude-code/scripts/harness_archive.py +0 -2319
- package/resources/harness/bundles/general/claude-code/scripts/harness_deploy.py +0 -674
- package/resources/harness/bundles/general/claude-code/scripts/harness_events.py +0 -680
- package/resources/harness/bundles/general/claude-code/scripts/harness_ledger.py +0 -671
- package/resources/harness/bundles/general/claude-code/scripts/harness_preflight.py +0 -780
- package/resources/harness/bundles/general/claude-code/scripts/harness_service.py +0 -1331
- package/resources/harness/bundles/general/codebuddy/.harness-build.json +0 -6
- package/resources/harness/bundles/general/codebuddy/CONTEXT.md +0 -65
- package/resources/harness/bundles/general/codebuddy/README.md +0 -478
- package/resources/harness/bundles/general/codebuddy/agents/harness-evaluator.md +0 -94
- package/resources/harness/bundles/general/codebuddy/agents/harness-explorer.md +0 -53
- package/resources/harness/bundles/general/codebuddy/agents/harness-reviewer.md +0 -52
- package/resources/harness/bundles/general/codebuddy/harness-archive/SKILL.md +0 -198
- package/resources/harness/bundles/general/codebuddy/harness-archive/checklist.md +0 -52
- package/resources/harness/bundles/general/codebuddy/harness-archive/reference.md +0 -103
- package/resources/harness/bundles/general/codebuddy/harness-archive/scripts/gen-manifest.ps1 +0 -31
- package/resources/harness/bundles/general/codebuddy/harness-archive/templates/render-summary.mjs +0 -171
- package/resources/harness/bundles/general/codebuddy/harness-archive/templates/summary-data-template.json +0 -128
- package/resources/harness/bundles/general/codebuddy/harness-codebase-map/SKILL.md +0 -95
- package/resources/harness/bundles/general/codebuddy/harness-codebase-map/checklist.md +0 -126
- package/resources/harness/bundles/general/codebuddy/harness-codebase-map/reference.md +0 -364
- package/resources/harness/bundles/general/codebuddy/harness-codebase-map/templates/ARCHITECTURE.md +0 -57
- package/resources/harness/bundles/general/codebuddy/harness-codebase-map/templates/CONCERNS.md +0 -49
- package/resources/harness/bundles/general/codebuddy/harness-codebase-map/templates/CONVENTIONS.md +0 -57
- package/resources/harness/bundles/general/codebuddy/harness-codebase-map/templates/INTEGRATIONS.md +0 -52
- package/resources/harness/bundles/general/codebuddy/harness-codebase-map/templates/STACK.md +0 -55
- package/resources/harness/bundles/general/codebuddy/harness-codebase-map/templates/STRUCTURE.md +0 -69
- package/resources/harness/bundles/general/codebuddy/harness-codebase-map/templates/TESTING.md +0 -64
- package/resources/harness/bundles/general/codebuddy/harness-codebase-map/templates/map-manifest.schema.json +0 -73
- package/resources/harness/bundles/general/codebuddy/harness-codebase-map/templates/map-summary.md +0 -64
- package/resources/harness/bundles/general/codebuddy/harness-knowledge-ingest/SKILL.md +0 -229
- package/resources/harness/bundles/general/codebuddy/harness-knowledge-ingest/design.md +0 -842
- package/resources/harness/bundles/general/codebuddy/harness-knowledge-ingest/evaluations/harness_knowledge_evaluation.xml +0 -42
- package/resources/harness/bundles/general/codebuddy/harness-knowledge-ingest/mcp-config.example.json +0 -10
- package/resources/harness/bundles/general/codebuddy/harness-knowledge-ingest/reference.md +0 -309
- package/resources/harness/bundles/general/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge.py +0 -4082
- package/resources/harness/bundles/general/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge_mcp.py +0 -273
- package/resources/harness/bundles/general/codebuddy/harness-knowledge-query/SKILL.md +0 -147
- package/resources/harness/bundles/general/codebuddy/harness-plan/SKILL.md +0 -109
- package/resources/harness/bundles/general/codebuddy/harness-plan/checklist.md +0 -243
- package/resources/harness/bundles/general/codebuddy/harness-plan/protocols.md +0 -124
- package/resources/harness/bundles/general/codebuddy/harness-plan/reference.md +0 -376
- package/resources/harness/bundles/general/codebuddy/harness-review/SKILL.md +0 -142
- package/resources/harness/bundles/general/codebuddy/harness-review/checklist.md +0 -124
- package/resources/harness/bundles/general/codebuddy/harness-review/protocols.md +0 -68
- package/resources/harness/bundles/general/codebuddy/harness-review/reference.md +0 -86
- package/resources/harness/bundles/general/codebuddy/harness-run/SKILL.md +0 -113
- package/resources/harness/bundles/general/codebuddy/harness-run/checklist.md +0 -259
- package/resources/harness/bundles/general/codebuddy/harness-run/protocols.md +0 -78
- package/resources/harness/bundles/general/codebuddy/harness-run/reference.md +0 -834
- package/resources/harness/bundles/general/codebuddy/harness-submit/SKILL.md +0 -142
- package/resources/harness/bundles/general/codebuddy/harness-submit/checklist.md +0 -407
- package/resources/harness/bundles/general/codebuddy/harness-submit/reference.md +0 -152
- package/resources/harness/bundles/general/codebuddy/harness-sync/SKILL.md +0 -65
- package/resources/harness/bundles/general/codebuddy/harness-sync/reference.md +0 -153
- package/resources/harness/bundles/general/codebuddy/harness-test/SKILL.md +0 -160
- package/resources/harness/bundles/general/codebuddy/harness-test/checklist.md +0 -319
- package/resources/harness/bundles/general/codebuddy/harness-test/pitfalls.md +0 -260
- package/resources/harness/bundles/general/codebuddy/harness-test/reference.md +0 -791
- package/resources/harness/bundles/general/codebuddy/protocols/archive-report-protocol.md +0 -175
- package/resources/harness/bundles/general/codebuddy/protocols/evidence-based-reporting-protocol.md +0 -134
- package/resources/harness/bundles/general/codebuddy/protocols/ledger-protocol.md +0 -270
- package/resources/harness/bundles/general/codebuddy/protocols/powershell-protocol.md +0 -181
- package/resources/harness/bundles/general/codebuddy/protocols/report-pipeline-protocol.md +0 -150
- package/resources/harness/bundles/general/codebuddy/protocols/sensitive-info-protocol.md +0 -92
- package/resources/harness/bundles/general/codebuddy/protocols/state-layout-protocol.md +0 -131
- package/resources/harness/bundles/general/codebuddy/protocols/submit-protocol.md +0 -78
- package/resources/harness/bundles/general/codebuddy/scripts/harness_archive.py +0 -2319
- package/resources/harness/bundles/general/codebuddy/scripts/harness_deploy.py +0 -674
- package/resources/harness/bundles/general/codebuddy/scripts/harness_events.py +0 -680
- package/resources/harness/bundles/general/codebuddy/scripts/harness_ledger.py +0 -671
- package/resources/harness/bundles/general/codebuddy/scripts/harness_preflight.py +0 -780
- package/resources/harness/bundles/general/codebuddy/scripts/harness_service.py +0 -1331
- package/resources/harness/bundles/general/codex/.harness-build.json +0 -6
- package/resources/harness/bundles/general/codex/CONTEXT.md +0 -65
- package/resources/harness/bundles/general/codex/README.md +0 -478
- package/resources/harness/bundles/general/codex/harness-archive/SKILL.md +0 -198
- package/resources/harness/bundles/general/codex/harness-archive/checklist.md +0 -52
- package/resources/harness/bundles/general/codex/harness-archive/reference.md +0 -103
- package/resources/harness/bundles/general/codex/harness-archive/scripts/gen-manifest.ps1 +0 -31
- package/resources/harness/bundles/general/codex/harness-archive/templates/render-summary.mjs +0 -171
- package/resources/harness/bundles/general/codex/harness-archive/templates/summary-data-template.json +0 -128
- package/resources/harness/bundles/general/codex/harness-codebase-map/SKILL.md +0 -95
- package/resources/harness/bundles/general/codex/harness-codebase-map/checklist.md +0 -126
- package/resources/harness/bundles/general/codex/harness-codebase-map/reference.md +0 -364
- package/resources/harness/bundles/general/codex/harness-codebase-map/templates/ARCHITECTURE.md +0 -57
- package/resources/harness/bundles/general/codex/harness-codebase-map/templates/CONCERNS.md +0 -49
- package/resources/harness/bundles/general/codex/harness-codebase-map/templates/CONVENTIONS.md +0 -57
- package/resources/harness/bundles/general/codex/harness-codebase-map/templates/INTEGRATIONS.md +0 -52
- package/resources/harness/bundles/general/codex/harness-codebase-map/templates/STACK.md +0 -55
- package/resources/harness/bundles/general/codex/harness-codebase-map/templates/STRUCTURE.md +0 -69
- package/resources/harness/bundles/general/codex/harness-codebase-map/templates/TESTING.md +0 -64
- package/resources/harness/bundles/general/codex/harness-codebase-map/templates/map-manifest.schema.json +0 -73
- package/resources/harness/bundles/general/codex/harness-codebase-map/templates/map-summary.md +0 -64
- package/resources/harness/bundles/general/codex/harness-knowledge-ingest/SKILL.md +0 -229
- package/resources/harness/bundles/general/codex/harness-knowledge-ingest/design.md +0 -842
- package/resources/harness/bundles/general/codex/harness-knowledge-ingest/evaluations/harness_knowledge_evaluation.xml +0 -42
- package/resources/harness/bundles/general/codex/harness-knowledge-ingest/mcp-config.example.json +0 -10
- package/resources/harness/bundles/general/codex/harness-knowledge-ingest/reference.md +0 -309
- package/resources/harness/bundles/general/codex/harness-knowledge-ingest/scripts/harness_knowledge.py +0 -4082
- package/resources/harness/bundles/general/codex/harness-knowledge-ingest/scripts/harness_knowledge_mcp.py +0 -273
- package/resources/harness/bundles/general/codex/harness-knowledge-query/SKILL.md +0 -147
- package/resources/harness/bundles/general/codex/harness-plan/SKILL.md +0 -109
- package/resources/harness/bundles/general/codex/harness-plan/checklist.md +0 -243
- package/resources/harness/bundles/general/codex/harness-plan/protocols.md +0 -124
- package/resources/harness/bundles/general/codex/harness-plan/reference.md +0 -376
- package/resources/harness/bundles/general/codex/harness-review/SKILL.md +0 -137
- package/resources/harness/bundles/general/codex/harness-review/checklist.md +0 -124
- package/resources/harness/bundles/general/codex/harness-review/protocols.md +0 -68
- package/resources/harness/bundles/general/codex/harness-review/reference.md +0 -86
- package/resources/harness/bundles/general/codex/harness-run/SKILL.md +0 -113
- package/resources/harness/bundles/general/codex/harness-run/checklist.md +0 -259
- package/resources/harness/bundles/general/codex/harness-run/protocols.md +0 -78
- package/resources/harness/bundles/general/codex/harness-run/reference.md +0 -834
- package/resources/harness/bundles/general/codex/harness-submit/SKILL.md +0 -142
- package/resources/harness/bundles/general/codex/harness-submit/checklist.md +0 -407
- package/resources/harness/bundles/general/codex/harness-submit/reference.md +0 -152
- package/resources/harness/bundles/general/codex/harness-sync/SKILL.md +0 -65
- package/resources/harness/bundles/general/codex/harness-sync/reference.md +0 -153
- package/resources/harness/bundles/general/codex/harness-test/SKILL.md +0 -160
- package/resources/harness/bundles/general/codex/harness-test/checklist.md +0 -319
- package/resources/harness/bundles/general/codex/harness-test/pitfalls.md +0 -260
- package/resources/harness/bundles/general/codex/harness-test/reference.md +0 -791
- package/resources/harness/bundles/general/codex/protocols/archive-report-protocol.md +0 -175
- package/resources/harness/bundles/general/codex/protocols/evidence-based-reporting-protocol.md +0 -134
- package/resources/harness/bundles/general/codex/protocols/ledger-protocol.md +0 -270
- package/resources/harness/bundles/general/codex/protocols/powershell-protocol.md +0 -181
- package/resources/harness/bundles/general/codex/protocols/report-pipeline-protocol.md +0 -150
- package/resources/harness/bundles/general/codex/protocols/sensitive-info-protocol.md +0 -92
- package/resources/harness/bundles/general/codex/protocols/state-layout-protocol.md +0 -131
- package/resources/harness/bundles/general/codex/protocols/submit-protocol.md +0 -78
- package/resources/harness/bundles/general/codex/scripts/harness_archive.py +0 -2319
- package/resources/harness/bundles/general/codex/scripts/harness_deploy.py +0 -674
- package/resources/harness/bundles/general/codex/scripts/harness_events.py +0 -680
- package/resources/harness/bundles/general/codex/scripts/harness_ledger.py +0 -671
- package/resources/harness/bundles/general/codex/scripts/harness_preflight.py +0 -780
- package/resources/harness/bundles/general/codex/scripts/harness_service.py +0 -1331
- package/resources/harness/bundles/general/cursor/.harness-build.json +0 -6
- package/resources/harness/bundles/general/cursor/CONTEXT.md +0 -65
- package/resources/harness/bundles/general/cursor/README.md +0 -478
- package/resources/harness/bundles/general/cursor/harness-archive/SKILL.md +0 -198
- package/resources/harness/bundles/general/cursor/harness-archive/checklist.md +0 -52
- package/resources/harness/bundles/general/cursor/harness-archive/reference.md +0 -103
- package/resources/harness/bundles/general/cursor/harness-archive/scripts/gen-manifest.ps1 +0 -31
- package/resources/harness/bundles/general/cursor/harness-archive/templates/render-summary.mjs +0 -171
- package/resources/harness/bundles/general/cursor/harness-archive/templates/summary-data-template.json +0 -128
- package/resources/harness/bundles/general/cursor/harness-codebase-map/SKILL.md +0 -95
- package/resources/harness/bundles/general/cursor/harness-codebase-map/checklist.md +0 -126
- package/resources/harness/bundles/general/cursor/harness-codebase-map/reference.md +0 -364
- package/resources/harness/bundles/general/cursor/harness-codebase-map/templates/ARCHITECTURE.md +0 -57
- package/resources/harness/bundles/general/cursor/harness-codebase-map/templates/CONCERNS.md +0 -49
- package/resources/harness/bundles/general/cursor/harness-codebase-map/templates/CONVENTIONS.md +0 -57
- package/resources/harness/bundles/general/cursor/harness-codebase-map/templates/INTEGRATIONS.md +0 -52
- package/resources/harness/bundles/general/cursor/harness-codebase-map/templates/STACK.md +0 -55
- package/resources/harness/bundles/general/cursor/harness-codebase-map/templates/STRUCTURE.md +0 -69
- package/resources/harness/bundles/general/cursor/harness-codebase-map/templates/TESTING.md +0 -64
- package/resources/harness/bundles/general/cursor/harness-codebase-map/templates/map-manifest.schema.json +0 -73
- package/resources/harness/bundles/general/cursor/harness-codebase-map/templates/map-summary.md +0 -64
- package/resources/harness/bundles/general/cursor/harness-knowledge-ingest/SKILL.md +0 -229
- package/resources/harness/bundles/general/cursor/harness-knowledge-ingest/design.md +0 -842
- package/resources/harness/bundles/general/cursor/harness-knowledge-ingest/evaluations/harness_knowledge_evaluation.xml +0 -42
- package/resources/harness/bundles/general/cursor/harness-knowledge-ingest/mcp-config.example.json +0 -10
- package/resources/harness/bundles/general/cursor/harness-knowledge-ingest/reference.md +0 -309
- package/resources/harness/bundles/general/cursor/harness-knowledge-ingest/scripts/harness_knowledge.py +0 -4082
- package/resources/harness/bundles/general/cursor/harness-knowledge-ingest/scripts/harness_knowledge_mcp.py +0 -273
- package/resources/harness/bundles/general/cursor/harness-knowledge-query/SKILL.md +0 -147
- package/resources/harness/bundles/general/cursor/harness-plan/SKILL.md +0 -109
- package/resources/harness/bundles/general/cursor/harness-plan/checklist.md +0 -243
- package/resources/harness/bundles/general/cursor/harness-plan/protocols.md +0 -124
- package/resources/harness/bundles/general/cursor/harness-plan/reference.md +0 -376
- package/resources/harness/bundles/general/cursor/harness-review/SKILL.md +0 -137
- package/resources/harness/bundles/general/cursor/harness-review/checklist.md +0 -124
- package/resources/harness/bundles/general/cursor/harness-review/protocols.md +0 -68
- package/resources/harness/bundles/general/cursor/harness-review/reference.md +0 -86
- package/resources/harness/bundles/general/cursor/harness-run/SKILL.md +0 -113
- package/resources/harness/bundles/general/cursor/harness-run/checklist.md +0 -259
- package/resources/harness/bundles/general/cursor/harness-run/protocols.md +0 -78
- package/resources/harness/bundles/general/cursor/harness-run/reference.md +0 -834
- package/resources/harness/bundles/general/cursor/harness-submit/SKILL.md +0 -142
- package/resources/harness/bundles/general/cursor/harness-submit/checklist.md +0 -407
- package/resources/harness/bundles/general/cursor/harness-submit/reference.md +0 -152
- package/resources/harness/bundles/general/cursor/harness-sync/SKILL.md +0 -65
- package/resources/harness/bundles/general/cursor/harness-sync/reference.md +0 -153
- package/resources/harness/bundles/general/cursor/harness-test/SKILL.md +0 -160
- package/resources/harness/bundles/general/cursor/harness-test/checklist.md +0 -319
- package/resources/harness/bundles/general/cursor/harness-test/pitfalls.md +0 -260
- package/resources/harness/bundles/general/cursor/harness-test/reference.md +0 -791
- package/resources/harness/bundles/general/cursor/protocols/archive-report-protocol.md +0 -175
- package/resources/harness/bundles/general/cursor/protocols/evidence-based-reporting-protocol.md +0 -134
- package/resources/harness/bundles/general/cursor/protocols/ledger-protocol.md +0 -270
- package/resources/harness/bundles/general/cursor/protocols/powershell-protocol.md +0 -181
- package/resources/harness/bundles/general/cursor/protocols/report-pipeline-protocol.md +0 -150
- package/resources/harness/bundles/general/cursor/protocols/sensitive-info-protocol.md +0 -92
- package/resources/harness/bundles/general/cursor/protocols/state-layout-protocol.md +0 -131
- package/resources/harness/bundles/general/cursor/protocols/submit-protocol.md +0 -78
- package/resources/harness/bundles/general/cursor/scripts/harness_archive.py +0 -2319
- package/resources/harness/bundles/general/cursor/scripts/harness_deploy.py +0 -674
- package/resources/harness/bundles/general/cursor/scripts/harness_events.py +0 -680
- package/resources/harness/bundles/general/cursor/scripts/harness_ledger.py +0 -671
- package/resources/harness/bundles/general/cursor/scripts/harness_preflight.py +0 -780
- package/resources/harness/bundles/general/cursor/scripts/harness_service.py +0 -1331
- package/resources/harness/bundles/java/claude-code/.harness-build.json +0 -6
- package/resources/harness/bundles/java/claude-code/CONTEXT.md +0 -65
- package/resources/harness/bundles/java/claude-code/README.md +0 -478
- package/resources/harness/bundles/java/claude-code/agents/harness-evaluator.md +0 -96
- package/resources/harness/bundles/java/claude-code/agents/harness-explorer.md +0 -72
- package/resources/harness/bundles/java/claude-code/agents/harness-reviewer.md +0 -69
- package/resources/harness/bundles/java/claude-code/harness-apidoc/SKILL.md +0 -86
- package/resources/harness/bundles/java/claude-code/harness-apidoc/checklist.md +0 -142
- package/resources/harness/bundles/java/claude-code/harness-apidoc/reference.md +0 -233
- package/resources/harness/bundles/java/claude-code/harness-archive/SKILL.md +0 -215
- package/resources/harness/bundles/java/claude-code/harness-archive/checklist.md +0 -52
- package/resources/harness/bundles/java/claude-code/harness-archive/reference.md +0 -103
- package/resources/harness/bundles/java/claude-code/harness-archive/scripts/gen-manifest.ps1 +0 -31
- package/resources/harness/bundles/java/claude-code/harness-archive/templates/render-summary.mjs +0 -171
- package/resources/harness/bundles/java/claude-code/harness-archive/templates/summary-data-template.json +0 -128
- package/resources/harness/bundles/java/claude-code/harness-codebase-map/SKILL.md +0 -112
- package/resources/harness/bundles/java/claude-code/harness-codebase-map/checklist.md +0 -126
- package/resources/harness/bundles/java/claude-code/harness-codebase-map/reference.md +0 -364
- package/resources/harness/bundles/java/claude-code/harness-codebase-map/templates/ARCHITECTURE.md +0 -57
- package/resources/harness/bundles/java/claude-code/harness-codebase-map/templates/CONCERNS.md +0 -49
- package/resources/harness/bundles/java/claude-code/harness-codebase-map/templates/CONVENTIONS.md +0 -57
- package/resources/harness/bundles/java/claude-code/harness-codebase-map/templates/INTEGRATIONS.md +0 -52
- package/resources/harness/bundles/java/claude-code/harness-codebase-map/templates/STACK.md +0 -55
- package/resources/harness/bundles/java/claude-code/harness-codebase-map/templates/STRUCTURE.md +0 -69
- package/resources/harness/bundles/java/claude-code/harness-codebase-map/templates/TESTING.md +0 -64
- package/resources/harness/bundles/java/claude-code/harness-codebase-map/templates/map-manifest.schema.json +0 -73
- package/resources/harness/bundles/java/claude-code/harness-codebase-map/templates/map-summary.md +0 -64
- package/resources/harness/bundles/java/claude-code/harness-knowledge-ingest/SKILL.md +0 -246
- package/resources/harness/bundles/java/claude-code/harness-knowledge-ingest/design.md +0 -842
- package/resources/harness/bundles/java/claude-code/harness-knowledge-ingest/evaluations/harness_knowledge_evaluation.xml +0 -42
- package/resources/harness/bundles/java/claude-code/harness-knowledge-ingest/mcp-config.example.json +0 -10
- package/resources/harness/bundles/java/claude-code/harness-knowledge-ingest/reference.md +0 -309
- package/resources/harness/bundles/java/claude-code/harness-knowledge-ingest/scripts/harness_knowledge.py +0 -4082
- package/resources/harness/bundles/java/claude-code/harness-knowledge-ingest/scripts/harness_knowledge_mcp.py +0 -273
- package/resources/harness/bundles/java/claude-code/harness-knowledge-query/SKILL.md +0 -164
- package/resources/harness/bundles/java/claude-code/harness-package/SKILL.md +0 -87
- package/resources/harness/bundles/java/claude-code/harness-package/checklist.md +0 -322
- package/resources/harness/bundles/java/claude-code/harness-package/reference.md +0 -210
- package/resources/harness/bundles/java/claude-code/harness-plan/SKILL.md +0 -127
- package/resources/harness/bundles/java/claude-code/harness-plan/checklist.md +0 -243
- package/resources/harness/bundles/java/claude-code/harness-plan/protocols.md +0 -124
- package/resources/harness/bundles/java/claude-code/harness-plan/reference.md +0 -376
- package/resources/harness/bundles/java/claude-code/harness-review/SKILL.md +0 -160
- package/resources/harness/bundles/java/claude-code/harness-review/checklist.md +0 -124
- package/resources/harness/bundles/java/claude-code/harness-review/protocols.md +0 -68
- package/resources/harness/bundles/java/claude-code/harness-review/reference.md +0 -86
- package/resources/harness/bundles/java/claude-code/harness-run/SKILL.md +0 -148
- package/resources/harness/bundles/java/claude-code/harness-run/checklist.md +0 -250
- package/resources/harness/bundles/java/claude-code/harness-run/protocols.md +0 -78
- package/resources/harness/bundles/java/claude-code/harness-run/reference.md +0 -814
- package/resources/harness/bundles/java/claude-code/harness-submit/SKILL.md +0 -166
- package/resources/harness/bundles/java/claude-code/harness-submit/checklist.md +0 -407
- package/resources/harness/bundles/java/claude-code/harness-submit/reference.md +0 -152
- package/resources/harness/bundles/java/claude-code/harness-sync/SKILL.md +0 -82
- package/resources/harness/bundles/java/claude-code/harness-sync/reference.md +0 -153
- package/resources/harness/bundles/java/claude-code/harness-test/SKILL.md +0 -192
- package/resources/harness/bundles/java/claude-code/harness-test/checklist.md +0 -317
- package/resources/harness/bundles/java/claude-code/harness-test/pitfalls-java.md +0 -16
- package/resources/harness/bundles/java/claude-code/harness-test/pitfalls.md +0 -260
- package/resources/harness/bundles/java/claude-code/harness-test/reference.md +0 -788
- package/resources/harness/bundles/java/claude-code/protocols/archive-report-protocol.md +0 -175
- package/resources/harness/bundles/java/claude-code/protocols/evidence-based-reporting-protocol.md +0 -134
- package/resources/harness/bundles/java/claude-code/protocols/ledger-protocol.md +0 -270
- package/resources/harness/bundles/java/claude-code/protocols/powershell-protocol.md +0 -181
- package/resources/harness/bundles/java/claude-code/protocols/report-pipeline-protocol.md +0 -150
- package/resources/harness/bundles/java/claude-code/protocols/sensitive-info-protocol.md +0 -92
- package/resources/harness/bundles/java/claude-code/protocols/state-layout-protocol.md +0 -131
- package/resources/harness/bundles/java/claude-code/protocols/submit-protocol.md +0 -78
- package/resources/harness/bundles/java/claude-code/scripts/harness_archive.py +0 -2319
- package/resources/harness/bundles/java/claude-code/scripts/harness_deploy.py +0 -674
- package/resources/harness/bundles/java/claude-code/scripts/harness_events.py +0 -680
- package/resources/harness/bundles/java/claude-code/scripts/harness_ledger.py +0 -671
- package/resources/harness/bundles/java/claude-code/scripts/harness_preflight.py +0 -780
- package/resources/harness/bundles/java/claude-code/scripts/harness_service.py +0 -1331
- package/resources/harness/bundles/java/codebuddy/.harness-build.json +0 -6
- package/resources/harness/bundles/java/codebuddy/CONTEXT.md +0 -65
- package/resources/harness/bundles/java/codebuddy/README.md +0 -478
- package/resources/harness/bundles/java/codebuddy/agents/harness-evaluator.md +0 -94
- package/resources/harness/bundles/java/codebuddy/agents/harness-explorer.md +0 -53
- package/resources/harness/bundles/java/codebuddy/agents/harness-reviewer.md +0 -52
- package/resources/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +0 -68
- package/resources/harness/bundles/java/codebuddy/harness-apidoc/checklist.md +0 -142
- package/resources/harness/bundles/java/codebuddy/harness-apidoc/reference.md +0 -233
- package/resources/harness/bundles/java/codebuddy/harness-archive/SKILL.md +0 -198
- package/resources/harness/bundles/java/codebuddy/harness-archive/checklist.md +0 -52
- package/resources/harness/bundles/java/codebuddy/harness-archive/reference.md +0 -103
- package/resources/harness/bundles/java/codebuddy/harness-archive/scripts/gen-manifest.ps1 +0 -31
- package/resources/harness/bundles/java/codebuddy/harness-archive/templates/render-summary.mjs +0 -171
- package/resources/harness/bundles/java/codebuddy/harness-archive/templates/summary-data-template.json +0 -128
- package/resources/harness/bundles/java/codebuddy/harness-codebase-map/SKILL.md +0 -95
- package/resources/harness/bundles/java/codebuddy/harness-codebase-map/checklist.md +0 -126
- package/resources/harness/bundles/java/codebuddy/harness-codebase-map/reference.md +0 -364
- package/resources/harness/bundles/java/codebuddy/harness-codebase-map/templates/ARCHITECTURE.md +0 -57
- package/resources/harness/bundles/java/codebuddy/harness-codebase-map/templates/CONCERNS.md +0 -49
- package/resources/harness/bundles/java/codebuddy/harness-codebase-map/templates/CONVENTIONS.md +0 -57
- package/resources/harness/bundles/java/codebuddy/harness-codebase-map/templates/INTEGRATIONS.md +0 -52
- package/resources/harness/bundles/java/codebuddy/harness-codebase-map/templates/STACK.md +0 -55
- package/resources/harness/bundles/java/codebuddy/harness-codebase-map/templates/STRUCTURE.md +0 -69
- package/resources/harness/bundles/java/codebuddy/harness-codebase-map/templates/TESTING.md +0 -64
- package/resources/harness/bundles/java/codebuddy/harness-codebase-map/templates/map-manifest.schema.json +0 -73
- package/resources/harness/bundles/java/codebuddy/harness-codebase-map/templates/map-summary.md +0 -64
- package/resources/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +0 -229
- package/resources/harness/bundles/java/codebuddy/harness-knowledge-ingest/design.md +0 -842
- package/resources/harness/bundles/java/codebuddy/harness-knowledge-ingest/evaluations/harness_knowledge_evaluation.xml +0 -42
- package/resources/harness/bundles/java/codebuddy/harness-knowledge-ingest/mcp-config.example.json +0 -10
- package/resources/harness/bundles/java/codebuddy/harness-knowledge-ingest/reference.md +0 -309
- package/resources/harness/bundles/java/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge.py +0 -4082
- package/resources/harness/bundles/java/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge_mcp.py +0 -273
- package/resources/harness/bundles/java/codebuddy/harness-knowledge-query/SKILL.md +0 -147
- package/resources/harness/bundles/java/codebuddy/harness-package/SKILL.md +0 -69
- package/resources/harness/bundles/java/codebuddy/harness-package/checklist.md +0 -322
- package/resources/harness/bundles/java/codebuddy/harness-package/reference.md +0 -210
- package/resources/harness/bundles/java/codebuddy/harness-plan/SKILL.md +0 -109
- package/resources/harness/bundles/java/codebuddy/harness-plan/checklist.md +0 -243
- package/resources/harness/bundles/java/codebuddy/harness-plan/protocols.md +0 -124
- package/resources/harness/bundles/java/codebuddy/harness-plan/reference.md +0 -376
- package/resources/harness/bundles/java/codebuddy/harness-review/SKILL.md +0 -142
- package/resources/harness/bundles/java/codebuddy/harness-review/checklist.md +0 -124
- package/resources/harness/bundles/java/codebuddy/harness-review/protocols.md +0 -68
- package/resources/harness/bundles/java/codebuddy/harness-review/reference.md +0 -86
- package/resources/harness/bundles/java/codebuddy/harness-run/SKILL.md +0 -129
- package/resources/harness/bundles/java/codebuddy/harness-run/checklist.md +0 -250
- package/resources/harness/bundles/java/codebuddy/harness-run/protocols.md +0 -78
- package/resources/harness/bundles/java/codebuddy/harness-run/reference.md +0 -814
- package/resources/harness/bundles/java/codebuddy/harness-submit/SKILL.md +0 -149
- package/resources/harness/bundles/java/codebuddy/harness-submit/checklist.md +0 -407
- package/resources/harness/bundles/java/codebuddy/harness-submit/reference.md +0 -152
- package/resources/harness/bundles/java/codebuddy/harness-sync/SKILL.md +0 -65
- package/resources/harness/bundles/java/codebuddy/harness-sync/reference.md +0 -153
- package/resources/harness/bundles/java/codebuddy/harness-test/SKILL.md +0 -172
- package/resources/harness/bundles/java/codebuddy/harness-test/checklist.md +0 -317
- package/resources/harness/bundles/java/codebuddy/harness-test/pitfalls-java.md +0 -16
- package/resources/harness/bundles/java/codebuddy/harness-test/pitfalls.md +0 -260
- package/resources/harness/bundles/java/codebuddy/harness-test/reference.md +0 -788
- package/resources/harness/bundles/java/codebuddy/protocols/archive-report-protocol.md +0 -175
- package/resources/harness/bundles/java/codebuddy/protocols/evidence-based-reporting-protocol.md +0 -134
- package/resources/harness/bundles/java/codebuddy/protocols/ledger-protocol.md +0 -270
- package/resources/harness/bundles/java/codebuddy/protocols/powershell-protocol.md +0 -181
- package/resources/harness/bundles/java/codebuddy/protocols/report-pipeline-protocol.md +0 -150
- package/resources/harness/bundles/java/codebuddy/protocols/sensitive-info-protocol.md +0 -92
- package/resources/harness/bundles/java/codebuddy/protocols/state-layout-protocol.md +0 -131
- package/resources/harness/bundles/java/codebuddy/protocols/submit-protocol.md +0 -78
- package/resources/harness/bundles/java/codebuddy/scripts/harness_archive.py +0 -2319
- package/resources/harness/bundles/java/codebuddy/scripts/harness_deploy.py +0 -674
- package/resources/harness/bundles/java/codebuddy/scripts/harness_events.py +0 -680
- package/resources/harness/bundles/java/codebuddy/scripts/harness_ledger.py +0 -671
- package/resources/harness/bundles/java/codebuddy/scripts/harness_preflight.py +0 -780
- package/resources/harness/bundles/java/codebuddy/scripts/harness_service.py +0 -1331
- package/resources/harness/bundles/java/codex/.harness-build.json +0 -6
- package/resources/harness/bundles/java/codex/CONTEXT.md +0 -65
- package/resources/harness/bundles/java/codex/README.md +0 -478
- package/resources/harness/bundles/java/codex/harness-apidoc/SKILL.md +0 -68
- package/resources/harness/bundles/java/codex/harness-apidoc/checklist.md +0 -142
- package/resources/harness/bundles/java/codex/harness-apidoc/reference.md +0 -233
- package/resources/harness/bundles/java/codex/harness-archive/SKILL.md +0 -198
- package/resources/harness/bundles/java/codex/harness-archive/checklist.md +0 -52
- package/resources/harness/bundles/java/codex/harness-archive/reference.md +0 -103
- package/resources/harness/bundles/java/codex/harness-archive/scripts/gen-manifest.ps1 +0 -31
- package/resources/harness/bundles/java/codex/harness-archive/templates/render-summary.mjs +0 -171
- package/resources/harness/bundles/java/codex/harness-archive/templates/summary-data-template.json +0 -128
- package/resources/harness/bundles/java/codex/harness-codebase-map/SKILL.md +0 -95
- package/resources/harness/bundles/java/codex/harness-codebase-map/checklist.md +0 -126
- package/resources/harness/bundles/java/codex/harness-codebase-map/reference.md +0 -364
- package/resources/harness/bundles/java/codex/harness-codebase-map/templates/ARCHITECTURE.md +0 -57
- package/resources/harness/bundles/java/codex/harness-codebase-map/templates/CONCERNS.md +0 -49
- package/resources/harness/bundles/java/codex/harness-codebase-map/templates/CONVENTIONS.md +0 -57
- package/resources/harness/bundles/java/codex/harness-codebase-map/templates/INTEGRATIONS.md +0 -52
- package/resources/harness/bundles/java/codex/harness-codebase-map/templates/STACK.md +0 -55
- package/resources/harness/bundles/java/codex/harness-codebase-map/templates/STRUCTURE.md +0 -69
- package/resources/harness/bundles/java/codex/harness-codebase-map/templates/TESTING.md +0 -64
- package/resources/harness/bundles/java/codex/harness-codebase-map/templates/map-manifest.schema.json +0 -73
- package/resources/harness/bundles/java/codex/harness-codebase-map/templates/map-summary.md +0 -64
- package/resources/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +0 -229
- package/resources/harness/bundles/java/codex/harness-knowledge-ingest/design.md +0 -842
- package/resources/harness/bundles/java/codex/harness-knowledge-ingest/evaluations/harness_knowledge_evaluation.xml +0 -42
- package/resources/harness/bundles/java/codex/harness-knowledge-ingest/mcp-config.example.json +0 -10
- package/resources/harness/bundles/java/codex/harness-knowledge-ingest/reference.md +0 -309
- package/resources/harness/bundles/java/codex/harness-knowledge-ingest/scripts/harness_knowledge.py +0 -4082
- package/resources/harness/bundles/java/codex/harness-knowledge-ingest/scripts/harness_knowledge_mcp.py +0 -273
- package/resources/harness/bundles/java/codex/harness-knowledge-query/SKILL.md +0 -147
- package/resources/harness/bundles/java/codex/harness-package/SKILL.md +0 -69
- package/resources/harness/bundles/java/codex/harness-package/checklist.md +0 -322
- package/resources/harness/bundles/java/codex/harness-package/reference.md +0 -210
- package/resources/harness/bundles/java/codex/harness-plan/SKILL.md +0 -109
- package/resources/harness/bundles/java/codex/harness-plan/checklist.md +0 -243
- package/resources/harness/bundles/java/codex/harness-plan/protocols.md +0 -124
- package/resources/harness/bundles/java/codex/harness-plan/reference.md +0 -376
- package/resources/harness/bundles/java/codex/harness-review/SKILL.md +0 -137
- package/resources/harness/bundles/java/codex/harness-review/checklist.md +0 -124
- package/resources/harness/bundles/java/codex/harness-review/protocols.md +0 -68
- package/resources/harness/bundles/java/codex/harness-review/reference.md +0 -86
- package/resources/harness/bundles/java/codex/harness-run/SKILL.md +0 -129
- package/resources/harness/bundles/java/codex/harness-run/checklist.md +0 -250
- package/resources/harness/bundles/java/codex/harness-run/protocols.md +0 -78
- package/resources/harness/bundles/java/codex/harness-run/reference.md +0 -814
- package/resources/harness/bundles/java/codex/harness-submit/SKILL.md +0 -149
- package/resources/harness/bundles/java/codex/harness-submit/checklist.md +0 -407
- package/resources/harness/bundles/java/codex/harness-submit/reference.md +0 -152
- package/resources/harness/bundles/java/codex/harness-sync/SKILL.md +0 -65
- package/resources/harness/bundles/java/codex/harness-sync/reference.md +0 -153
- package/resources/harness/bundles/java/codex/harness-test/SKILL.md +0 -172
- package/resources/harness/bundles/java/codex/harness-test/checklist.md +0 -317
- package/resources/harness/bundles/java/codex/harness-test/pitfalls-java.md +0 -16
- package/resources/harness/bundles/java/codex/harness-test/pitfalls.md +0 -260
- package/resources/harness/bundles/java/codex/harness-test/reference.md +0 -788
- package/resources/harness/bundles/java/codex/protocols/archive-report-protocol.md +0 -175
- package/resources/harness/bundles/java/codex/protocols/evidence-based-reporting-protocol.md +0 -134
- package/resources/harness/bundles/java/codex/protocols/ledger-protocol.md +0 -270
- package/resources/harness/bundles/java/codex/protocols/powershell-protocol.md +0 -181
- package/resources/harness/bundles/java/codex/protocols/report-pipeline-protocol.md +0 -150
- package/resources/harness/bundles/java/codex/protocols/sensitive-info-protocol.md +0 -92
- package/resources/harness/bundles/java/codex/protocols/state-layout-protocol.md +0 -131
- package/resources/harness/bundles/java/codex/protocols/submit-protocol.md +0 -78
- package/resources/harness/bundles/java/codex/scripts/harness_archive.py +0 -2319
- package/resources/harness/bundles/java/codex/scripts/harness_deploy.py +0 -674
- package/resources/harness/bundles/java/codex/scripts/harness_events.py +0 -680
- package/resources/harness/bundles/java/codex/scripts/harness_ledger.py +0 -671
- package/resources/harness/bundles/java/codex/scripts/harness_preflight.py +0 -780
- package/resources/harness/bundles/java/codex/scripts/harness_service.py +0 -1331
- package/resources/harness/bundles/java/cursor/.harness-build.json +0 -6
- package/resources/harness/bundles/java/cursor/CONTEXT.md +0 -65
- package/resources/harness/bundles/java/cursor/README.md +0 -478
- package/resources/harness/bundles/java/cursor/harness-apidoc/SKILL.md +0 -68
- package/resources/harness/bundles/java/cursor/harness-apidoc/checklist.md +0 -142
- package/resources/harness/bundles/java/cursor/harness-apidoc/reference.md +0 -233
- package/resources/harness/bundles/java/cursor/harness-archive/SKILL.md +0 -198
- package/resources/harness/bundles/java/cursor/harness-archive/checklist.md +0 -52
- package/resources/harness/bundles/java/cursor/harness-archive/reference.md +0 -103
- package/resources/harness/bundles/java/cursor/harness-archive/scripts/gen-manifest.ps1 +0 -31
- package/resources/harness/bundles/java/cursor/harness-archive/templates/render-summary.mjs +0 -171
- package/resources/harness/bundles/java/cursor/harness-archive/templates/summary-data-template.json +0 -128
- package/resources/harness/bundles/java/cursor/harness-codebase-map/SKILL.md +0 -95
- package/resources/harness/bundles/java/cursor/harness-codebase-map/checklist.md +0 -126
- package/resources/harness/bundles/java/cursor/harness-codebase-map/reference.md +0 -364
- package/resources/harness/bundles/java/cursor/harness-codebase-map/templates/ARCHITECTURE.md +0 -57
- package/resources/harness/bundles/java/cursor/harness-codebase-map/templates/CONCERNS.md +0 -49
- package/resources/harness/bundles/java/cursor/harness-codebase-map/templates/CONVENTIONS.md +0 -57
- package/resources/harness/bundles/java/cursor/harness-codebase-map/templates/INTEGRATIONS.md +0 -52
- package/resources/harness/bundles/java/cursor/harness-codebase-map/templates/STACK.md +0 -55
- package/resources/harness/bundles/java/cursor/harness-codebase-map/templates/STRUCTURE.md +0 -69
- package/resources/harness/bundles/java/cursor/harness-codebase-map/templates/TESTING.md +0 -64
- package/resources/harness/bundles/java/cursor/harness-codebase-map/templates/map-manifest.schema.json +0 -73
- package/resources/harness/bundles/java/cursor/harness-codebase-map/templates/map-summary.md +0 -64
- package/resources/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +0 -229
- package/resources/harness/bundles/java/cursor/harness-knowledge-ingest/design.md +0 -842
- package/resources/harness/bundles/java/cursor/harness-knowledge-ingest/evaluations/harness_knowledge_evaluation.xml +0 -42
- package/resources/harness/bundles/java/cursor/harness-knowledge-ingest/mcp-config.example.json +0 -10
- package/resources/harness/bundles/java/cursor/harness-knowledge-ingest/reference.md +0 -309
- package/resources/harness/bundles/java/cursor/harness-knowledge-ingest/scripts/harness_knowledge.py +0 -4082
- package/resources/harness/bundles/java/cursor/harness-knowledge-ingest/scripts/harness_knowledge_mcp.py +0 -273
- package/resources/harness/bundles/java/cursor/harness-knowledge-query/SKILL.md +0 -147
- package/resources/harness/bundles/java/cursor/harness-package/SKILL.md +0 -69
- package/resources/harness/bundles/java/cursor/harness-package/checklist.md +0 -322
- package/resources/harness/bundles/java/cursor/harness-package/reference.md +0 -210
- package/resources/harness/bundles/java/cursor/harness-plan/SKILL.md +0 -109
- package/resources/harness/bundles/java/cursor/harness-plan/checklist.md +0 -243
- package/resources/harness/bundles/java/cursor/harness-plan/protocols.md +0 -124
- package/resources/harness/bundles/java/cursor/harness-plan/reference.md +0 -376
- package/resources/harness/bundles/java/cursor/harness-review/SKILL.md +0 -137
- package/resources/harness/bundles/java/cursor/harness-review/checklist.md +0 -124
- package/resources/harness/bundles/java/cursor/harness-review/protocols.md +0 -68
- package/resources/harness/bundles/java/cursor/harness-review/reference.md +0 -86
- package/resources/harness/bundles/java/cursor/harness-run/SKILL.md +0 -129
- package/resources/harness/bundles/java/cursor/harness-run/checklist.md +0 -250
- package/resources/harness/bundles/java/cursor/harness-run/protocols.md +0 -78
- package/resources/harness/bundles/java/cursor/harness-run/reference.md +0 -814
- package/resources/harness/bundles/java/cursor/harness-submit/SKILL.md +0 -149
- package/resources/harness/bundles/java/cursor/harness-submit/checklist.md +0 -407
- package/resources/harness/bundles/java/cursor/harness-submit/reference.md +0 -152
- package/resources/harness/bundles/java/cursor/harness-sync/SKILL.md +0 -65
- package/resources/harness/bundles/java/cursor/harness-sync/reference.md +0 -153
- package/resources/harness/bundles/java/cursor/harness-test/SKILL.md +0 -172
- package/resources/harness/bundles/java/cursor/harness-test/checklist.md +0 -317
- package/resources/harness/bundles/java/cursor/harness-test/pitfalls-java.md +0 -16
- package/resources/harness/bundles/java/cursor/harness-test/pitfalls.md +0 -260
- package/resources/harness/bundles/java/cursor/harness-test/reference.md +0 -788
- package/resources/harness/bundles/java/cursor/protocols/archive-report-protocol.md +0 -175
- package/resources/harness/bundles/java/cursor/protocols/evidence-based-reporting-protocol.md +0 -134
- package/resources/harness/bundles/java/cursor/protocols/ledger-protocol.md +0 -270
- package/resources/harness/bundles/java/cursor/protocols/powershell-protocol.md +0 -181
- package/resources/harness/bundles/java/cursor/protocols/report-pipeline-protocol.md +0 -150
- package/resources/harness/bundles/java/cursor/protocols/sensitive-info-protocol.md +0 -92
- package/resources/harness/bundles/java/cursor/protocols/state-layout-protocol.md +0 -131
- package/resources/harness/bundles/java/cursor/protocols/submit-protocol.md +0 -78
- package/resources/harness/bundles/java/cursor/scripts/harness_archive.py +0 -2319
- package/resources/harness/bundles/java/cursor/scripts/harness_deploy.py +0 -674
- package/resources/harness/bundles/java/cursor/scripts/harness_events.py +0 -680
- package/resources/harness/bundles/java/cursor/scripts/harness_ledger.py +0 -671
- package/resources/harness/bundles/java/cursor/scripts/harness_preflight.py +0 -780
- package/resources/harness/bundles/java/cursor/scripts/harness_service.py +0 -1331
- package/resources/harness/manifests/general/claude-code.json +0 -277
- package/resources/harness/manifests/general/codebuddy.json +0 -277
- package/resources/harness/manifests/general/codex.json +0 -265
- package/resources/harness/manifests/general/cursor.json +0 -265
- package/resources/harness/manifests/java/claude-code.json +0 -305
- package/resources/harness/manifests/java/codebuddy.json +0 -305
- package/resources/harness/manifests/java/codex.json +0 -293
- package/resources/harness/manifests/java/cursor.json +0 -293
- package/resources/harness/migrations/0.1.1/general.json +0 -358
- package/resources/harness/migrations/0.1.1/java.json +0 -398
|
@@ -1,1331 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Harness service-session manager (D7).
|
|
3
|
-
|
|
4
|
-
Subcommands:
|
|
5
|
-
ensure — reuse / restart / start / needs-user-decision
|
|
6
|
-
status — session + liveness + fingerprint match
|
|
7
|
-
stop — stop AI-managed service and clear session
|
|
8
|
-
|
|
9
|
-
Python 3.10+, stdlib only. UTF-8 without BOM. Windows path safe.
|
|
10
|
-
|
|
11
|
-
Safety: never kill a process that cannot be verified as AI-started.
|
|
12
|
-
Missing or corrupt session → treat as user process → needs-user-decision.
|
|
13
|
-
"""
|
|
14
|
-
|
|
15
|
-
from __future__ import annotations
|
|
16
|
-
|
|
17
|
-
import argparse
|
|
18
|
-
import datetime as dt
|
|
19
|
-
import hashlib
|
|
20
|
-
import json
|
|
21
|
-
import os
|
|
22
|
-
import re
|
|
23
|
-
import signal
|
|
24
|
-
import socket
|
|
25
|
-
import subprocess
|
|
26
|
-
import sys
|
|
27
|
-
import time
|
|
28
|
-
import urllib.error
|
|
29
|
-
import urllib.request
|
|
30
|
-
from pathlib import Path
|
|
31
|
-
from typing import Any
|
|
32
|
-
from urllib.parse import urlparse
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
SCRIPTS_DIR = Path(__file__).resolve().parent
|
|
36
|
-
if str(SCRIPTS_DIR) not in sys.path:
|
|
37
|
-
sys.path.insert(0, str(SCRIPTS_DIR))
|
|
38
|
-
|
|
39
|
-
from harness_ledger import compute_inputs_hash # noqa: E402
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if hasattr(sys.stdout, "reconfigure"):
|
|
43
|
-
sys.stdout.reconfigure(encoding="utf-8")
|
|
44
|
-
if hasattr(sys.stderr, "reconfigure"):
|
|
45
|
-
sys.stderr.reconfigure(encoding="utf-8")
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
PROFILE_REL = Path(".harness") / "config" / "build-profile.json"
|
|
49
|
-
SESSION_REL = Path("runtime") / "service-session.json"
|
|
50
|
-
LOG_REL = Path("logs") / "service-start.log"
|
|
51
|
-
|
|
52
|
-
FATAL_KEYWORDS = (
|
|
53
|
-
"BindException",
|
|
54
|
-
"Could not resolve placeholder",
|
|
55
|
-
"BeanCreationException",
|
|
56
|
-
"BUILD FAILURE",
|
|
57
|
-
)
|
|
58
|
-
|
|
59
|
-
# Process create-time vs session.startedAt tolerance (seconds).
|
|
60
|
-
IDENTITY_TOLERANCE_SEC = 5.0
|
|
61
|
-
|
|
62
|
-
# Windows process flags
|
|
63
|
-
_DETACHED_PROCESS = 0x00000008
|
|
64
|
-
_CREATE_NEW_PROCESS_GROUP = 0x00000200
|
|
65
|
-
_CREATE_NO_WINDOW = 0x08000000
|
|
66
|
-
_CREATE_BREAKAWAY_FROM_JOB = 0x01000000
|
|
67
|
-
_STILL_ACTIVE = 259
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
def now_iso() -> str:
|
|
71
|
-
return dt.datetime.now().astimezone().isoformat(timespec="milliseconds")
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
def emit_json(payload: dict[str, Any], *, as_json: bool) -> None:
|
|
75
|
-
text = json.dumps(payload, ensure_ascii=False, indent=2) + "\n"
|
|
76
|
-
if as_json:
|
|
77
|
-
sys.stdout.write(text)
|
|
78
|
-
else:
|
|
79
|
-
action = payload.get("action") or payload.get("status")
|
|
80
|
-
ok = payload.get("ok", True)
|
|
81
|
-
sys.stdout.write(f"ok={ok} action={action}\n")
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
def emit_error(message: str, *, as_json: bool, code: int = 1, **extra: Any) -> int:
|
|
85
|
-
payload: dict[str, Any] = {"ok": False, "error": message}
|
|
86
|
-
payload.update(extra)
|
|
87
|
-
if as_json:
|
|
88
|
-
sys.stderr.write(json.dumps(payload, ensure_ascii=False) + "\n")
|
|
89
|
-
else:
|
|
90
|
-
sys.stderr.write(f"error: {message}\n")
|
|
91
|
-
return code
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
def resolve_path(raw: str | Path) -> Path:
|
|
95
|
-
return Path(raw).expanduser().resolve()
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
def parse_files_arg(raw: str | None) -> list[str]:
|
|
99
|
-
if raw is None or not str(raw).strip():
|
|
100
|
-
return []
|
|
101
|
-
parts = [p.strip() for p in str(raw).split(",")]
|
|
102
|
-
return [p for p in parts if p]
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
def sha256_text(text: str) -> str:
|
|
106
|
-
return "sha256:" + hashlib.sha256(text.encode("utf-8")).hexdigest()
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
def session_path(change_dir: Path) -> Path:
|
|
110
|
-
return change_dir / SESSION_REL
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
def log_path(change_dir: Path) -> Path:
|
|
114
|
-
return change_dir / LOG_REL
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
def profile_path(project: Path) -> Path:
|
|
118
|
-
return project / PROFILE_REL
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
def read_json(path: Path) -> Any:
|
|
122
|
-
return json.loads(path.read_text(encoding="utf-8-sig"))
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
def write_json(path: Path, data: Any) -> None:
|
|
126
|
-
path.parent.mkdir(parents=True, exist_ok=True)
|
|
127
|
-
text = json.dumps(data, ensure_ascii=False, indent=2) + "\n"
|
|
128
|
-
path.write_text(text, encoding="utf-8", newline="\n")
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
def load_build_profile(project: Path) -> dict[str, Any]:
|
|
132
|
-
path = profile_path(project)
|
|
133
|
-
if not path.is_file():
|
|
134
|
-
raise FileNotFoundError(
|
|
135
|
-
f"build-profile.json missing: {path}; run harness_preflight.py detect first"
|
|
136
|
-
)
|
|
137
|
-
data = read_json(path)
|
|
138
|
-
if not isinstance(data, dict):
|
|
139
|
-
raise ValueError(f"build-profile.json must be an object: {path}")
|
|
140
|
-
return data
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
def get_service_start(profile: dict[str, Any]) -> dict[str, Any]:
|
|
144
|
-
svc = profile.get("serviceStart")
|
|
145
|
-
if not isinstance(svc, dict):
|
|
146
|
-
raise ValueError("build-profile.json missing serviceStart object")
|
|
147
|
-
command = svc.get("command")
|
|
148
|
-
if not isinstance(command, str) or not command.strip():
|
|
149
|
-
raise ValueError(
|
|
150
|
-
"build-profile.json serviceStart.command is empty; "
|
|
151
|
-
"configure the service start command explicitly (will not guess)"
|
|
152
|
-
)
|
|
153
|
-
return svc
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
def resolve_service_input_files(
|
|
157
|
-
project: Path,
|
|
158
|
-
service_start: dict[str, Any],
|
|
159
|
-
cli_files: list[str],
|
|
160
|
-
) -> list[str]:
|
|
161
|
-
"""Union of CLI ``--files`` and ``serviceStart.inputFiles`` globs.
|
|
162
|
-
|
|
163
|
-
Globs expand relative to project; only files inside project are kept
|
|
164
|
-
(deduped, path-sorted). Empty result raises ValueError -- never produce a
|
|
165
|
-
reusable empty fingerprint (§5.1/§5.2). Never globs outside project.
|
|
166
|
-
"""
|
|
167
|
-
base = project.resolve()
|
|
168
|
-
seen: set[str] = set()
|
|
169
|
-
|
|
170
|
-
for raw in cli_files:
|
|
171
|
-
p = Path(raw).expanduser()
|
|
172
|
-
if not p.is_absolute():
|
|
173
|
-
p = base / p
|
|
174
|
-
try:
|
|
175
|
-
p = p.resolve()
|
|
176
|
-
except OSError:
|
|
177
|
-
continue
|
|
178
|
-
if not p.is_file():
|
|
179
|
-
continue
|
|
180
|
-
try:
|
|
181
|
-
p.relative_to(base)
|
|
182
|
-
except ValueError:
|
|
183
|
-
continue # reject project-external path
|
|
184
|
-
seen.add(p.as_posix())
|
|
185
|
-
|
|
186
|
-
input_files = service_start.get("inputFiles")
|
|
187
|
-
if isinstance(input_files, list):
|
|
188
|
-
for pat in input_files:
|
|
189
|
-
if not isinstance(pat, str) or not pat.strip():
|
|
190
|
-
continue
|
|
191
|
-
for match in base.glob(pat):
|
|
192
|
-
if not match.is_file():
|
|
193
|
-
continue
|
|
194
|
-
resolved = match.resolve()
|
|
195
|
-
try:
|
|
196
|
-
resolved.relative_to(base)
|
|
197
|
-
except ValueError:
|
|
198
|
-
continue # reject project-external glob escape
|
|
199
|
-
seen.add(resolved.as_posix())
|
|
200
|
-
|
|
201
|
-
result = sorted(seen)
|
|
202
|
-
if not result:
|
|
203
|
-
raise ValueError(
|
|
204
|
-
"service inputs are empty; configure serviceStart.inputFiles "
|
|
205
|
-
"(or pass --files) so the service fingerprint covers real source"
|
|
206
|
-
)
|
|
207
|
-
return result
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
# ---------------------------------------------------------------------------
|
|
211
|
-
# Process liveness / identity (stdlib-first; ctypes best-effort on Windows)
|
|
212
|
-
# ---------------------------------------------------------------------------
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
def is_pid_alive(pid: int) -> bool:
|
|
216
|
-
if not isinstance(pid, int) or pid <= 0:
|
|
217
|
-
return False
|
|
218
|
-
if os.name == "nt":
|
|
219
|
-
return _windows_pid_alive(pid)
|
|
220
|
-
try:
|
|
221
|
-
os.kill(pid, 0)
|
|
222
|
-
return True
|
|
223
|
-
except ProcessLookupError:
|
|
224
|
-
return False
|
|
225
|
-
except PermissionError:
|
|
226
|
-
return True
|
|
227
|
-
except OSError:
|
|
228
|
-
return False
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
def _windows_pid_alive(pid: int) -> bool:
|
|
232
|
-
try:
|
|
233
|
-
import ctypes
|
|
234
|
-
from ctypes import wintypes
|
|
235
|
-
|
|
236
|
-
kernel32 = ctypes.windll.kernel32
|
|
237
|
-
PROCESS_QUERY_LIMITED_INFORMATION = 0x1000
|
|
238
|
-
handle = kernel32.OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, False, pid)
|
|
239
|
-
if not handle:
|
|
240
|
-
handle = kernel32.OpenProcess(0x0400, False, pid) # PROCESS_QUERY_INFORMATION
|
|
241
|
-
if not handle:
|
|
242
|
-
return False
|
|
243
|
-
try:
|
|
244
|
-
exit_code = wintypes.DWORD()
|
|
245
|
-
if not kernel32.GetExitCodeProcess(handle, ctypes.byref(exit_code)):
|
|
246
|
-
return False
|
|
247
|
-
return int(exit_code.value) == _STILL_ACTIVE
|
|
248
|
-
finally:
|
|
249
|
-
kernel32.CloseHandle(handle)
|
|
250
|
-
except Exception:
|
|
251
|
-
return False
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
def get_process_create_time(pid: int) -> dt.datetime | None:
|
|
255
|
-
"""Best-effort process create time (timezone-aware). None if unavailable."""
|
|
256
|
-
if os.name == "nt":
|
|
257
|
-
return _windows_process_create_time(pid)
|
|
258
|
-
return _posix_process_create_time(pid)
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
def _windows_process_create_time(pid: int) -> dt.datetime | None:
|
|
262
|
-
try:
|
|
263
|
-
import ctypes
|
|
264
|
-
from ctypes import wintypes
|
|
265
|
-
|
|
266
|
-
kernel32 = ctypes.windll.kernel32
|
|
267
|
-
handle = kernel32.OpenProcess(0x1000, False, pid)
|
|
268
|
-
if not handle:
|
|
269
|
-
handle = kernel32.OpenProcess(0x0400, False, pid)
|
|
270
|
-
if not handle:
|
|
271
|
-
return None
|
|
272
|
-
try:
|
|
273
|
-
creation = wintypes.FILETIME()
|
|
274
|
-
exit_time = wintypes.FILETIME()
|
|
275
|
-
kernel_time = wintypes.FILETIME()
|
|
276
|
-
user_time = wintypes.FILETIME()
|
|
277
|
-
ok = kernel32.GetProcessTimes(
|
|
278
|
-
handle,
|
|
279
|
-
ctypes.byref(creation),
|
|
280
|
-
ctypes.byref(exit_time),
|
|
281
|
-
ctypes.byref(kernel_time),
|
|
282
|
-
ctypes.byref(user_time),
|
|
283
|
-
)
|
|
284
|
-
if not ok:
|
|
285
|
-
return None
|
|
286
|
-
val = (creation.dwHighDateTime << 32) | creation.dwLowDateTime
|
|
287
|
-
# FILETIME: 100-ns since 1601-01-01 UTC
|
|
288
|
-
unix_sec = (val - 116444736000000000) / 10_000_000
|
|
289
|
-
return dt.datetime.fromtimestamp(unix_sec, tz=dt.timezone.utc)
|
|
290
|
-
finally:
|
|
291
|
-
kernel32.CloseHandle(handle)
|
|
292
|
-
except Exception:
|
|
293
|
-
return None
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
def _posix_process_create_time(pid: int) -> dt.datetime | None:
|
|
297
|
-
try:
|
|
298
|
-
stat_path = Path(f"/proc/{pid}/stat")
|
|
299
|
-
if not stat_path.is_file():
|
|
300
|
-
# macOS / other: no reliable stdlib create-time
|
|
301
|
-
return None
|
|
302
|
-
text = stat_path.read_text(encoding="utf-8", errors="replace")
|
|
303
|
-
# comm may contain spaces/parens — split after last ')'
|
|
304
|
-
rparen = text.rfind(")")
|
|
305
|
-
if rparen < 0:
|
|
306
|
-
return None
|
|
307
|
-
fields = text[rparen + 2 :].split()
|
|
308
|
-
# field index 20 in remaining = starttime (clock ticks since boot)
|
|
309
|
-
start_ticks = int(fields[19])
|
|
310
|
-
ticks = os.sysconf(os.sysconf_names.get("SC_CLK_TCK", "SC_CLK_TCK"))
|
|
311
|
-
if not ticks:
|
|
312
|
-
ticks = 100
|
|
313
|
-
boot = _linux_boot_time()
|
|
314
|
-
if boot is None:
|
|
315
|
-
return None
|
|
316
|
-
return dt.datetime.fromtimestamp(boot + start_ticks / ticks, tz=dt.timezone.utc)
|
|
317
|
-
except Exception:
|
|
318
|
-
return None
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
def _linux_boot_time() -> float | None:
|
|
322
|
-
try:
|
|
323
|
-
for line in Path("/proc/stat").read_text(encoding="utf-8").splitlines():
|
|
324
|
-
if line.startswith("btime "):
|
|
325
|
-
return float(line.split()[1])
|
|
326
|
-
except Exception:
|
|
327
|
-
return None
|
|
328
|
-
return None
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
def parse_iso(value: Any) -> dt.datetime | None:
|
|
332
|
-
if not isinstance(value, str) or not value.strip():
|
|
333
|
-
return None
|
|
334
|
-
text = value.strip()
|
|
335
|
-
try:
|
|
336
|
-
# Support "...Z" and space separator
|
|
337
|
-
if text.endswith("Z"):
|
|
338
|
-
text = text[:-1] + "+00:00"
|
|
339
|
-
parsed = dt.datetime.fromisoformat(text)
|
|
340
|
-
if parsed.tzinfo is None:
|
|
341
|
-
parsed = parsed.astimezone()
|
|
342
|
-
return parsed
|
|
343
|
-
except ValueError:
|
|
344
|
-
return None
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
def verify_process_identity(session: dict[str, Any]) -> bool | None:
|
|
348
|
-
"""True=same process, False=definitely different/dead, None=cannot verify.
|
|
349
|
-
|
|
350
|
-
Requires pid alive AND create-time within tolerance of session.startedAt.
|
|
351
|
-
If create-time unavailable → None (conservative → needs-user-decision).
|
|
352
|
-
"""
|
|
353
|
-
pid = session.get("pid")
|
|
354
|
-
if not isinstance(pid, int) or pid <= 0:
|
|
355
|
-
return False
|
|
356
|
-
if not is_pid_alive(pid):
|
|
357
|
-
return False
|
|
358
|
-
|
|
359
|
-
started_at = parse_iso(session.get("startedAt"))
|
|
360
|
-
if started_at is None:
|
|
361
|
-
return None
|
|
362
|
-
|
|
363
|
-
create_time = get_process_create_time(pid)
|
|
364
|
-
if create_time is None:
|
|
365
|
-
return None
|
|
366
|
-
|
|
367
|
-
# Compare in UTC
|
|
368
|
-
started_utc = started_at.astimezone(dt.timezone.utc)
|
|
369
|
-
create_utc = create_time.astimezone(dt.timezone.utc)
|
|
370
|
-
delta = abs((create_utc - started_utc).total_seconds())
|
|
371
|
-
return delta <= IDENTITY_TOLERANCE_SEC
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
def terminate_process_tree(pid: int) -> None:
|
|
375
|
-
"""Stop process (and children on Windows via taskkill /T). Never raises for missing pid."""
|
|
376
|
-
if not isinstance(pid, int) or pid <= 0:
|
|
377
|
-
return
|
|
378
|
-
if not is_pid_alive(pid):
|
|
379
|
-
return
|
|
380
|
-
if os.name == "nt":
|
|
381
|
-
try:
|
|
382
|
-
subprocess.run(
|
|
383
|
-
["taskkill", "/PID", str(pid), "/T", "/F"],
|
|
384
|
-
capture_output=True,
|
|
385
|
-
text=True,
|
|
386
|
-
encoding="utf-8",
|
|
387
|
-
errors="replace",
|
|
388
|
-
timeout=30,
|
|
389
|
-
check=False,
|
|
390
|
-
)
|
|
391
|
-
except (OSError, subprocess.TimeoutExpired):
|
|
392
|
-
pass
|
|
393
|
-
return
|
|
394
|
-
try:
|
|
395
|
-
os.killpg(pid, signal.SIGTERM)
|
|
396
|
-
except OSError:
|
|
397
|
-
try:
|
|
398
|
-
os.kill(pid, signal.SIGTERM)
|
|
399
|
-
except OSError:
|
|
400
|
-
pass
|
|
401
|
-
# Brief wait then SIGKILL
|
|
402
|
-
deadline = time.monotonic() + 3.0
|
|
403
|
-
while time.monotonic() < deadline and is_pid_alive(pid):
|
|
404
|
-
time.sleep(0.1)
|
|
405
|
-
if is_pid_alive(pid):
|
|
406
|
-
try:
|
|
407
|
-
os.killpg(pid, signal.SIGKILL)
|
|
408
|
-
except OSError:
|
|
409
|
-
try:
|
|
410
|
-
os.kill(pid, signal.SIGKILL)
|
|
411
|
-
except OSError:
|
|
412
|
-
pass
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
# ---------------------------------------------------------------------------
|
|
416
|
-
# Port / health probing
|
|
417
|
-
# ---------------------------------------------------------------------------
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
def extract_port(service_start: dict[str, Any]) -> int | None:
|
|
421
|
-
for key in ("port", "listenPort"):
|
|
422
|
-
val = service_start.get(key)
|
|
423
|
-
if isinstance(val, int) and 0 < val < 65536:
|
|
424
|
-
return val
|
|
425
|
-
if isinstance(val, str) and val.strip().isdigit():
|
|
426
|
-
p = int(val.strip())
|
|
427
|
-
if 0 < p < 65536:
|
|
428
|
-
return p
|
|
429
|
-
|
|
430
|
-
health = service_start.get("healthUrl") or service_start.get("healthFile") or ""
|
|
431
|
-
if not isinstance(health, str) or not health.strip():
|
|
432
|
-
return None
|
|
433
|
-
return port_from_health_spec(health.strip())
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
def port_from_health_spec(spec: str) -> int | None:
|
|
437
|
-
lower = spec.lower()
|
|
438
|
-
if lower.startswith("file:") or lower.startswith("path:"):
|
|
439
|
-
return None
|
|
440
|
-
# bare path (Windows drive or relative) — not a URL
|
|
441
|
-
if re.match(r"^[A-Za-z]:[\\/]", spec) or (os.sep in spec and "://" not in spec):
|
|
442
|
-
return None
|
|
443
|
-
if lower.startswith("tcp://") or lower.startswith("socket://"):
|
|
444
|
-
rest = spec.split("://", 1)[1]
|
|
445
|
-
host_port = rest.split("/", 1)[0]
|
|
446
|
-
if ":" in host_port:
|
|
447
|
-
try:
|
|
448
|
-
return int(host_port.rsplit(":", 1)[1])
|
|
449
|
-
except ValueError:
|
|
450
|
-
return None
|
|
451
|
-
return None
|
|
452
|
-
try:
|
|
453
|
-
parsed = urlparse(spec if "://" in spec else f"http://{spec}")
|
|
454
|
-
if parsed.port:
|
|
455
|
-
return int(parsed.port)
|
|
456
|
-
except ValueError:
|
|
457
|
-
return None
|
|
458
|
-
return None
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
def is_port_in_use(port: int, host: str = "127.0.0.1") -> bool:
|
|
462
|
-
"""True if something is accepting connections on host:port."""
|
|
463
|
-
try:
|
|
464
|
-
with socket.create_connection((host, port), timeout=0.5):
|
|
465
|
-
return True
|
|
466
|
-
except OSError:
|
|
467
|
-
return False
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
def resolve_health_file(spec: str) -> Path | None:
|
|
471
|
-
lower = spec.lower()
|
|
472
|
-
if lower.startswith("file:"):
|
|
473
|
-
raw = spec[5:]
|
|
474
|
-
# file:///C:/path or file:C:/path or file:/path
|
|
475
|
-
if raw.startswith("///"):
|
|
476
|
-
raw = raw[3:]
|
|
477
|
-
elif raw.startswith("//"):
|
|
478
|
-
raw = raw[2:]
|
|
479
|
-
return Path(raw)
|
|
480
|
-
if lower.startswith("path:"):
|
|
481
|
-
return Path(spec[5:])
|
|
482
|
-
if re.match(r"^[A-Za-z]:[\\/]", spec) or (spec.startswith(".") and "://" not in spec):
|
|
483
|
-
return Path(spec)
|
|
484
|
-
return None
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
def probe_health(service_start: dict[str, Any]) -> bool:
|
|
488
|
-
"""Return True if service appears healthy per serviceStart health config."""
|
|
489
|
-
# Prefer explicit healthFile
|
|
490
|
-
health_file = service_start.get("healthFile")
|
|
491
|
-
if isinstance(health_file, str) and health_file.strip():
|
|
492
|
-
return Path(health_file.strip()).is_file()
|
|
493
|
-
|
|
494
|
-
spec = service_start.get("healthUrl")
|
|
495
|
-
if not isinstance(spec, str) or not spec.strip():
|
|
496
|
-
# No health probe configured → treat as healthy once process is up
|
|
497
|
-
# (caller should still have started the process). For wait loop, require
|
|
498
|
-
# at least that we have a running pid — handled by caller.
|
|
499
|
-
return True
|
|
500
|
-
|
|
501
|
-
spec = spec.strip()
|
|
502
|
-
file_path = resolve_health_file(spec)
|
|
503
|
-
if file_path is not None:
|
|
504
|
-
return file_path.is_file()
|
|
505
|
-
|
|
506
|
-
lower = spec.lower()
|
|
507
|
-
if lower.startswith("tcp://") or lower.startswith("socket://"):
|
|
508
|
-
rest = spec.split("://", 1)[1]
|
|
509
|
-
host_port = rest.split("/", 1)[0]
|
|
510
|
-
if ":" not in host_port:
|
|
511
|
-
return False
|
|
512
|
-
host, port_s = host_port.rsplit(":", 1)
|
|
513
|
-
try:
|
|
514
|
-
port = int(port_s)
|
|
515
|
-
except ValueError:
|
|
516
|
-
return False
|
|
517
|
-
return is_port_in_use(port, host or "127.0.0.1")
|
|
518
|
-
|
|
519
|
-
# HTTP(S)
|
|
520
|
-
url = spec if "://" in spec else f"http://{spec}"
|
|
521
|
-
try:
|
|
522
|
-
req = urllib.request.Request(url, method="GET")
|
|
523
|
-
with urllib.request.urlopen(req, timeout=2.0) as resp:
|
|
524
|
-
return 200 <= int(getattr(resp, "status", 200)) < 300
|
|
525
|
-
except (urllib.error.URLError, urllib.error.HTTPError, TimeoutError, OSError):
|
|
526
|
-
return False
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
def log_has_fatal(path: Path) -> str | None:
|
|
530
|
-
if not path.is_file():
|
|
531
|
-
return None
|
|
532
|
-
try:
|
|
533
|
-
text = path.read_text(encoding="utf-8", errors="replace")
|
|
534
|
-
except OSError:
|
|
535
|
-
return None
|
|
536
|
-
# Check last ~200 lines for fatal keywords
|
|
537
|
-
lines = text.splitlines()
|
|
538
|
-
tail = "\n".join(lines[-200:])
|
|
539
|
-
for kw in FATAL_KEYWORDS:
|
|
540
|
-
if kw in tail:
|
|
541
|
-
return kw
|
|
542
|
-
return None
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
class ServiceStartError(Exception):
|
|
546
|
-
def __init__(self, message: str, *, fatal_keyword: str | None = None) -> None:
|
|
547
|
-
super().__init__(message)
|
|
548
|
-
self.fatal_keyword = fatal_keyword
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
def wait_for_healthy(
|
|
552
|
-
service_start: dict[str, Any],
|
|
553
|
-
log_file: Path,
|
|
554
|
-
*,
|
|
555
|
-
pid: int | None = None,
|
|
556
|
-
) -> None:
|
|
557
|
-
"""Startup wait state machine: 0–30s /2s, 30–120s /5s; fatal keywords abort."""
|
|
558
|
-
timeout_sec = service_start.get("startTimeoutSec", 120)
|
|
559
|
-
try:
|
|
560
|
-
timeout_sec = float(timeout_sec)
|
|
561
|
-
except (TypeError, ValueError):
|
|
562
|
-
timeout_sec = 120.0
|
|
563
|
-
if timeout_sec <= 0:
|
|
564
|
-
timeout_sec = 120.0
|
|
565
|
-
|
|
566
|
-
start = time.monotonic()
|
|
567
|
-
while True:
|
|
568
|
-
elapsed = time.monotonic() - start
|
|
569
|
-
fatal = log_has_fatal(log_file)
|
|
570
|
-
if fatal:
|
|
571
|
-
raise ServiceStartError(
|
|
572
|
-
f"service start aborted: fatal keyword in log: {fatal}",
|
|
573
|
-
fatal_keyword=fatal,
|
|
574
|
-
)
|
|
575
|
-
if pid is not None and not is_pid_alive(pid):
|
|
576
|
-
raise ServiceStartError(f"service process exited early (pid={pid})")
|
|
577
|
-
if probe_health(service_start):
|
|
578
|
-
return
|
|
579
|
-
if elapsed >= timeout_sec:
|
|
580
|
-
raise ServiceStartError(
|
|
581
|
-
f"service start timed out after {timeout_sec:.0f}s "
|
|
582
|
-
f"(health probe not ready; see {log_file})"
|
|
583
|
-
)
|
|
584
|
-
# 0–30s every 2s; 30–120s every 5s
|
|
585
|
-
if elapsed < 30.0:
|
|
586
|
-
time.sleep(2.0)
|
|
587
|
-
else:
|
|
588
|
-
time.sleep(5.0)
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
# ---------------------------------------------------------------------------
|
|
592
|
-
# Session I/O
|
|
593
|
-
# ---------------------------------------------------------------------------
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
class SessionCorrupt(Exception):
|
|
597
|
-
pass
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
def load_session(change_dir: Path) -> dict[str, Any] | None:
|
|
601
|
-
"""Return session dict, None if missing, raise SessionCorrupt if damaged."""
|
|
602
|
-
path = session_path(change_dir)
|
|
603
|
-
if not path.is_file():
|
|
604
|
-
return None
|
|
605
|
-
try:
|
|
606
|
-
text = path.read_text(encoding="utf-8-sig")
|
|
607
|
-
except OSError as exc:
|
|
608
|
-
raise SessionCorrupt(f"cannot read session: {exc}") from exc
|
|
609
|
-
if not text.strip():
|
|
610
|
-
raise SessionCorrupt("session file is empty")
|
|
611
|
-
try:
|
|
612
|
-
data = json.loads(text)
|
|
613
|
-
except json.JSONDecodeError as exc:
|
|
614
|
-
raise SessionCorrupt(f"session JSON corrupt: {exc}") from exc
|
|
615
|
-
if not isinstance(data, dict):
|
|
616
|
-
raise SessionCorrupt("session must be a JSON object")
|
|
617
|
-
# Minimal required fields for a usable AI session
|
|
618
|
-
pid = data.get("pid")
|
|
619
|
-
if not isinstance(pid, int):
|
|
620
|
-
raise SessionCorrupt("session.pid missing or not an int")
|
|
621
|
-
if "startedBy" not in data:
|
|
622
|
-
raise SessionCorrupt("session.startedBy missing")
|
|
623
|
-
if "startedAt" not in data:
|
|
624
|
-
raise SessionCorrupt("session.startedAt missing")
|
|
625
|
-
return data
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
def clear_session(change_dir: Path) -> None:
|
|
629
|
-
path = session_path(change_dir)
|
|
630
|
-
if path.is_file():
|
|
631
|
-
try:
|
|
632
|
-
path.unlink()
|
|
633
|
-
except OSError:
|
|
634
|
-
pass
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
def write_session(change_dir: Path, session: dict[str, Any]) -> Path:
|
|
638
|
-
path = session_path(change_dir)
|
|
639
|
-
write_json(path, session)
|
|
640
|
-
return path
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
# ---------------------------------------------------------------------------
|
|
644
|
-
# Start / stop helpers
|
|
645
|
-
# ---------------------------------------------------------------------------
|
|
646
|
-
|
|
647
|
-
_WIN_LAUNCHER_SOURCE = """\
|
|
648
|
-
#!/usr/bin/env python3
|
|
649
|
-
\"\"\"Harness Windows service launcher (internal).\"\"\"
|
|
650
|
-
from __future__ import annotations
|
|
651
|
-
|
|
652
|
-
import subprocess
|
|
653
|
-
import sys
|
|
654
|
-
from pathlib import Path
|
|
655
|
-
|
|
656
|
-
_CREATE_NEW_PROCESS_GROUP = 0x00000200
|
|
657
|
-
_CREATE_NO_WINDOW = 0x08000000
|
|
658
|
-
_CREATE_BREAKAWAY_FROM_JOB = 0x01000000
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
def main() -> int:
|
|
662
|
-
log_path = Path(sys.argv[1])
|
|
663
|
-
command_path = Path(sys.argv[2])
|
|
664
|
-
pid_path = Path(sys.argv[3])
|
|
665
|
-
command = command_path.read_text(encoding="utf-8").strip()
|
|
666
|
-
if not command:
|
|
667
|
-
raise SystemExit("empty service command")
|
|
668
|
-
|
|
669
|
-
log_path.parent.mkdir(parents=True, exist_ok=True)
|
|
670
|
-
log_handle = log_path.open("w", encoding="utf-8", errors="replace")
|
|
671
|
-
try:
|
|
672
|
-
proc = subprocess.Popen(
|
|
673
|
-
command,
|
|
674
|
-
shell=True,
|
|
675
|
-
stdout=log_handle,
|
|
676
|
-
stderr=subprocess.STDOUT,
|
|
677
|
-
stdin=subprocess.DEVNULL,
|
|
678
|
-
creationflags=(
|
|
679
|
-
_CREATE_NEW_PROCESS_GROUP
|
|
680
|
-
| _CREATE_NO_WINDOW
|
|
681
|
-
| _CREATE_BREAKAWAY_FROM_JOB
|
|
682
|
-
),
|
|
683
|
-
close_fds=False,
|
|
684
|
-
)
|
|
685
|
-
finally:
|
|
686
|
-
log_handle.close()
|
|
687
|
-
|
|
688
|
-
pid_path.write_text(str(proc.pid), encoding="utf-8")
|
|
689
|
-
# Exit immediately: the detached service (shell) continues on its own.
|
|
690
|
-
# Staying alive to wait() would keep the launcher process running and
|
|
691
|
-
# trigger ResourceWarning when the harness_service Popen is GC'd.
|
|
692
|
-
return 0
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
if __name__ == "__main__":
|
|
696
|
-
raise SystemExit(main())
|
|
697
|
-
"""
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
def _runtime_dir(change_dir: Path) -> Path:
|
|
701
|
-
path = change_dir / "runtime"
|
|
702
|
-
path.mkdir(parents=True, exist_ok=True)
|
|
703
|
-
return path
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
def _launcher_pid_path(change_dir: Path) -> Path:
|
|
707
|
-
return _runtime_dir(change_dir) / "_harness_service.launcher.pid"
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
def _cleanup_windows_launcher(change_dir: Path) -> None:
|
|
711
|
-
if os.name != "nt":
|
|
712
|
-
return
|
|
713
|
-
path = _launcher_pid_path(change_dir)
|
|
714
|
-
if not path.is_file():
|
|
715
|
-
return
|
|
716
|
-
text = path.read_text(encoding="utf-8").strip()
|
|
717
|
-
try:
|
|
718
|
-
path.unlink()
|
|
719
|
-
except OSError:
|
|
720
|
-
pass
|
|
721
|
-
if text.isdigit():
|
|
722
|
-
pid = int(text)
|
|
723
|
-
if is_pid_alive(pid):
|
|
724
|
-
terminate_process_tree(pid)
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
def _write_windows_launcher(change_dir: Path) -> Path:
|
|
728
|
-
launcher = _runtime_dir(change_dir) / "_harness_service_launcher.py"
|
|
729
|
-
launcher.write_text(_WIN_LAUNCHER_SOURCE, encoding="utf-8", newline="\n")
|
|
730
|
-
return launcher
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
def _wait_for_child_pid(pid_path: Path, *, timeout_sec: float = 15.0) -> int:
|
|
734
|
-
deadline = time.monotonic() + timeout_sec
|
|
735
|
-
while time.monotonic() < deadline:
|
|
736
|
-
if pid_path.is_file():
|
|
737
|
-
text = pid_path.read_text(encoding="utf-8").strip()
|
|
738
|
-
if text.isdigit():
|
|
739
|
-
return int(text)
|
|
740
|
-
time.sleep(0.05)
|
|
741
|
-
raise TimeoutError(f"service child pid not recorded within {timeout_sec:.0f}s ({pid_path})")
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
def _start_detached_service_windows(
|
|
745
|
-
command: str,
|
|
746
|
-
*,
|
|
747
|
-
change_dir: Path,
|
|
748
|
-
cwd: Path,
|
|
749
|
-
log_file: Path,
|
|
750
|
-
) -> int:
|
|
751
|
-
runtime = _runtime_dir(change_dir)
|
|
752
|
-
launcher = _write_windows_launcher(change_dir)
|
|
753
|
-
command_path = runtime / "_harness_service.command.txt"
|
|
754
|
-
pid_path = runtime / "_harness_service.child.pid"
|
|
755
|
-
command_path.write_text(command, encoding="utf-8", newline="\n")
|
|
756
|
-
if pid_path.exists():
|
|
757
|
-
pid_path.unlink()
|
|
758
|
-
|
|
759
|
-
launcher_args = [
|
|
760
|
-
sys.executable,
|
|
761
|
-
str(launcher),
|
|
762
|
-
str(log_file),
|
|
763
|
-
str(command_path),
|
|
764
|
-
str(pid_path),
|
|
765
|
-
]
|
|
766
|
-
win_flags = (
|
|
767
|
-
_DETACHED_PROCESS
|
|
768
|
-
| _CREATE_NEW_PROCESS_GROUP
|
|
769
|
-
| _CREATE_NO_WINDOW
|
|
770
|
-
| _CREATE_BREAKAWAY_FROM_JOB
|
|
771
|
-
)
|
|
772
|
-
launcher_proc = subprocess.Popen(
|
|
773
|
-
launcher_args,
|
|
774
|
-
cwd=str(cwd),
|
|
775
|
-
stdin=subprocess.DEVNULL,
|
|
776
|
-
stdout=subprocess.DEVNULL,
|
|
777
|
-
stderr=subprocess.DEVNULL,
|
|
778
|
-
creationflags=win_flags,
|
|
779
|
-
close_fds=False,
|
|
780
|
-
)
|
|
781
|
-
_launcher_pid_path(change_dir).write_text(str(launcher_proc.pid), encoding="utf-8")
|
|
782
|
-
# Reap the short-lived launcher (it exits right after recording the child
|
|
783
|
-
# pid) so its Popen does not ResourceWarning at GC.
|
|
784
|
-
try:
|
|
785
|
-
launcher_proc.wait(timeout=5.0)
|
|
786
|
-
except subprocess.TimeoutExpired:
|
|
787
|
-
pass
|
|
788
|
-
return _wait_for_child_pid(pid_path)
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
def start_detached_service(
|
|
792
|
-
command: str,
|
|
793
|
-
*,
|
|
794
|
-
change_dir: Path,
|
|
795
|
-
cwd: Path,
|
|
796
|
-
) -> int:
|
|
797
|
-
"""Start service detached from this script; log to logs/service-start.log."""
|
|
798
|
-
log_file = log_path(change_dir)
|
|
799
|
-
log_file.parent.mkdir(parents=True, exist_ok=True)
|
|
800
|
-
if log_file.is_file():
|
|
801
|
-
log_file.unlink()
|
|
802
|
-
|
|
803
|
-
if os.name == "nt":
|
|
804
|
-
return _start_detached_service_windows(
|
|
805
|
-
command,
|
|
806
|
-
change_dir=change_dir,
|
|
807
|
-
cwd=cwd,
|
|
808
|
-
log_file=log_file,
|
|
809
|
-
)
|
|
810
|
-
|
|
811
|
-
log_handle = log_file.open("w", encoding="utf-8", errors="replace")
|
|
812
|
-
popen_kwargs: dict[str, Any] = {
|
|
813
|
-
"args": command,
|
|
814
|
-
"shell": True,
|
|
815
|
-
"stdout": log_handle,
|
|
816
|
-
"stderr": subprocess.STDOUT,
|
|
817
|
-
"cwd": str(cwd),
|
|
818
|
-
"stdin": subprocess.DEVNULL,
|
|
819
|
-
"start_new_session": True,
|
|
820
|
-
"close_fds": True,
|
|
821
|
-
}
|
|
822
|
-
try:
|
|
823
|
-
proc = subprocess.Popen(**popen_kwargs)
|
|
824
|
-
finally:
|
|
825
|
-
try:
|
|
826
|
-
log_handle.close()
|
|
827
|
-
except OSError:
|
|
828
|
-
pass
|
|
829
|
-
|
|
830
|
-
return int(proc.pid)
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
def build_session(
|
|
834
|
-
*,
|
|
835
|
-
pid: int,
|
|
836
|
-
module_inputs_hash: str,
|
|
837
|
-
module_inputs_files: list[str],
|
|
838
|
-
command: str,
|
|
839
|
-
service_start: dict[str, Any],
|
|
840
|
-
started_at: str | None = None,
|
|
841
|
-
) -> dict[str, Any]:
|
|
842
|
-
profile_name = service_start.get("profile") or "local-dev"
|
|
843
|
-
overlay = service_start.get("overlayPath") or ""
|
|
844
|
-
return {
|
|
845
|
-
"pid": pid,
|
|
846
|
-
"startedBy": "AI",
|
|
847
|
-
"moduleInputsHash": module_inputs_hash,
|
|
848
|
-
"moduleInputsFiles": module_inputs_files,
|
|
849
|
-
"profile": profile_name,
|
|
850
|
-
"startCommandHash": sha256_text(command),
|
|
851
|
-
"overlayPath": overlay,
|
|
852
|
-
"startedAt": started_at or now_iso(),
|
|
853
|
-
"command": command,
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
def compute_module_hash(
|
|
858
|
-
files: list[str],
|
|
859
|
-
session: dict[str, Any] | None = None,
|
|
860
|
-
) -> tuple[str, list[str]]:
|
|
861
|
-
"""Compute inputsHash from --files, else session.moduleInputsFiles, else empty."""
|
|
862
|
-
use_files = list(files)
|
|
863
|
-
if not use_files and session is not None:
|
|
864
|
-
stored = session.get("moduleInputsFiles")
|
|
865
|
-
if isinstance(stored, list):
|
|
866
|
-
use_files = [str(x) for x in stored if str(x).strip()]
|
|
867
|
-
if not use_files:
|
|
868
|
-
# Empty set → stable empty hash (order-independent)
|
|
869
|
-
return compute_inputs_hash([])
|
|
870
|
-
return compute_inputs_hash(use_files)
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
def needs_user_decision(
|
|
874
|
-
*,
|
|
875
|
-
reason: str,
|
|
876
|
-
as_json: bool,
|
|
877
|
-
**extra: Any,
|
|
878
|
-
) -> int:
|
|
879
|
-
payload: dict[str, Any] = {
|
|
880
|
-
"ok": True,
|
|
881
|
-
"action": "needs-user-decision",
|
|
882
|
-
"reason": reason,
|
|
883
|
-
}
|
|
884
|
-
payload.update(extra)
|
|
885
|
-
emit_json(payload, as_json=as_json)
|
|
886
|
-
return 0
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
def stop_ai_session(
|
|
890
|
-
change_dir: Path,
|
|
891
|
-
session: dict[str, Any],
|
|
892
|
-
*,
|
|
893
|
-
require_identity: bool = True,
|
|
894
|
-
) -> dict[str, Any]:
|
|
895
|
-
"""Stop verified AI session process and clear session file."""
|
|
896
|
-
pid = session.get("pid")
|
|
897
|
-
if require_identity:
|
|
898
|
-
identity = verify_process_identity(session)
|
|
899
|
-
if identity is not True:
|
|
900
|
-
return {
|
|
901
|
-
"ok": True,
|
|
902
|
-
"action": "needs-user-decision",
|
|
903
|
-
"reason": (
|
|
904
|
-
"cannot-verify-process-identity"
|
|
905
|
-
if identity is None
|
|
906
|
-
else "process-identity-mismatch"
|
|
907
|
-
),
|
|
908
|
-
"pid": pid,
|
|
909
|
-
"killed": False,
|
|
910
|
-
}
|
|
911
|
-
if isinstance(pid, int) and is_pid_alive(pid):
|
|
912
|
-
terminate_process_tree(pid)
|
|
913
|
-
_cleanup_windows_launcher(change_dir)
|
|
914
|
-
clear_session(change_dir)
|
|
915
|
-
return {
|
|
916
|
-
"ok": True,
|
|
917
|
-
"action": "stopped",
|
|
918
|
-
"pid": pid,
|
|
919
|
-
"killed": True,
|
|
920
|
-
"sessionCleared": True,
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
# ---------------------------------------------------------------------------
|
|
925
|
-
# Subcommands
|
|
926
|
-
# ---------------------------------------------------------------------------
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
def cmd_ensure(args: argparse.Namespace) -> int:
|
|
930
|
-
as_json = bool(args.json)
|
|
931
|
-
change_dir = resolve_path(args.change_dir)
|
|
932
|
-
project = resolve_path(args.project)
|
|
933
|
-
files = parse_files_arg(getattr(args, "files", None))
|
|
934
|
-
|
|
935
|
-
try:
|
|
936
|
-
profile = load_build_profile(project)
|
|
937
|
-
service_start = get_service_start(profile)
|
|
938
|
-
except (OSError, ValueError, FileNotFoundError, json.JSONDecodeError) as exc:
|
|
939
|
-
return emit_error(str(exc), as_json=as_json)
|
|
940
|
-
|
|
941
|
-
command = str(service_start["command"]).strip()
|
|
942
|
-
port = extract_port(service_start)
|
|
943
|
-
|
|
944
|
-
# Resolve service input file set (CLI --files ∪ serviceStart.inputFiles).
|
|
945
|
-
# Empty result is deferred: _start_and_record rejects it before generating a
|
|
946
|
-
# reusable empty fingerprint; the port-occupied / reuse paths still proceed.
|
|
947
|
-
try:
|
|
948
|
-
files = resolve_service_input_files(project, service_start, files)
|
|
949
|
-
except ValueError:
|
|
950
|
-
files = []
|
|
951
|
-
|
|
952
|
-
try:
|
|
953
|
-
session = load_session(change_dir)
|
|
954
|
-
except SessionCorrupt as exc:
|
|
955
|
-
return needs_user_decision(
|
|
956
|
-
reason=f"session-corrupt: {exc}",
|
|
957
|
-
as_json=as_json,
|
|
958
|
-
detail="missing or corrupt session is treated as a user process; will not kill",
|
|
959
|
-
)
|
|
960
|
-
|
|
961
|
-
# --- Branch 1: existing AI session with live verified process ---
|
|
962
|
-
if session is not None:
|
|
963
|
-
pid = session["pid"]
|
|
964
|
-
alive = is_pid_alive(pid)
|
|
965
|
-
if alive:
|
|
966
|
-
identity = verify_process_identity(session)
|
|
967
|
-
if identity is not True:
|
|
968
|
-
return needs_user_decision(
|
|
969
|
-
reason=(
|
|
970
|
-
"cannot-verify-process-identity"
|
|
971
|
-
if identity is None
|
|
972
|
-
else "process-identity-mismatch"
|
|
973
|
-
),
|
|
974
|
-
as_json=as_json,
|
|
975
|
-
pid=pid,
|
|
976
|
-
sessionPath=str(session_path(change_dir)),
|
|
977
|
-
detail=(
|
|
978
|
-
"pid is alive but create-time/cmdline identity could not be "
|
|
979
|
-
"confirmed against session; refusing to reuse or kill"
|
|
980
|
-
),
|
|
981
|
-
)
|
|
982
|
-
|
|
983
|
-
try:
|
|
984
|
-
current_hash, current_files = compute_module_hash(files, session)
|
|
985
|
-
except (OSError, FileNotFoundError) as exc:
|
|
986
|
-
return emit_error(f"inputsHash failed: {exc}", as_json=as_json)
|
|
987
|
-
|
|
988
|
-
stored_hash = session.get("moduleInputsHash")
|
|
989
|
-
# §5.3: reuse must compare inputsHash + startCommandHash + profile
|
|
990
|
-
# + overlayPath (process identity already verified above). Any
|
|
991
|
-
# change -> restart; never reuse on a partial match.
|
|
992
|
-
current_cmd_hash = sha256_text(command)
|
|
993
|
-
current_profile = service_start.get("profile") or "local-dev"
|
|
994
|
-
current_overlay = service_start.get("overlayPath") or ""
|
|
995
|
-
fingerprint_match = (
|
|
996
|
-
stored_hash == current_hash
|
|
997
|
-
and session.get("startCommandHash") == current_cmd_hash
|
|
998
|
-
and session.get("profile") == current_profile
|
|
999
|
-
and session.get("overlayPath") == current_overlay
|
|
1000
|
-
)
|
|
1001
|
-
if fingerprint_match:
|
|
1002
|
-
payload = {
|
|
1003
|
-
"ok": True,
|
|
1004
|
-
"action": "reused",
|
|
1005
|
-
"pid": pid,
|
|
1006
|
-
"moduleInputsHash": current_hash,
|
|
1007
|
-
"moduleInputsFiles": current_files,
|
|
1008
|
-
"sessionPath": str(session_path(change_dir)),
|
|
1009
|
-
}
|
|
1010
|
-
emit_json(payload, as_json=as_json)
|
|
1011
|
-
return 0
|
|
1012
|
-
|
|
1013
|
-
# Fingerprint mismatch → stop old, start new
|
|
1014
|
-
stop_result = stop_ai_session(change_dir, session, require_identity=True)
|
|
1015
|
-
if stop_result.get("action") == "needs-user-decision":
|
|
1016
|
-
emit_json(stop_result, as_json=as_json)
|
|
1017
|
-
return 0
|
|
1018
|
-
|
|
1019
|
-
return _start_and_record(
|
|
1020
|
-
change_dir=change_dir,
|
|
1021
|
-
project=project,
|
|
1022
|
-
service_start=service_start,
|
|
1023
|
-
command=command,
|
|
1024
|
-
files=files if files else current_files,
|
|
1025
|
-
as_json=as_json,
|
|
1026
|
-
action="restarted",
|
|
1027
|
-
previousPid=pid,
|
|
1028
|
-
)
|
|
1029
|
-
|
|
1030
|
-
# pid dead → stale session; clear and fall through
|
|
1031
|
-
clear_session(change_dir)
|
|
1032
|
-
|
|
1033
|
-
# --- Branch 2: no usable session; port occupied → user decision ---
|
|
1034
|
-
if port is not None and is_port_in_use(port):
|
|
1035
|
-
return needs_user_decision(
|
|
1036
|
-
reason="port-occupied-without-ai-session",
|
|
1037
|
-
as_json=as_json,
|
|
1038
|
-
port=port,
|
|
1039
|
-
detail=(
|
|
1040
|
-
"port is in use but no verified AI service-session exists; "
|
|
1041
|
-
"treated as user process — will not kill"
|
|
1042
|
-
),
|
|
1043
|
-
)
|
|
1044
|
-
|
|
1045
|
-
# --- Branch 3: start fresh ---
|
|
1046
|
-
return _start_and_record(
|
|
1047
|
-
change_dir=change_dir,
|
|
1048
|
-
project=project,
|
|
1049
|
-
service_start=service_start,
|
|
1050
|
-
command=command,
|
|
1051
|
-
files=files,
|
|
1052
|
-
as_json=as_json,
|
|
1053
|
-
action="started",
|
|
1054
|
-
)
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
def _start_and_record(
|
|
1058
|
-
*,
|
|
1059
|
-
change_dir: Path,
|
|
1060
|
-
project: Path,
|
|
1061
|
-
service_start: dict[str, Any],
|
|
1062
|
-
command: str,
|
|
1063
|
-
files: list[str],
|
|
1064
|
-
as_json: bool,
|
|
1065
|
-
action: str,
|
|
1066
|
-
**extra: Any,
|
|
1067
|
-
) -> int:
|
|
1068
|
-
if not files:
|
|
1069
|
-
# §5.1/§5.2: never generate a reusable empty service fingerprint.
|
|
1070
|
-
return emit_error(
|
|
1071
|
-
"service inputs are empty; configure serviceStart.inputFiles "
|
|
1072
|
-
"(or pass --files) so the service fingerprint covers real source",
|
|
1073
|
-
as_json=as_json,
|
|
1074
|
-
)
|
|
1075
|
-
try:
|
|
1076
|
-
module_hash, module_files = compute_module_hash(files, None)
|
|
1077
|
-
except (OSError, FileNotFoundError) as exc:
|
|
1078
|
-
return emit_error(f"inputsHash failed: {exc}", as_json=as_json)
|
|
1079
|
-
|
|
1080
|
-
# Clear stale file-based health markers so wait_for_healthy cannot
|
|
1081
|
-
# succeed on a leftover marker from a previous process.
|
|
1082
|
-
_clear_file_health_markers(service_start)
|
|
1083
|
-
|
|
1084
|
-
started_at = now_iso()
|
|
1085
|
-
try:
|
|
1086
|
-
pid = start_detached_service(command, change_dir=change_dir, cwd=project)
|
|
1087
|
-
except (OSError, TimeoutError) as exc:
|
|
1088
|
-
_cleanup_windows_launcher(change_dir)
|
|
1089
|
-
return emit_error(f"failed to start service: {exc}", as_json=as_json)
|
|
1090
|
-
|
|
1091
|
-
# Give the OS a moment to register the process before identity/create-time reads
|
|
1092
|
-
time.sleep(0.15)
|
|
1093
|
-
|
|
1094
|
-
try:
|
|
1095
|
-
wait_for_healthy(service_start, log_path(change_dir), pid=pid)
|
|
1096
|
-
except ServiceStartError as exc:
|
|
1097
|
-
# Best-effort cleanup of the failed start
|
|
1098
|
-
if is_pid_alive(pid):
|
|
1099
|
-
terminate_process_tree(pid)
|
|
1100
|
-
_cleanup_windows_launcher(change_dir)
|
|
1101
|
-
clear_session(change_dir)
|
|
1102
|
-
return emit_error(
|
|
1103
|
-
str(exc),
|
|
1104
|
-
as_json=as_json,
|
|
1105
|
-
action="start-failed",
|
|
1106
|
-
pid=pid,
|
|
1107
|
-
fatalKeyword=exc.fatal_keyword,
|
|
1108
|
-
)
|
|
1109
|
-
|
|
1110
|
-
session = build_session(
|
|
1111
|
-
pid=pid,
|
|
1112
|
-
module_inputs_hash=module_hash,
|
|
1113
|
-
module_inputs_files=module_files,
|
|
1114
|
-
command=command,
|
|
1115
|
-
service_start=service_start,
|
|
1116
|
-
started_at=started_at,
|
|
1117
|
-
)
|
|
1118
|
-
write_session(change_dir, session)
|
|
1119
|
-
|
|
1120
|
-
payload: dict[str, Any] = {
|
|
1121
|
-
"ok": True,
|
|
1122
|
-
"action": action,
|
|
1123
|
-
"pid": pid,
|
|
1124
|
-
"moduleInputsHash": module_hash,
|
|
1125
|
-
"moduleInputsFiles": module_files,
|
|
1126
|
-
"sessionPath": str(session_path(change_dir)),
|
|
1127
|
-
"logPath": str(log_path(change_dir)),
|
|
1128
|
-
"startedAt": started_at,
|
|
1129
|
-
}
|
|
1130
|
-
payload.update(extra)
|
|
1131
|
-
emit_json(payload, as_json=as_json)
|
|
1132
|
-
return 0
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
def _clear_file_health_markers(service_start: dict[str, Any]) -> None:
|
|
1136
|
-
candidates: list[Path] = []
|
|
1137
|
-
hf = service_start.get("healthFile")
|
|
1138
|
-
if isinstance(hf, str) and hf.strip():
|
|
1139
|
-
candidates.append(Path(hf.strip()))
|
|
1140
|
-
spec = service_start.get("healthUrl")
|
|
1141
|
-
if isinstance(spec, str) and spec.strip():
|
|
1142
|
-
resolved = resolve_health_file(spec.strip())
|
|
1143
|
-
if resolved is not None:
|
|
1144
|
-
candidates.append(resolved)
|
|
1145
|
-
for path in candidates:
|
|
1146
|
-
try:
|
|
1147
|
-
if path.is_file():
|
|
1148
|
-
path.unlink()
|
|
1149
|
-
except OSError:
|
|
1150
|
-
pass
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
def cmd_status(args: argparse.Namespace) -> int:
|
|
1154
|
-
as_json = bool(args.json)
|
|
1155
|
-
change_dir = resolve_path(args.change_dir)
|
|
1156
|
-
files = parse_files_arg(getattr(args, "files", None))
|
|
1157
|
-
|
|
1158
|
-
try:
|
|
1159
|
-
session = load_session(change_dir)
|
|
1160
|
-
except SessionCorrupt as exc:
|
|
1161
|
-
payload = {
|
|
1162
|
-
"ok": True,
|
|
1163
|
-
"action": "status",
|
|
1164
|
-
"sessionPresent": True,
|
|
1165
|
-
"sessionCorrupt": True,
|
|
1166
|
-
"reason": str(exc),
|
|
1167
|
-
"alive": False,
|
|
1168
|
-
"identityVerified": False,
|
|
1169
|
-
"fingerprintMatch": None,
|
|
1170
|
-
"treatAsUserProcess": True,
|
|
1171
|
-
}
|
|
1172
|
-
emit_json(payload, as_json=as_json)
|
|
1173
|
-
return 0
|
|
1174
|
-
|
|
1175
|
-
if session is None:
|
|
1176
|
-
payload = {
|
|
1177
|
-
"ok": True,
|
|
1178
|
-
"action": "status",
|
|
1179
|
-
"sessionPresent": False,
|
|
1180
|
-
"alive": False,
|
|
1181
|
-
"identityVerified": False,
|
|
1182
|
-
"fingerprintMatch": None,
|
|
1183
|
-
}
|
|
1184
|
-
emit_json(payload, as_json=as_json)
|
|
1185
|
-
return 0
|
|
1186
|
-
|
|
1187
|
-
pid = session.get("pid")
|
|
1188
|
-
alive = isinstance(pid, int) and is_pid_alive(pid)
|
|
1189
|
-
identity = verify_process_identity(session) if alive else False
|
|
1190
|
-
|
|
1191
|
-
fingerprint_match: bool | None = None
|
|
1192
|
-
current_hash: str | None = None
|
|
1193
|
-
try:
|
|
1194
|
-
current_hash, _ = compute_module_hash(files, session)
|
|
1195
|
-
stored = session.get("moduleInputsHash")
|
|
1196
|
-
if isinstance(stored, str) and stored:
|
|
1197
|
-
fingerprint_match = stored == current_hash
|
|
1198
|
-
except (OSError, FileNotFoundError):
|
|
1199
|
-
fingerprint_match = None
|
|
1200
|
-
|
|
1201
|
-
payload = {
|
|
1202
|
-
"ok": True,
|
|
1203
|
-
"action": "status",
|
|
1204
|
-
"sessionPresent": True,
|
|
1205
|
-
"sessionCorrupt": False,
|
|
1206
|
-
"session": session,
|
|
1207
|
-
"pid": pid,
|
|
1208
|
-
"alive": alive,
|
|
1209
|
-
"identityVerified": identity is True,
|
|
1210
|
-
"identityStatus": (
|
|
1211
|
-
"verified" if identity is True else ("unknown" if identity is None else "mismatch")
|
|
1212
|
-
),
|
|
1213
|
-
"fingerprintMatch": fingerprint_match,
|
|
1214
|
-
"currentModuleInputsHash": current_hash,
|
|
1215
|
-
"startedBy": session.get("startedBy"),
|
|
1216
|
-
}
|
|
1217
|
-
emit_json(payload, as_json=as_json)
|
|
1218
|
-
return 0
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
def cmd_stop(args: argparse.Namespace) -> int:
|
|
1222
|
-
as_json = bool(args.json)
|
|
1223
|
-
change_dir = resolve_path(args.change_dir)
|
|
1224
|
-
if_started_by_ai = bool(getattr(args, "if_started_by_ai", False))
|
|
1225
|
-
|
|
1226
|
-
try:
|
|
1227
|
-
session = load_session(change_dir)
|
|
1228
|
-
except SessionCorrupt as exc:
|
|
1229
|
-
return needs_user_decision(
|
|
1230
|
-
reason=f"session-corrupt: {exc}",
|
|
1231
|
-
as_json=as_json,
|
|
1232
|
-
detail="corrupt session treated as user process; will not kill",
|
|
1233
|
-
killed=False,
|
|
1234
|
-
)
|
|
1235
|
-
|
|
1236
|
-
if session is None:
|
|
1237
|
-
payload = {
|
|
1238
|
-
"ok": True,
|
|
1239
|
-
"action": "already-stopped",
|
|
1240
|
-
"killed": False,
|
|
1241
|
-
"sessionCleared": False,
|
|
1242
|
-
"detail": "no service-session.json",
|
|
1243
|
-
}
|
|
1244
|
-
emit_json(payload, as_json=as_json)
|
|
1245
|
-
return 0
|
|
1246
|
-
|
|
1247
|
-
started_by = session.get("startedBy")
|
|
1248
|
-
if if_started_by_ai and started_by != "AI":
|
|
1249
|
-
payload = {
|
|
1250
|
-
"ok": True,
|
|
1251
|
-
"action": "skipped",
|
|
1252
|
-
"reason": "not-started-by-ai",
|
|
1253
|
-
"startedBy": started_by,
|
|
1254
|
-
"killed": False,
|
|
1255
|
-
"sessionCleared": False,
|
|
1256
|
-
}
|
|
1257
|
-
emit_json(payload, as_json=as_json)
|
|
1258
|
-
return 0
|
|
1259
|
-
|
|
1260
|
-
# Default stop also refuses to kill non-AI / unverified processes
|
|
1261
|
-
if started_by != "AI":
|
|
1262
|
-
return needs_user_decision(
|
|
1263
|
-
reason="not-started-by-ai",
|
|
1264
|
-
as_json=as_json,
|
|
1265
|
-
startedBy=started_by,
|
|
1266
|
-
killed=False,
|
|
1267
|
-
detail="session not marked startedBy=AI; will not kill",
|
|
1268
|
-
)
|
|
1269
|
-
|
|
1270
|
-
result = stop_ai_session(change_dir, session, require_identity=True)
|
|
1271
|
-
emit_json(result, as_json=as_json)
|
|
1272
|
-
return 0
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
def build_parser() -> argparse.ArgumentParser:
|
|
1276
|
-
parser = argparse.ArgumentParser(
|
|
1277
|
-
prog="harness_service.py",
|
|
1278
|
-
description="Manage AI service-session lifecycle (ensure/status/stop)",
|
|
1279
|
-
)
|
|
1280
|
-
parser.add_argument(
|
|
1281
|
-
"--json",
|
|
1282
|
-
action="store_true",
|
|
1283
|
-
help="emit machine-readable JSON on stdout",
|
|
1284
|
-
)
|
|
1285
|
-
sub = parser.add_subparsers(dest="command_name", required=True)
|
|
1286
|
-
|
|
1287
|
-
p_ensure = sub.add_parser("ensure", help="reuse / restart / start service")
|
|
1288
|
-
p_ensure.add_argument("--change-dir", required=True)
|
|
1289
|
-
p_ensure.add_argument("--project", required=True)
|
|
1290
|
-
p_ensure.add_argument(
|
|
1291
|
-
"--files",
|
|
1292
|
-
default=None,
|
|
1293
|
-
help="comma-separated service module source files for inputsHash",
|
|
1294
|
-
)
|
|
1295
|
-
p_ensure.add_argument("--json", action="store_true")
|
|
1296
|
-
p_ensure.set_defaults(func=cmd_ensure)
|
|
1297
|
-
|
|
1298
|
-
p_status = sub.add_parser("status", help="show session + liveness + fingerprint")
|
|
1299
|
-
p_status.add_argument("--change-dir", required=True)
|
|
1300
|
-
p_status.add_argument(
|
|
1301
|
-
"--files",
|
|
1302
|
-
default=None,
|
|
1303
|
-
help="optional files for current fingerprint comparison",
|
|
1304
|
-
)
|
|
1305
|
-
p_status.add_argument("--json", action="store_true")
|
|
1306
|
-
p_status.set_defaults(func=cmd_status)
|
|
1307
|
-
|
|
1308
|
-
p_stop = sub.add_parser("stop", help="stop service and clear session")
|
|
1309
|
-
p_stop.add_argument("--change-dir", required=True)
|
|
1310
|
-
p_stop.add_argument(
|
|
1311
|
-
"--if-started-by-ai",
|
|
1312
|
-
action="store_true",
|
|
1313
|
-
help="only stop when session.startedBy == AI (archive cleanup)",
|
|
1314
|
-
)
|
|
1315
|
-
p_stop.add_argument("--json", action="store_true")
|
|
1316
|
-
p_stop.set_defaults(func=cmd_stop)
|
|
1317
|
-
|
|
1318
|
-
return parser
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
def main(argv: list[str] | None = None) -> int:
|
|
1322
|
-
parser = build_parser()
|
|
1323
|
-
args = parser.parse_args(argv)
|
|
1324
|
-
# Allow top-level --json as well as subcommand --json
|
|
1325
|
-
if getattr(args, "json", False) is False and "--json" in (argv or sys.argv[1:]):
|
|
1326
|
-
args.json = True
|
|
1327
|
-
return int(args.func(args))
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
if __name__ == "__main__":
|
|
1331
|
-
raise SystemExit(main())
|