@harness-engineering/cli 1.15.0 → 1.17.0
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/agents/commands/codex/AGENTS.md +39 -0
- package/dist/agents/commands/codex/harness/add-harness-component/SKILL.md +195 -0
- package/dist/agents/commands/codex/harness/add-harness-component/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/cleanup-dead-code/SKILL.md +248 -0
- package/dist/agents/commands/codex/harness/cleanup-dead-code/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/detect-doc-drift/SKILL.md +182 -0
- package/dist/agents/commands/codex/harness/detect-doc-drift/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/enforce-architecture/SKILL.md +299 -0
- package/dist/agents/commands/codex/harness/enforce-architecture/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/harness-architecture-advisor/SKILL.md +452 -0
- package/dist/agents/commands/codex/harness/harness-architecture-advisor/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/harness-autopilot/SKILL.md +919 -0
- package/dist/agents/commands/codex/harness/harness-autopilot/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/harness-brainstorming/SKILL.md +409 -0
- package/dist/agents/commands/codex/harness/harness-brainstorming/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/harness-code-review/SKILL.md +860 -0
- package/dist/agents/commands/codex/harness/harness-code-review/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/harness-codebase-cleanup/SKILL.md +227 -0
- package/dist/agents/commands/codex/harness/harness-codebase-cleanup/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/harness-debugging/SKILL.md +369 -0
- package/dist/agents/commands/codex/harness/harness-debugging/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/harness-dependency-health/SKILL.md +182 -0
- package/dist/agents/commands/codex/harness/harness-dependency-health/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/harness-docs-pipeline/SKILL.md +463 -0
- package/dist/agents/commands/codex/harness/harness-docs-pipeline/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/harness-execution/SKILL.md +513 -0
- package/dist/agents/commands/codex/harness/harness-execution/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/harness-hotspot-detector/SKILL.md +164 -0
- package/dist/agents/commands/codex/harness/harness-hotspot-detector/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/harness-impact-analysis/SKILL.md +187 -0
- package/dist/agents/commands/codex/harness/harness-impact-analysis/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/harness-integrity/SKILL.md +170 -0
- package/dist/agents/commands/codex/harness/harness-integrity/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/harness-onboarding/SKILL.md +291 -0
- package/dist/agents/commands/codex/harness/harness-onboarding/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/harness-perf/SKILL.md +263 -0
- package/dist/agents/commands/codex/harness/harness-perf/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/harness-planning/SKILL.md +582 -0
- package/dist/agents/commands/codex/harness/harness-planning/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/harness-refactoring/SKILL.md +172 -0
- package/dist/agents/commands/codex/harness/harness-refactoring/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/harness-release-readiness/SKILL.md +692 -0
- package/dist/agents/commands/codex/harness/harness-release-readiness/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/harness-roadmap/SKILL.md +598 -0
- package/dist/agents/commands/codex/harness/harness-roadmap/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/harness-security-scan/SKILL.md +157 -0
- package/dist/agents/commands/codex/harness/harness-security-scan/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/harness-skill-authoring/SKILL.md +295 -0
- package/dist/agents/commands/codex/harness/harness-skill-authoring/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/harness-soundness-review/SKILL.md +1270 -0
- package/dist/agents/commands/codex/harness/harness-soundness-review/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/harness-supply-chain-audit/SKILL.md +247 -0
- package/dist/agents/commands/codex/harness/harness-supply-chain-audit/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/harness-tdd/SKILL.md +180 -0
- package/dist/agents/commands/codex/harness/harness-tdd/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/harness-test-advisor/SKILL.md +163 -0
- package/dist/agents/commands/codex/harness/harness-test-advisor/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/harness-verification/SKILL.md +424 -0
- package/dist/agents/commands/codex/harness/harness-verification/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/harness-verify/SKILL.md +162 -0
- package/dist/agents/commands/codex/harness/harness-verify/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/initialize-harness-project/SKILL.md +235 -0
- package/dist/agents/commands/codex/harness/initialize-harness-project/agents/openai.yaml +3 -0
- package/dist/agents/commands/cursor/harness/add-harness-component.mdc +200 -0
- package/dist/agents/commands/cursor/harness/cleanup-dead-code.mdc +253 -0
- package/dist/agents/commands/cursor/harness/detect-doc-drift.mdc +187 -0
- package/dist/agents/commands/cursor/harness/enforce-architecture.mdc +304 -0
- package/dist/agents/commands/cursor/harness/harness-architecture-advisor.mdc +457 -0
- package/dist/agents/commands/cursor/harness/harness-autopilot.mdc +924 -0
- package/dist/agents/commands/cursor/harness/harness-brainstorming.mdc +414 -0
- package/dist/agents/commands/cursor/harness/harness-code-review.mdc +865 -0
- package/dist/agents/commands/cursor/harness/harness-codebase-cleanup.mdc +232 -0
- package/dist/agents/commands/cursor/harness/harness-debugging.mdc +374 -0
- package/dist/agents/commands/cursor/harness/harness-dependency-health.mdc +187 -0
- package/dist/agents/commands/cursor/harness/harness-docs-pipeline.mdc +468 -0
- package/dist/agents/commands/cursor/harness/harness-execution.mdc +518 -0
- package/dist/agents/commands/cursor/harness/harness-hotspot-detector.mdc +169 -0
- package/dist/agents/commands/cursor/harness/harness-impact-analysis.mdc +192 -0
- package/dist/agents/commands/cursor/harness/harness-integrity.mdc +175 -0
- package/dist/agents/commands/cursor/harness/harness-onboarding.mdc +296 -0
- package/dist/agents/commands/cursor/harness/harness-perf.mdc +268 -0
- package/dist/agents/commands/cursor/harness/harness-planning.mdc +587 -0
- package/dist/agents/commands/cursor/harness/harness-refactoring.mdc +177 -0
- package/dist/agents/commands/cursor/harness/harness-release-readiness.mdc +697 -0
- package/dist/agents/commands/cursor/harness/harness-roadmap.mdc +603 -0
- package/dist/agents/commands/cursor/harness/harness-security-scan.mdc +162 -0
- package/dist/agents/commands/cursor/harness/harness-skill-authoring.mdc +300 -0
- package/dist/agents/commands/cursor/harness/harness-soundness-review.mdc +1275 -0
- package/dist/agents/commands/cursor/harness/harness-supply-chain-audit.mdc +252 -0
- package/dist/agents/commands/cursor/harness/harness-tdd.mdc +185 -0
- package/dist/agents/commands/cursor/harness/harness-test-advisor.mdc +168 -0
- package/dist/agents/commands/cursor/harness/harness-verification.mdc +429 -0
- package/dist/agents/commands/cursor/harness/harness-verify.mdc +167 -0
- package/dist/agents/commands/cursor/harness/initialize-harness-project.mdc +240 -0
- package/dist/agents/skills/claude-code/enforce-architecture/SKILL.md +52 -0
- package/dist/agents/skills/claude-code/harness-api-design/SKILL.md +52 -0
- package/dist/agents/skills/claude-code/harness-architecture-advisor/SKILL.md +52 -0
- package/dist/agents/skills/claude-code/harness-auth/SKILL.md +52 -0
- package/dist/agents/skills/claude-code/harness-autopilot/SKILL.md +123 -14
- package/dist/agents/skills/claude-code/harness-autopilot/skill.yaml +6 -0
- package/dist/agents/skills/claude-code/harness-code-review/SKILL.md +97 -3
- package/dist/agents/skills/claude-code/harness-code-review/skill.yaml +6 -0
- package/dist/agents/skills/claude-code/harness-codebase-cleanup/SKILL.md +2 -4
- package/dist/agents/skills/claude-code/harness-database/SKILL.md +52 -0
- package/dist/agents/skills/claude-code/harness-deployment/SKILL.md +52 -0
- package/dist/agents/skills/claude-code/harness-planning/SKILL.md +99 -3
- package/dist/agents/skills/claude-code/harness-planning/skill.yaml +6 -0
- package/dist/agents/skills/claude-code/harness-pre-commit-review/SKILL.md +1 -1
- package/dist/agents/skills/claude-code/harness-roadmap-pilot/SKILL.md +204 -0
- package/dist/agents/skills/claude-code/harness-roadmap-pilot/skill.yaml +52 -0
- package/dist/agents/skills/claude-code/harness-security-review/SKILL.md +27 -7
- package/dist/agents/skills/claude-code/harness-security-scan/SKILL.md +52 -0
- package/dist/agents/skills/claude-code/harness-supply-chain-audit/SKILL.md +281 -0
- package/dist/agents/skills/claude-code/harness-supply-chain-audit/skill.yaml +51 -0
- package/dist/agents/skills/codex/add-harness-component/SKILL.md +192 -0
- package/dist/agents/skills/codex/add-harness-component/skill.yaml +33 -0
- package/dist/agents/skills/codex/align-documentation/SKILL.md +213 -0
- package/dist/agents/skills/codex/align-documentation/skill.yaml +32 -0
- package/dist/agents/skills/codex/check-mechanical-constraints/SKILL.md +191 -0
- package/dist/agents/skills/codex/check-mechanical-constraints/skill.yaml +33 -0
- package/dist/agents/skills/codex/cleanup-dead-code/SKILL.md +245 -0
- package/dist/agents/skills/codex/cleanup-dead-code/skill.yaml +34 -0
- package/dist/agents/skills/codex/detect-doc-drift/SKILL.md +179 -0
- package/dist/agents/skills/codex/detect-doc-drift/skill.yaml +31 -0
- package/dist/agents/skills/codex/enforce-architecture/SKILL.md +296 -0
- package/dist/agents/skills/codex/enforce-architecture/skill.yaml +35 -0
- package/dist/agents/skills/codex/harness-accessibility/SKILL.md +281 -0
- package/dist/agents/skills/codex/harness-accessibility/skill.yaml +52 -0
- package/dist/agents/skills/codex/harness-api-design/SKILL.md +356 -0
- package/dist/agents/skills/codex/harness-api-design/skill.yaml +74 -0
- package/dist/agents/skills/codex/harness-architecture-advisor/SKILL.md +449 -0
- package/dist/agents/skills/codex/harness-architecture-advisor/skill.yaml +49 -0
- package/dist/agents/skills/codex/harness-auth/SKILL.md +331 -0
- package/dist/agents/skills/codex/harness-auth/skill.yaml +81 -0
- package/dist/agents/skills/codex/harness-autopilot/SKILL.md +916 -0
- package/dist/agents/skills/codex/harness-autopilot/skill.yaml +67 -0
- package/dist/agents/skills/codex/harness-brainstorming/SKILL.md +406 -0
- package/dist/agents/skills/codex/harness-brainstorming/skill.yaml +50 -0
- package/dist/agents/skills/codex/harness-caching/SKILL.md +309 -0
- package/dist/agents/skills/codex/harness-caching/skill.yaml +73 -0
- package/dist/agents/skills/codex/harness-chaos/SKILL.md +295 -0
- package/dist/agents/skills/codex/harness-chaos/skill.yaml +72 -0
- package/dist/agents/skills/codex/harness-code-review/SKILL.md +857 -0
- package/dist/agents/skills/codex/harness-code-review/skill.yaml +52 -0
- package/dist/agents/skills/codex/harness-codebase-cleanup/SKILL.md +224 -0
- package/dist/agents/skills/codex/harness-codebase-cleanup/skill.yaml +65 -0
- package/dist/agents/skills/codex/harness-compliance/SKILL.md +303 -0
- package/dist/agents/skills/codex/harness-compliance/skill.yaml +78 -0
- package/dist/agents/skills/codex/harness-containerization/SKILL.md +284 -0
- package/dist/agents/skills/codex/harness-containerization/skill.yaml +80 -0
- package/dist/agents/skills/codex/harness-data-pipeline/SKILL.md +274 -0
- package/dist/agents/skills/codex/harness-data-pipeline/skill.yaml +81 -0
- package/dist/agents/skills/codex/harness-data-validation/SKILL.md +343 -0
- package/dist/agents/skills/codex/harness-data-validation/skill.yaml +75 -0
- package/dist/agents/skills/codex/harness-database/SKILL.md +310 -0
- package/dist/agents/skills/codex/harness-database/skill.yaml +80 -0
- package/dist/agents/skills/codex/harness-debugging/SKILL.md +366 -0
- package/dist/agents/skills/codex/harness-debugging/skill.yaml +48 -0
- package/dist/agents/skills/codex/harness-dependency-health/SKILL.md +179 -0
- package/dist/agents/skills/codex/harness-dependency-health/skill.yaml +42 -0
- package/dist/agents/skills/codex/harness-deployment/SKILL.md +307 -0
- package/dist/agents/skills/codex/harness-deployment/skill.yaml +77 -0
- package/dist/agents/skills/codex/harness-design/SKILL.md +265 -0
- package/dist/agents/skills/codex/harness-design/skill.yaml +54 -0
- package/dist/agents/skills/codex/harness-design-mobile/SKILL.md +336 -0
- package/dist/agents/skills/codex/harness-design-mobile/skill.yaml +50 -0
- package/dist/agents/skills/codex/harness-design-system/SKILL.md +282 -0
- package/dist/agents/skills/codex/harness-design-system/skill.yaml +51 -0
- package/dist/agents/skills/codex/harness-design-web/SKILL.md +360 -0
- package/dist/agents/skills/codex/harness-design-web/skill.yaml +53 -0
- package/dist/agents/skills/codex/harness-diagnostics/SKILL.md +318 -0
- package/dist/agents/skills/codex/harness-diagnostics/skill.yaml +51 -0
- package/dist/agents/skills/codex/harness-docs-pipeline/SKILL.md +460 -0
- package/dist/agents/skills/codex/harness-docs-pipeline/skill.yaml +70 -0
- package/dist/agents/skills/codex/harness-dx/SKILL.md +276 -0
- package/dist/agents/skills/codex/harness-dx/skill.yaml +76 -0
- package/dist/agents/skills/codex/harness-e2e/SKILL.md +245 -0
- package/dist/agents/skills/codex/harness-e2e/skill.yaml +78 -0
- package/dist/agents/skills/codex/harness-event-driven/SKILL.md +280 -0
- package/dist/agents/skills/codex/harness-event-driven/skill.yaml +77 -0
- package/dist/agents/skills/codex/harness-execution/SKILL.md +510 -0
- package/dist/agents/skills/codex/harness-execution/skill.yaml +52 -0
- package/dist/agents/skills/codex/harness-feature-flags/SKILL.md +287 -0
- package/dist/agents/skills/codex/harness-feature-flags/skill.yaml +74 -0
- package/dist/agents/skills/codex/harness-git-workflow/SKILL.md +268 -0
- package/dist/agents/skills/codex/harness-git-workflow/skill.yaml +32 -0
- package/dist/agents/skills/codex/harness-hotspot-detector/SKILL.md +161 -0
- package/dist/agents/skills/codex/harness-hotspot-detector/skill.yaml +45 -0
- package/dist/agents/skills/codex/harness-i18n/SKILL.md +484 -0
- package/dist/agents/skills/codex/harness-i18n/skill.yaml +55 -0
- package/dist/agents/skills/codex/harness-i18n-process/SKILL.md +388 -0
- package/dist/agents/skills/codex/harness-i18n-process/skill.yaml +44 -0
- package/dist/agents/skills/codex/harness-i18n-workflow/SKILL.md +512 -0
- package/dist/agents/skills/codex/harness-i18n-workflow/skill.yaml +54 -0
- package/dist/agents/skills/codex/harness-impact-analysis/SKILL.md +184 -0
- package/dist/agents/skills/codex/harness-impact-analysis/skill.yaml +45 -0
- package/dist/agents/skills/codex/harness-incident-response/SKILL.md +223 -0
- package/dist/agents/skills/codex/harness-incident-response/skill.yaml +78 -0
- package/dist/agents/skills/codex/harness-infrastructure-as-code/SKILL.md +279 -0
- package/dist/agents/skills/codex/harness-infrastructure-as-code/skill.yaml +80 -0
- package/dist/agents/skills/codex/harness-integration-test/SKILL.md +271 -0
- package/dist/agents/skills/codex/harness-integration-test/skill.yaml +73 -0
- package/dist/agents/skills/codex/harness-integrity/SKILL.md +167 -0
- package/dist/agents/skills/codex/harness-integrity/skill.yaml +48 -0
- package/dist/agents/skills/codex/harness-knowledge-mapper/SKILL.md +195 -0
- package/dist/agents/skills/codex/harness-knowledge-mapper/skill.yaml +50 -0
- package/dist/agents/skills/codex/harness-load-testing/SKILL.md +274 -0
- package/dist/agents/skills/codex/harness-load-testing/skill.yaml +79 -0
- package/dist/agents/skills/codex/harness-ml-ops/SKILL.md +341 -0
- package/dist/agents/skills/codex/harness-ml-ops/skill.yaml +79 -0
- package/dist/agents/skills/codex/harness-mobile-patterns/SKILL.md +326 -0
- package/dist/agents/skills/codex/harness-mobile-patterns/skill.yaml +82 -0
- package/dist/agents/skills/codex/harness-mutation-test/SKILL.md +251 -0
- package/dist/agents/skills/codex/harness-mutation-test/skill.yaml +70 -0
- package/dist/agents/skills/codex/harness-observability/SKILL.md +283 -0
- package/dist/agents/skills/codex/harness-observability/skill.yaml +78 -0
- package/dist/agents/skills/codex/harness-onboarding/SKILL.md +288 -0
- package/dist/agents/skills/codex/harness-onboarding/skill.yaml +31 -0
- package/dist/agents/skills/codex/harness-parallel-agents/SKILL.md +256 -0
- package/dist/agents/skills/codex/harness-parallel-agents/skill.yaml +34 -0
- package/dist/agents/skills/codex/harness-perf/SKILL.md +260 -0
- package/dist/agents/skills/codex/harness-perf/skill.yaml +51 -0
- package/dist/agents/skills/codex/harness-perf-tdd/SKILL.md +249 -0
- package/dist/agents/skills/codex/harness-perf-tdd/skill.yaml +48 -0
- package/dist/agents/skills/codex/harness-planning/SKILL.md +579 -0
- package/dist/agents/skills/codex/harness-planning/skill.yaml +56 -0
- package/dist/agents/skills/codex/harness-pre-commit-review/SKILL.md +324 -0
- package/dist/agents/skills/codex/harness-pre-commit-review/skill.yaml +34 -0
- package/dist/agents/skills/codex/harness-product-spec/SKILL.md +285 -0
- package/dist/agents/skills/codex/harness-product-spec/skill.yaml +72 -0
- package/dist/agents/skills/codex/harness-property-test/SKILL.md +281 -0
- package/dist/agents/skills/codex/harness-property-test/skill.yaml +71 -0
- package/dist/agents/skills/codex/harness-refactoring/SKILL.md +169 -0
- package/dist/agents/skills/codex/harness-refactoring/skill.yaml +34 -0
- package/dist/agents/skills/codex/harness-release-readiness/SKILL.md +689 -0
- package/dist/agents/skills/codex/harness-release-readiness/skill.yaml +58 -0
- package/dist/agents/skills/codex/harness-resilience/SKILL.md +255 -0
- package/dist/agents/skills/codex/harness-resilience/skill.yaml +76 -0
- package/dist/agents/skills/codex/harness-roadmap/SKILL.md +595 -0
- package/dist/agents/skills/codex/harness-roadmap/skill.yaml +44 -0
- package/dist/agents/skills/codex/harness-roadmap-pilot/SKILL.md +204 -0
- package/dist/agents/skills/codex/harness-roadmap-pilot/skill.yaml +52 -0
- package/dist/agents/skills/codex/harness-secrets/SKILL.md +293 -0
- package/dist/agents/skills/codex/harness-secrets/skill.yaml +76 -0
- package/dist/agents/skills/codex/harness-security-review/SKILL.md +260 -0
- package/dist/agents/skills/codex/harness-security-review/skill.yaml +53 -0
- package/dist/agents/skills/codex/harness-security-scan/SKILL.md +154 -0
- package/dist/agents/skills/codex/harness-security-scan/skill.yaml +42 -0
- package/dist/agents/skills/codex/harness-skill-authoring/SKILL.md +292 -0
- package/dist/agents/skills/codex/harness-skill-authoring/skill.yaml +33 -0
- package/dist/agents/skills/codex/harness-soundness-review/SKILL.md +1267 -0
- package/dist/agents/skills/codex/harness-soundness-review/skill.yaml +49 -0
- package/dist/agents/skills/codex/harness-sql-review/SKILL.md +315 -0
- package/dist/agents/skills/codex/harness-sql-review/skill.yaml +74 -0
- package/dist/agents/skills/codex/harness-state-management/SKILL.md +309 -0
- package/dist/agents/skills/codex/harness-state-management/skill.yaml +33 -0
- package/dist/agents/skills/codex/harness-supply-chain-audit/SKILL.md +281 -0
- package/dist/agents/skills/codex/harness-supply-chain-audit/skill.yaml +51 -0
- package/dist/agents/skills/codex/harness-tdd/SKILL.md +177 -0
- package/dist/agents/skills/codex/harness-tdd/skill.yaml +49 -0
- package/dist/agents/skills/codex/harness-test-advisor/SKILL.md +160 -0
- package/dist/agents/skills/codex/harness-test-advisor/skill.yaml +45 -0
- package/dist/agents/skills/codex/harness-test-data/SKILL.md +268 -0
- package/dist/agents/skills/codex/harness-test-data/skill.yaml +74 -0
- package/dist/agents/skills/codex/harness-ux-copy/SKILL.md +271 -0
- package/dist/agents/skills/codex/harness-ux-copy/skill.yaml +77 -0
- package/dist/agents/skills/codex/harness-verification/SKILL.md +421 -0
- package/dist/agents/skills/codex/harness-verification/skill.yaml +43 -0
- package/dist/agents/skills/codex/harness-verify/SKILL.md +159 -0
- package/dist/agents/skills/codex/harness-verify/skill.yaml +41 -0
- package/dist/agents/skills/codex/harness-visual-regression/SKILL.md +257 -0
- package/dist/agents/skills/codex/harness-visual-regression/skill.yaml +74 -0
- package/dist/agents/skills/codex/initialize-harness-project/SKILL.md +232 -0
- package/dist/agents/skills/codex/initialize-harness-project/skill.yaml +32 -0
- package/dist/agents/skills/codex/validate-context-engineering/SKILL.md +150 -0
- package/dist/agents/skills/codex/validate-context-engineering/skill.yaml +32 -0
- package/dist/agents/skills/cursor/add-harness-component/SKILL.md +192 -0
- package/dist/agents/skills/cursor/add-harness-component/skill.yaml +33 -0
- package/dist/agents/skills/cursor/align-documentation/SKILL.md +213 -0
- package/dist/agents/skills/cursor/align-documentation/skill.yaml +32 -0
- package/dist/agents/skills/cursor/check-mechanical-constraints/SKILL.md +191 -0
- package/dist/agents/skills/cursor/check-mechanical-constraints/skill.yaml +33 -0
- package/dist/agents/skills/cursor/cleanup-dead-code/SKILL.md +245 -0
- package/dist/agents/skills/cursor/cleanup-dead-code/skill.yaml +34 -0
- package/dist/agents/skills/cursor/detect-doc-drift/SKILL.md +179 -0
- package/dist/agents/skills/cursor/detect-doc-drift/skill.yaml +31 -0
- package/dist/agents/skills/cursor/enforce-architecture/SKILL.md +296 -0
- package/dist/agents/skills/cursor/enforce-architecture/skill.yaml +35 -0
- package/dist/agents/skills/cursor/harness-accessibility/SKILL.md +281 -0
- package/dist/agents/skills/cursor/harness-accessibility/skill.yaml +52 -0
- package/dist/agents/skills/cursor/harness-api-design/SKILL.md +356 -0
- package/dist/agents/skills/cursor/harness-api-design/skill.yaml +74 -0
- package/dist/agents/skills/cursor/harness-architecture-advisor/SKILL.md +449 -0
- package/dist/agents/skills/cursor/harness-architecture-advisor/skill.yaml +49 -0
- package/dist/agents/skills/cursor/harness-auth/SKILL.md +331 -0
- package/dist/agents/skills/cursor/harness-auth/skill.yaml +81 -0
- package/dist/agents/skills/cursor/harness-autopilot/SKILL.md +916 -0
- package/dist/agents/skills/cursor/harness-autopilot/skill.yaml +67 -0
- package/dist/agents/skills/cursor/harness-brainstorming/SKILL.md +406 -0
- package/dist/agents/skills/cursor/harness-brainstorming/skill.yaml +50 -0
- package/dist/agents/skills/cursor/harness-caching/SKILL.md +309 -0
- package/dist/agents/skills/cursor/harness-caching/skill.yaml +73 -0
- package/dist/agents/skills/cursor/harness-chaos/SKILL.md +295 -0
- package/dist/agents/skills/cursor/harness-chaos/skill.yaml +72 -0
- package/dist/agents/skills/cursor/harness-code-review/SKILL.md +857 -0
- package/dist/agents/skills/cursor/harness-code-review/skill.yaml +52 -0
- package/dist/agents/skills/cursor/harness-codebase-cleanup/SKILL.md +224 -0
- package/dist/agents/skills/cursor/harness-codebase-cleanup/skill.yaml +65 -0
- package/dist/agents/skills/cursor/harness-compliance/SKILL.md +303 -0
- package/dist/agents/skills/cursor/harness-compliance/skill.yaml +78 -0
- package/dist/agents/skills/cursor/harness-containerization/SKILL.md +284 -0
- package/dist/agents/skills/cursor/harness-containerization/skill.yaml +80 -0
- package/dist/agents/skills/cursor/harness-data-pipeline/SKILL.md +274 -0
- package/dist/agents/skills/cursor/harness-data-pipeline/skill.yaml +81 -0
- package/dist/agents/skills/cursor/harness-data-validation/SKILL.md +343 -0
- package/dist/agents/skills/cursor/harness-data-validation/skill.yaml +75 -0
- package/dist/agents/skills/cursor/harness-database/SKILL.md +310 -0
- package/dist/agents/skills/cursor/harness-database/skill.yaml +80 -0
- package/dist/agents/skills/cursor/harness-debugging/SKILL.md +366 -0
- package/dist/agents/skills/cursor/harness-debugging/skill.yaml +48 -0
- package/dist/agents/skills/cursor/harness-dependency-health/SKILL.md +179 -0
- package/dist/agents/skills/cursor/harness-dependency-health/skill.yaml +42 -0
- package/dist/agents/skills/cursor/harness-deployment/SKILL.md +307 -0
- package/dist/agents/skills/cursor/harness-deployment/skill.yaml +77 -0
- package/dist/agents/skills/cursor/harness-design/SKILL.md +265 -0
- package/dist/agents/skills/cursor/harness-design/skill.yaml +54 -0
- package/dist/agents/skills/cursor/harness-design-mobile/SKILL.md +336 -0
- package/dist/agents/skills/cursor/harness-design-mobile/skill.yaml +50 -0
- package/dist/agents/skills/cursor/harness-design-system/SKILL.md +282 -0
- package/dist/agents/skills/cursor/harness-design-system/skill.yaml +51 -0
- package/dist/agents/skills/cursor/harness-design-web/SKILL.md +360 -0
- package/dist/agents/skills/cursor/harness-design-web/skill.yaml +53 -0
- package/dist/agents/skills/cursor/harness-diagnostics/SKILL.md +318 -0
- package/dist/agents/skills/cursor/harness-diagnostics/skill.yaml +51 -0
- package/dist/agents/skills/cursor/harness-docs-pipeline/SKILL.md +460 -0
- package/dist/agents/skills/cursor/harness-docs-pipeline/skill.yaml +70 -0
- package/dist/agents/skills/cursor/harness-dx/SKILL.md +276 -0
- package/dist/agents/skills/cursor/harness-dx/skill.yaml +76 -0
- package/dist/agents/skills/cursor/harness-e2e/SKILL.md +245 -0
- package/dist/agents/skills/cursor/harness-e2e/skill.yaml +78 -0
- package/dist/agents/skills/cursor/harness-event-driven/SKILL.md +280 -0
- package/dist/agents/skills/cursor/harness-event-driven/skill.yaml +77 -0
- package/dist/agents/skills/cursor/harness-execution/SKILL.md +510 -0
- package/dist/agents/skills/cursor/harness-execution/skill.yaml +52 -0
- package/dist/agents/skills/cursor/harness-feature-flags/SKILL.md +287 -0
- package/dist/agents/skills/cursor/harness-feature-flags/skill.yaml +74 -0
- package/dist/agents/skills/cursor/harness-git-workflow/SKILL.md +268 -0
- package/dist/agents/skills/cursor/harness-git-workflow/skill.yaml +32 -0
- package/dist/agents/skills/cursor/harness-hotspot-detector/SKILL.md +161 -0
- package/dist/agents/skills/cursor/harness-hotspot-detector/skill.yaml +45 -0
- package/dist/agents/skills/cursor/harness-i18n/SKILL.md +484 -0
- package/dist/agents/skills/cursor/harness-i18n/skill.yaml +55 -0
- package/dist/agents/skills/cursor/harness-i18n-process/SKILL.md +388 -0
- package/dist/agents/skills/cursor/harness-i18n-process/skill.yaml +44 -0
- package/dist/agents/skills/cursor/harness-i18n-workflow/SKILL.md +512 -0
- package/dist/agents/skills/cursor/harness-i18n-workflow/skill.yaml +54 -0
- package/dist/agents/skills/cursor/harness-impact-analysis/SKILL.md +184 -0
- package/dist/agents/skills/cursor/harness-impact-analysis/skill.yaml +45 -0
- package/dist/agents/skills/cursor/harness-incident-response/SKILL.md +223 -0
- package/dist/agents/skills/cursor/harness-incident-response/skill.yaml +78 -0
- package/dist/agents/skills/cursor/harness-infrastructure-as-code/SKILL.md +279 -0
- package/dist/agents/skills/cursor/harness-infrastructure-as-code/skill.yaml +80 -0
- package/dist/agents/skills/cursor/harness-integration-test/SKILL.md +271 -0
- package/dist/agents/skills/cursor/harness-integration-test/skill.yaml +73 -0
- package/dist/agents/skills/cursor/harness-integrity/SKILL.md +167 -0
- package/dist/agents/skills/cursor/harness-integrity/skill.yaml +48 -0
- package/dist/agents/skills/cursor/harness-knowledge-mapper/SKILL.md +195 -0
- package/dist/agents/skills/cursor/harness-knowledge-mapper/skill.yaml +50 -0
- package/dist/agents/skills/cursor/harness-load-testing/SKILL.md +274 -0
- package/dist/agents/skills/cursor/harness-load-testing/skill.yaml +79 -0
- package/dist/agents/skills/cursor/harness-ml-ops/SKILL.md +341 -0
- package/dist/agents/skills/cursor/harness-ml-ops/skill.yaml +79 -0
- package/dist/agents/skills/cursor/harness-mobile-patterns/SKILL.md +326 -0
- package/dist/agents/skills/cursor/harness-mobile-patterns/skill.yaml +82 -0
- package/dist/agents/skills/cursor/harness-mutation-test/SKILL.md +251 -0
- package/dist/agents/skills/cursor/harness-mutation-test/skill.yaml +70 -0
- package/dist/agents/skills/cursor/harness-observability/SKILL.md +283 -0
- package/dist/agents/skills/cursor/harness-observability/skill.yaml +78 -0
- package/dist/agents/skills/cursor/harness-onboarding/SKILL.md +288 -0
- package/dist/agents/skills/cursor/harness-onboarding/skill.yaml +31 -0
- package/dist/agents/skills/cursor/harness-parallel-agents/SKILL.md +256 -0
- package/dist/agents/skills/cursor/harness-parallel-agents/skill.yaml +34 -0
- package/dist/agents/skills/cursor/harness-perf/SKILL.md +260 -0
- package/dist/agents/skills/cursor/harness-perf/skill.yaml +51 -0
- package/dist/agents/skills/cursor/harness-perf-tdd/SKILL.md +249 -0
- package/dist/agents/skills/cursor/harness-perf-tdd/skill.yaml +48 -0
- package/dist/agents/skills/cursor/harness-planning/SKILL.md +579 -0
- package/dist/agents/skills/cursor/harness-planning/skill.yaml +56 -0
- package/dist/agents/skills/cursor/harness-pre-commit-review/SKILL.md +324 -0
- package/dist/agents/skills/cursor/harness-pre-commit-review/skill.yaml +34 -0
- package/dist/agents/skills/cursor/harness-product-spec/SKILL.md +285 -0
- package/dist/agents/skills/cursor/harness-product-spec/skill.yaml +72 -0
- package/dist/agents/skills/cursor/harness-property-test/SKILL.md +281 -0
- package/dist/agents/skills/cursor/harness-property-test/skill.yaml +71 -0
- package/dist/agents/skills/cursor/harness-refactoring/SKILL.md +169 -0
- package/dist/agents/skills/cursor/harness-refactoring/skill.yaml +34 -0
- package/dist/agents/skills/cursor/harness-release-readiness/SKILL.md +689 -0
- package/dist/agents/skills/cursor/harness-release-readiness/skill.yaml +58 -0
- package/dist/agents/skills/cursor/harness-resilience/SKILL.md +255 -0
- package/dist/agents/skills/cursor/harness-resilience/skill.yaml +76 -0
- package/dist/agents/skills/cursor/harness-roadmap/SKILL.md +595 -0
- package/dist/agents/skills/cursor/harness-roadmap/skill.yaml +44 -0
- package/dist/agents/skills/cursor/harness-roadmap-pilot/SKILL.md +204 -0
- package/dist/agents/skills/cursor/harness-roadmap-pilot/skill.yaml +52 -0
- package/dist/agents/skills/cursor/harness-secrets/SKILL.md +293 -0
- package/dist/agents/skills/cursor/harness-secrets/skill.yaml +76 -0
- package/dist/agents/skills/cursor/harness-security-review/SKILL.md +260 -0
- package/dist/agents/skills/cursor/harness-security-review/skill.yaml +53 -0
- package/dist/agents/skills/cursor/harness-security-scan/SKILL.md +154 -0
- package/dist/agents/skills/cursor/harness-security-scan/skill.yaml +42 -0
- package/dist/agents/skills/cursor/harness-skill-authoring/SKILL.md +292 -0
- package/dist/agents/skills/cursor/harness-skill-authoring/skill.yaml +33 -0
- package/dist/agents/skills/cursor/harness-soundness-review/SKILL.md +1267 -0
- package/dist/agents/skills/cursor/harness-soundness-review/skill.yaml +49 -0
- package/dist/agents/skills/cursor/harness-sql-review/SKILL.md +315 -0
- package/dist/agents/skills/cursor/harness-sql-review/skill.yaml +74 -0
- package/dist/agents/skills/cursor/harness-state-management/SKILL.md +309 -0
- package/dist/agents/skills/cursor/harness-state-management/skill.yaml +33 -0
- package/dist/agents/skills/cursor/harness-supply-chain-audit/SKILL.md +281 -0
- package/dist/agents/skills/cursor/harness-supply-chain-audit/skill.yaml +51 -0
- package/dist/agents/skills/cursor/harness-tdd/SKILL.md +177 -0
- package/dist/agents/skills/cursor/harness-tdd/skill.yaml +49 -0
- package/dist/agents/skills/cursor/harness-test-advisor/SKILL.md +160 -0
- package/dist/agents/skills/cursor/harness-test-advisor/skill.yaml +45 -0
- package/dist/agents/skills/cursor/harness-test-data/SKILL.md +268 -0
- package/dist/agents/skills/cursor/harness-test-data/skill.yaml +74 -0
- package/dist/agents/skills/cursor/harness-ux-copy/SKILL.md +271 -0
- package/dist/agents/skills/cursor/harness-ux-copy/skill.yaml +77 -0
- package/dist/agents/skills/cursor/harness-verification/SKILL.md +421 -0
- package/dist/agents/skills/cursor/harness-verification/skill.yaml +43 -0
- package/dist/agents/skills/cursor/harness-verify/SKILL.md +159 -0
- package/dist/agents/skills/cursor/harness-verify/skill.yaml +41 -0
- package/dist/agents/skills/cursor/harness-visual-regression/SKILL.md +257 -0
- package/dist/agents/skills/cursor/harness-visual-regression/skill.yaml +74 -0
- package/dist/agents/skills/cursor/initialize-harness-project/SKILL.md +232 -0
- package/dist/agents/skills/cursor/initialize-harness-project/skill.yaml +32 -0
- package/dist/agents/skills/cursor/validate-context-engineering/SKILL.md +150 -0
- package/dist/agents/skills/cursor/validate-context-engineering/skill.yaml +32 -0
- package/dist/agents/skills/gemini-cli/enforce-architecture/SKILL.md +52 -0
- package/dist/agents/skills/gemini-cli/harness-api-design/SKILL.md +52 -0
- package/dist/agents/skills/gemini-cli/harness-architecture-advisor/SKILL.md +52 -0
- package/dist/agents/skills/gemini-cli/harness-auth/SKILL.md +52 -0
- package/dist/agents/skills/gemini-cli/harness-autopilot/SKILL.md +123 -14
- package/dist/agents/skills/gemini-cli/harness-autopilot/skill.yaml +6 -0
- package/dist/agents/skills/gemini-cli/harness-code-review/SKILL.md +97 -3
- package/dist/agents/skills/gemini-cli/harness-code-review/skill.yaml +6 -0
- package/dist/agents/skills/gemini-cli/harness-codebase-cleanup/SKILL.md +2 -4
- package/dist/agents/skills/gemini-cli/harness-database/SKILL.md +52 -0
- package/dist/agents/skills/gemini-cli/harness-deployment/SKILL.md +52 -0
- package/dist/agents/skills/gemini-cli/harness-planning/SKILL.md +99 -3
- package/dist/agents/skills/gemini-cli/harness-planning/skill.yaml +6 -0
- package/dist/agents/skills/gemini-cli/harness-pre-commit-review/SKILL.md +1 -1
- package/dist/agents/skills/gemini-cli/harness-roadmap-pilot/SKILL.md +204 -0
- package/dist/agents/skills/gemini-cli/harness-roadmap-pilot/skill.yaml +52 -0
- package/dist/agents/skills/gemini-cli/harness-security-review/SKILL.md +27 -7
- package/dist/agents/skills/gemini-cli/harness-security-scan/SKILL.md +52 -0
- package/dist/agents/skills/gemini-cli/harness-supply-chain-audit/SKILL.md +281 -0
- package/dist/agents/skills/gemini-cli/harness-supply-chain-audit/skill.yaml +51 -0
- package/dist/agents/skills/package.json +5 -5
- package/dist/agents/skills/templates/discipline-template.md +49 -0
- package/dist/agents/skills/tests/schema.ts +1 -1
- package/dist/{agents-md-ZGNIDWAF.js → agents-md-DUYNKHJZ.js} +1 -1
- package/dist/{architecture-ZLIH5533.js → architecture-UBO5KKUV.js} +2 -2
- package/dist/bin/harness-mcp.js +14 -14
- package/dist/bin/harness.js +20 -20
- package/dist/{check-phase-gate-ZOXVBDCN.js → check-phase-gate-OSHN2AEL.js} +3 -3
- package/dist/{chunk-NNHDDXYT.js → chunk-2DMIQ35P.js} +486 -132
- package/dist/{chunk-OFXQSFOW.js → chunk-5FM64G6D.js} +2 -2
- package/dist/{chunk-RCWZBSK5.js → chunk-6KWBH4EO.js} +1 -1
- package/dist/{chunk-LGYBN7Y6.js → chunk-ABQUCXRE.js} +2 -1
- package/dist/{chunk-VEPAJXBW.js → chunk-APNPXLB2.js} +4 -4
- package/dist/{chunk-ZOAWBDWU.js → chunk-CJDVBBPB.js} +5 -1
- package/dist/{chunk-FTMXDOR6.js → chunk-CZZXE6BL.js} +1 -1
- package/dist/{chunk-N25INEIX.js → chunk-GWXP3JVA.js} +3 -3
- package/dist/{chunk-XYLGHKG6.js → chunk-HKUX2X7O.js} +11 -2
- package/dist/{chunk-YBJ262QL.js → chunk-LRG3B43J.js} +1 -1
- package/dist/{chunk-AOZRDOIP.js → chunk-M6TIO6NF.js} +1 -1
- package/dist/{chunk-J4RAX7YB.js → chunk-OA3MOZGG.js} +1683 -507
- package/dist/{chunk-YLXFKVJE.js → chunk-OHZVGIPE.js} +9 -9
- package/dist/{chunk-2BKLWLY6.js → chunk-QSRRBNLY.js} +8 -8
- package/dist/{chunk-3ZZKVN62.js → chunk-TG7IUJ3J.js} +1 -1
- package/dist/{chunk-EDXIVMAP.js → chunk-TZIHFNEG.js} +20 -6
- package/dist/{chunk-ND2ENWDM.js → chunk-UX3JHYEA.js} +1 -1
- package/dist/{chunk-Z2OOPXJO.js → chunk-VF23UTNB.js} +1771 -164
- package/dist/{chunk-7MJAPE3Z.js → chunk-YLN34N65.js} +1 -0
- package/dist/{chunk-B2HKP423.js → chunk-ZA2I7S3E.js} +28 -1
- package/dist/{ci-workflow-765LSHRD.js → ci-workflow-FJZMNZPT.js} +1 -1
- package/dist/{create-skill-XSWHMSM5.js → create-skill-NDXQSTIK.js} +2 -2
- package/dist/{dist-ALQDD67R.js → dist-MF5BK5AD.js} +77 -1
- package/dist/{dist-B26DFXMP.js → dist-U7EAO6T2.js} +110 -60
- package/dist/{docs-NRMQCOJ6.js → docs-WZHW4N4P.js} +3 -3
- package/dist/{engine-3RB7MXPP.js → engine-VS6ZJ2VZ.js} +2 -2
- package/dist/{entropy-6AGX2ZUN.js → entropy-FCIGJIIT.js} +2 -2
- package/dist/{feedback-MY4QZIFD.js → feedback-O3FYTZIE.js} +1 -1
- package/dist/{generate-agent-definitions-ZAE726AU.js → generate-agent-definitions-EYG263XD.js} +3 -3
- package/dist/{graph-loader-2M2HXDQI.js → graph-loader-KMHDQYDT.js} +1 -1
- package/dist/index.d.ts +95 -15
- package/dist/index.js +20 -20
- package/dist/{loader-UUTVMQCC.js → loader-B4XWX4K6.js} +1 -1
- package/dist/{mcp-VU5FMO52.js → mcp-DVVUODN7.js} +14 -14
- package/dist/{performance-2D7G6NMJ.js → performance-NMJDV6HF.js} +4 -2
- package/dist/{review-pipeline-RAQ55ISU.js → review-pipeline-MSEJWTKM.js} +1 -1
- package/dist/{runtime-BCK5RRZQ.js → runtime-YHVLJNPG.js} +1 -1
- package/dist/{security-2RPQEN62.js → security-HTDKKGMX.js} +1 -1
- package/dist/{skill-executor-XZLYZYAK.js → skill-executor-XEVDGXUM.js} +2 -2
- package/dist/{validate-KBYQAEWE.js → validate-SPSTH2YW.js} +2 -2
- package/dist/{validate-cross-check-OABMREW4.js → validate-cross-check-YTDWIMFI.js} +1 -1
- package/package.json +20 -20
|
@@ -6,11 +6,11 @@ import {
|
|
|
6
6
|
OutputMode,
|
|
7
7
|
createCheckPhaseGateCommand,
|
|
8
8
|
findFiles
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-ABQUCXRE.js";
|
|
10
10
|
import {
|
|
11
11
|
createGenerateAgentDefinitionsCommand,
|
|
12
12
|
generateAgentDefinitions
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-6KWBH4EO.js";
|
|
14
14
|
import {
|
|
15
15
|
listPersonas,
|
|
16
16
|
loadPersona
|
|
@@ -20,13 +20,13 @@ import {
|
|
|
20
20
|
} from "./chunk-TRAPF4IX.js";
|
|
21
21
|
import {
|
|
22
22
|
executeSkill
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-M6TIO6NF.js";
|
|
24
24
|
import {
|
|
25
25
|
ALLOWED_PERSONA_COMMANDS
|
|
26
26
|
} from "./chunk-TEFCFC4H.js";
|
|
27
27
|
import {
|
|
28
28
|
createCreateSkillCommand
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-LRG3B43J.js";
|
|
30
30
|
import {
|
|
31
31
|
logger
|
|
32
32
|
} from "./chunk-EBJQ6N4M.js";
|
|
@@ -50,14 +50,14 @@ import {
|
|
|
50
50
|
handleGetImpact,
|
|
51
51
|
handleOrphanDeletion,
|
|
52
52
|
persistToolingConfig
|
|
53
|
-
} from "./chunk-
|
|
53
|
+
} from "./chunk-2DMIQ35P.js";
|
|
54
54
|
import {
|
|
55
55
|
VALID_PLATFORMS
|
|
56
|
-
} from "./chunk-
|
|
56
|
+
} from "./chunk-CJDVBBPB.js";
|
|
57
57
|
import {
|
|
58
58
|
findConfigFile,
|
|
59
59
|
resolveConfig
|
|
60
|
-
} from "./chunk-
|
|
60
|
+
} from "./chunk-ZA2I7S3E.js";
|
|
61
61
|
import {
|
|
62
62
|
resolveGlobalSkillsDir,
|
|
63
63
|
resolvePersonasDir,
|
|
@@ -72,13 +72,13 @@ import {
|
|
|
72
72
|
} from "./chunk-3WGJMBKH.js";
|
|
73
73
|
import {
|
|
74
74
|
SkillMetadataSchema
|
|
75
|
-
} from "./chunk-
|
|
75
|
+
} from "./chunk-HKUX2X7O.js";
|
|
76
76
|
import {
|
|
77
77
|
CLI_VERSION
|
|
78
78
|
} from "./chunk-BM3PWGXQ.js";
|
|
79
79
|
import {
|
|
80
80
|
TemplateEngine
|
|
81
|
-
} from "./chunk-
|
|
81
|
+
} from "./chunk-YLN34N65.js";
|
|
82
82
|
import {
|
|
83
83
|
ArchBaselineManager,
|
|
84
84
|
ArchConfigSchema,
|
|
@@ -96,6 +96,10 @@ import {
|
|
|
96
96
|
archiveStream,
|
|
97
97
|
buildSnapshot,
|
|
98
98
|
checkDocCoverage,
|
|
99
|
+
checkTaint,
|
|
100
|
+
clearTaint,
|
|
101
|
+
computeOverallSeverity,
|
|
102
|
+
computeScanExitCode,
|
|
99
103
|
createFixes,
|
|
100
104
|
createStream,
|
|
101
105
|
deepMergeConstraints,
|
|
@@ -107,8 +111,11 @@ import {
|
|
|
107
111
|
extractBundle,
|
|
108
112
|
generateSuggestions,
|
|
109
113
|
listStreams,
|
|
114
|
+
listTaintedSessions,
|
|
110
115
|
loadState,
|
|
111
116
|
loadStreamIndex,
|
|
117
|
+
mapInjectionFindings,
|
|
118
|
+
mapSecurityFindings,
|
|
112
119
|
parseDiff,
|
|
113
120
|
parseManifest,
|
|
114
121
|
parseSecurityConfig,
|
|
@@ -121,20 +128,21 @@ import {
|
|
|
121
128
|
runAll,
|
|
122
129
|
runCIChecks,
|
|
123
130
|
runReviewPipeline,
|
|
131
|
+
scanForInjection,
|
|
124
132
|
setActiveStream,
|
|
125
133
|
validateAgentsMap,
|
|
126
134
|
validateDependencies,
|
|
127
135
|
validateKnowledgeMap,
|
|
128
136
|
writeConfig,
|
|
129
137
|
writeLockfile
|
|
130
|
-
} from "./chunk-
|
|
138
|
+
} from "./chunk-VF23UTNB.js";
|
|
131
139
|
import {
|
|
132
140
|
Err,
|
|
133
141
|
Ok
|
|
134
142
|
} from "./chunk-ERS5EVUZ.js";
|
|
135
143
|
|
|
136
144
|
// src/index.ts
|
|
137
|
-
import { Command as
|
|
145
|
+
import { Command as Command70 } from "commander";
|
|
138
146
|
|
|
139
147
|
// src/commands/validate.ts
|
|
140
148
|
import { Command } from "commander";
|
|
@@ -213,7 +221,7 @@ function createValidateCommand() {
|
|
|
213
221
|
process.exit(result.error.exitCode);
|
|
214
222
|
}
|
|
215
223
|
if (opts.crossCheck) {
|
|
216
|
-
const { runCrossCheck: runCrossCheck2 } = await import("./validate-cross-check-
|
|
224
|
+
const { runCrossCheck: runCrossCheck2 } = await import("./validate-cross-check-YTDWIMFI.js");
|
|
217
225
|
const cwd = process.cwd();
|
|
218
226
|
const specsDir = path.join(cwd, "docs", "specs");
|
|
219
227
|
const plansDir = path.join(cwd, "docs", "plans");
|
|
@@ -481,10 +489,10 @@ async function runCheckSecurity(cwd, options) {
|
|
|
481
489
|
const projectRoot = path4.resolve(cwd);
|
|
482
490
|
let configData = {};
|
|
483
491
|
try {
|
|
484
|
-
const
|
|
492
|
+
const fs34 = await import("fs");
|
|
485
493
|
const configPath = path4.join(projectRoot, "harness.config.json");
|
|
486
|
-
if (
|
|
487
|
-
const raw =
|
|
494
|
+
if (fs34.existsSync(configPath)) {
|
|
495
|
+
const raw = fs34.readFileSync(configPath, "utf-8");
|
|
488
496
|
const parsed = JSON.parse(raw);
|
|
489
497
|
configData = parsed.security ?? {};
|
|
490
498
|
}
|
|
@@ -570,7 +578,7 @@ function registerBenchCommand(perf) {
|
|
|
570
578
|
perf.command("bench [glob]").description("Run benchmarks via vitest bench").action(async (glob, _opts, cmd) => {
|
|
571
579
|
const globalOpts = cmd.optsWithGlobals();
|
|
572
580
|
const cwd = process.cwd();
|
|
573
|
-
const { BenchmarkRunner } = await import("./dist-
|
|
581
|
+
const { BenchmarkRunner } = await import("./dist-MF5BK5AD.js");
|
|
574
582
|
const runner = new BenchmarkRunner();
|
|
575
583
|
const benchFiles = runner.discover(cwd, glob);
|
|
576
584
|
if (benchFiles.length === 0) {
|
|
@@ -638,7 +646,7 @@ function registerBaselinesCommands(perf) {
|
|
|
638
646
|
baselines.command("update").description("Update baselines from latest benchmark run").action(async (_opts, cmd) => {
|
|
639
647
|
const globalOpts = cmd.optsWithGlobals();
|
|
640
648
|
const cwd = process.cwd();
|
|
641
|
-
const { BenchmarkRunner } = await import("./dist-
|
|
649
|
+
const { BenchmarkRunner } = await import("./dist-MF5BK5AD.js");
|
|
642
650
|
const runner = new BenchmarkRunner();
|
|
643
651
|
const manager = new BaselineManager(cwd);
|
|
644
652
|
logger.info("Running benchmarks to update baselines...");
|
|
@@ -671,7 +679,7 @@ function registerReportCommand(perf) {
|
|
|
671
679
|
perf.command("report").description("Full performance report with metrics, trends, and hotspots").action(async (_opts, cmd) => {
|
|
672
680
|
const globalOpts = cmd.optsWithGlobals();
|
|
673
681
|
const cwd = process.cwd();
|
|
674
|
-
const { EntropyAnalyzer: EntropyAnalyzer2 } = await import("./dist-
|
|
682
|
+
const { EntropyAnalyzer: EntropyAnalyzer2 } = await import("./dist-MF5BK5AD.js");
|
|
675
683
|
const analyzer = new EntropyAnalyzer2({
|
|
676
684
|
rootDir: path5.resolve(cwd),
|
|
677
685
|
analyze: { complexity: true, coupling: true }
|
|
@@ -852,33 +860,248 @@ function createCheckDocsCommand() {
|
|
|
852
860
|
// src/commands/init.ts
|
|
853
861
|
import { Command as Command8 } from "commander";
|
|
854
862
|
import chalk2 from "chalk";
|
|
855
|
-
import * as
|
|
856
|
-
import * as
|
|
863
|
+
import * as fs4 from "fs";
|
|
864
|
+
import * as path10 from "path";
|
|
857
865
|
|
|
858
866
|
// src/commands/setup-mcp.ts
|
|
859
867
|
import { Command as Command7 } from "commander";
|
|
860
|
-
import * as
|
|
861
|
-
import * as
|
|
868
|
+
import * as fs3 from "fs";
|
|
869
|
+
import * as path9 from "path";
|
|
862
870
|
import * as os from "os";
|
|
863
871
|
import chalk from "chalk";
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
872
|
+
import * as clack from "@clack/prompts";
|
|
873
|
+
|
|
874
|
+
// src/integrations/config.ts
|
|
875
|
+
import * as fs from "fs";
|
|
876
|
+
import * as path7 from "path";
|
|
877
|
+
function readJsonSafe(filePath) {
|
|
868
878
|
if (!fs.existsSync(filePath)) return null;
|
|
869
879
|
try {
|
|
870
880
|
return JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
871
881
|
} catch {
|
|
872
|
-
fs.copyFileSync(filePath, filePath + ".bak");
|
|
873
882
|
return null;
|
|
874
883
|
}
|
|
875
884
|
}
|
|
876
|
-
function
|
|
885
|
+
function writeJson(filePath, data) {
|
|
877
886
|
const dir = path7.dirname(filePath);
|
|
878
887
|
if (!fs.existsSync(dir)) {
|
|
879
888
|
fs.mkdirSync(dir, { recursive: true });
|
|
880
889
|
}
|
|
881
|
-
|
|
890
|
+
const tmp = filePath + ".tmp";
|
|
891
|
+
fs.writeFileSync(tmp, JSON.stringify(data, null, 2) + "\n");
|
|
892
|
+
fs.renameSync(tmp, filePath);
|
|
893
|
+
}
|
|
894
|
+
function readMcpConfig(filePath) {
|
|
895
|
+
const config = readJsonSafe(filePath);
|
|
896
|
+
if (!config) return { mcpServers: {} };
|
|
897
|
+
if (!config.mcpServers) config.mcpServers = {};
|
|
898
|
+
return config;
|
|
899
|
+
}
|
|
900
|
+
function writeMcpEntry(filePath, name, entry) {
|
|
901
|
+
const config = readMcpConfig(filePath);
|
|
902
|
+
config.mcpServers[name] = entry;
|
|
903
|
+
writeJson(filePath, config);
|
|
904
|
+
}
|
|
905
|
+
function removeMcpEntry(filePath, name) {
|
|
906
|
+
if (!fs.existsSync(filePath)) return;
|
|
907
|
+
const config = readMcpConfig(filePath);
|
|
908
|
+
delete config.mcpServers[name];
|
|
909
|
+
writeJson(filePath, config);
|
|
910
|
+
}
|
|
911
|
+
function readIntegrationsConfig(configPath) {
|
|
912
|
+
const raw = readJsonSafe(configPath);
|
|
913
|
+
if (!raw || !raw.integrations) return { enabled: [], dismissed: [] };
|
|
914
|
+
const integ = raw.integrations;
|
|
915
|
+
return {
|
|
916
|
+
enabled: Array.isArray(integ.enabled) ? integ.enabled : [],
|
|
917
|
+
dismissed: Array.isArray(integ.dismissed) ? integ.dismissed : []
|
|
918
|
+
};
|
|
919
|
+
}
|
|
920
|
+
function writeIntegrationsConfig(configPath, integrations) {
|
|
921
|
+
const raw = readJsonSafe(configPath) ?? {};
|
|
922
|
+
raw.integrations = integrations;
|
|
923
|
+
writeJson(configPath, raw);
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
// src/integrations/toml.ts
|
|
927
|
+
import * as fs2 from "fs";
|
|
928
|
+
import * as path8 from "path";
|
|
929
|
+
function writeTomlMcpEntry(filePath, name, entry) {
|
|
930
|
+
const dir = path8.dirname(filePath);
|
|
931
|
+
if (!fs2.existsSync(dir)) {
|
|
932
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
933
|
+
}
|
|
934
|
+
const existing = fs2.existsSync(filePath) ? fs2.readFileSync(filePath, "utf-8") : "";
|
|
935
|
+
const blockHeader = `[mcp_servers.${name}]`;
|
|
936
|
+
const newBlock = serializeTomlMcpBlock(name, entry);
|
|
937
|
+
let updated;
|
|
938
|
+
if (existing.includes(blockHeader)) {
|
|
939
|
+
updated = replaceTomlBlock(existing, blockHeader, newBlock);
|
|
940
|
+
} else {
|
|
941
|
+
const separator = existing.length > 0 && !existing.endsWith("\n\n") ? "\n" : "";
|
|
942
|
+
updated = existing + separator + newBlock;
|
|
943
|
+
}
|
|
944
|
+
const tmp = filePath + ".tmp";
|
|
945
|
+
fs2.writeFileSync(tmp, updated);
|
|
946
|
+
fs2.renameSync(tmp, filePath);
|
|
947
|
+
}
|
|
948
|
+
function serializeTomlMcpBlock(name, entry) {
|
|
949
|
+
const lines = [`[mcp_servers.${name}]`];
|
|
950
|
+
lines.push(`command = ${JSON.stringify(entry.command)}`);
|
|
951
|
+
if (entry.args !== void 0) {
|
|
952
|
+
const argsLiteral = "[" + entry.args.map((a) => JSON.stringify(a)).join(", ") + "]";
|
|
953
|
+
lines.push(`args = ${argsLiteral}`);
|
|
954
|
+
}
|
|
955
|
+
if (entry.enabled !== void 0) {
|
|
956
|
+
lines.push(`enabled = ${entry.enabled}`);
|
|
957
|
+
}
|
|
958
|
+
return lines.join("\n") + "\n";
|
|
959
|
+
}
|
|
960
|
+
function replaceTomlBlock(content, blockHeader, newBlock) {
|
|
961
|
+
const lines = content.split("\n");
|
|
962
|
+
const startIdx = lines.findIndex((l) => l.trim() === blockHeader);
|
|
963
|
+
if (startIdx === -1) return content + newBlock;
|
|
964
|
+
let endIdx = lines.length;
|
|
965
|
+
for (let i = startIdx + 1; i < lines.length; i++) {
|
|
966
|
+
if (lines[i]?.match(/^\[(?!\[)/)) {
|
|
967
|
+
endIdx = i;
|
|
968
|
+
break;
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
while (endIdx > startIdx + 1 && lines[endIdx - 1]?.trim() === "") {
|
|
972
|
+
endIdx--;
|
|
973
|
+
}
|
|
974
|
+
const newBlockLines = newBlock.trimEnd().split("\n");
|
|
975
|
+
const result = [
|
|
976
|
+
...lines.slice(0, startIdx),
|
|
977
|
+
...newBlockLines,
|
|
978
|
+
...endIdx < lines.length ? ["", ...lines.slice(endIdx)] : []
|
|
979
|
+
];
|
|
980
|
+
return result.join("\n") + (content.endsWith("\n") ? "\n" : "");
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
// src/commands/setup-mcp.ts
|
|
984
|
+
var HARNESS_MCP_ENTRY = {
|
|
985
|
+
command: "harness-mcp"
|
|
986
|
+
};
|
|
987
|
+
var CURSOR_CURATED_TOOLS = [
|
|
988
|
+
"run_skill",
|
|
989
|
+
"validate_project",
|
|
990
|
+
"emit_interaction",
|
|
991
|
+
"check_docs",
|
|
992
|
+
"manage_roadmap",
|
|
993
|
+
"run_code_review",
|
|
994
|
+
"check_phase_gate",
|
|
995
|
+
"gather_context",
|
|
996
|
+
"find_context_for",
|
|
997
|
+
"get_impact",
|
|
998
|
+
"detect_entropy",
|
|
999
|
+
"run_security_scan",
|
|
1000
|
+
"assess_project",
|
|
1001
|
+
"manage_state",
|
|
1002
|
+
"create_self_review",
|
|
1003
|
+
"analyze_diff",
|
|
1004
|
+
"request_peer_review",
|
|
1005
|
+
"review_changes",
|
|
1006
|
+
"check_dependencies",
|
|
1007
|
+
"search_skills",
|
|
1008
|
+
"code_search",
|
|
1009
|
+
"code_outline",
|
|
1010
|
+
"ask_graph",
|
|
1011
|
+
"query_graph",
|
|
1012
|
+
"detect_anomalies"
|
|
1013
|
+
];
|
|
1014
|
+
var ALL_MCP_TOOLS = [
|
|
1015
|
+
"validate_project",
|
|
1016
|
+
"check_dependencies",
|
|
1017
|
+
"check_docs",
|
|
1018
|
+
"detect_entropy",
|
|
1019
|
+
"generate_linter",
|
|
1020
|
+
"validate_linter_config",
|
|
1021
|
+
"init_project",
|
|
1022
|
+
"list_personas",
|
|
1023
|
+
"generate_persona_artifacts",
|
|
1024
|
+
"run_persona",
|
|
1025
|
+
"add_component",
|
|
1026
|
+
"run_agent_task",
|
|
1027
|
+
"run_skill",
|
|
1028
|
+
"manage_state",
|
|
1029
|
+
"create_self_review",
|
|
1030
|
+
"analyze_diff",
|
|
1031
|
+
"request_peer_review",
|
|
1032
|
+
"check_phase_gate",
|
|
1033
|
+
"validate_cross_check",
|
|
1034
|
+
"create_skill",
|
|
1035
|
+
"generate_slash_commands",
|
|
1036
|
+
"query_graph",
|
|
1037
|
+
"search_similar",
|
|
1038
|
+
"find_context_for",
|
|
1039
|
+
"get_relationships",
|
|
1040
|
+
"get_impact",
|
|
1041
|
+
"ingest_source",
|
|
1042
|
+
"generate_agent_definitions",
|
|
1043
|
+
"run_security_scan",
|
|
1044
|
+
"check_performance",
|
|
1045
|
+
"get_perf_baselines",
|
|
1046
|
+
"update_perf_baselines",
|
|
1047
|
+
"get_critical_paths",
|
|
1048
|
+
"list_streams",
|
|
1049
|
+
"manage_roadmap",
|
|
1050
|
+
"emit_interaction",
|
|
1051
|
+
"run_code_review",
|
|
1052
|
+
"gather_context",
|
|
1053
|
+
"assess_project",
|
|
1054
|
+
"review_changes",
|
|
1055
|
+
"detect_anomalies",
|
|
1056
|
+
"ask_graph",
|
|
1057
|
+
"check_task_independence",
|
|
1058
|
+
"predict_conflicts",
|
|
1059
|
+
"detect_stale_constraints",
|
|
1060
|
+
"search_skills",
|
|
1061
|
+
"code_outline",
|
|
1062
|
+
"code_search",
|
|
1063
|
+
"code_unfold"
|
|
1064
|
+
];
|
|
1065
|
+
async function runCursorToolPicker() {
|
|
1066
|
+
try {
|
|
1067
|
+
const selected = await clack.multiselect({
|
|
1068
|
+
message: "Select tools to register for Cursor (25 recommended; Cursor supports ~40 across all servers)",
|
|
1069
|
+
options: ALL_MCP_TOOLS.map((tool) => {
|
|
1070
|
+
const opt = { value: tool, label: tool };
|
|
1071
|
+
if (CURSOR_CURATED_TOOLS.includes(tool)) opt.hint = "recommended";
|
|
1072
|
+
return opt;
|
|
1073
|
+
}),
|
|
1074
|
+
initialValues: CURSOR_CURATED_TOOLS
|
|
1075
|
+
});
|
|
1076
|
+
if (clack.isCancel(selected)) {
|
|
1077
|
+
return CURSOR_CURATED_TOOLS;
|
|
1078
|
+
}
|
|
1079
|
+
return selected;
|
|
1080
|
+
} catch {
|
|
1081
|
+
return CURSOR_CURATED_TOOLS;
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
function writeCursorMcpEntryWithTools(configPath, tools) {
|
|
1085
|
+
writeMcpEntry(configPath, "harness", {
|
|
1086
|
+
command: "harness",
|
|
1087
|
+
args: ["mcp", "--tools", ...tools]
|
|
1088
|
+
});
|
|
1089
|
+
}
|
|
1090
|
+
function readJsonFile(filePath) {
|
|
1091
|
+
if (!fs3.existsSync(filePath)) return null;
|
|
1092
|
+
try {
|
|
1093
|
+
return JSON.parse(fs3.readFileSync(filePath, "utf-8"));
|
|
1094
|
+
} catch {
|
|
1095
|
+
fs3.copyFileSync(filePath, filePath + ".bak");
|
|
1096
|
+
return null;
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
function writeJsonFile(filePath, data) {
|
|
1100
|
+
const dir = path9.dirname(filePath);
|
|
1101
|
+
if (!fs3.existsSync(dir)) {
|
|
1102
|
+
fs3.mkdirSync(dir, { recursive: true });
|
|
1103
|
+
}
|
|
1104
|
+
fs3.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
|
|
882
1105
|
}
|
|
883
1106
|
function configureMcpServer(configPath) {
|
|
884
1107
|
const config = readJsonFile(configPath) ?? {};
|
|
@@ -893,7 +1116,7 @@ function configureMcpServer(configPath) {
|
|
|
893
1116
|
return true;
|
|
894
1117
|
}
|
|
895
1118
|
function addGeminiTrustedFolder(cwd) {
|
|
896
|
-
const trustedPath =
|
|
1119
|
+
const trustedPath = path9.join(os.homedir(), ".gemini", "trustedFolders.json");
|
|
897
1120
|
const folders = readJsonFile(trustedPath) ?? {};
|
|
898
1121
|
if (folders[cwd] === "TRUST_FOLDER") {
|
|
899
1122
|
return false;
|
|
@@ -907,7 +1130,7 @@ function setupMcp(cwd, client) {
|
|
|
907
1130
|
const skipped = [];
|
|
908
1131
|
let trustedFolder = false;
|
|
909
1132
|
if (client === "all" || client === "claude") {
|
|
910
|
-
const configPath =
|
|
1133
|
+
const configPath = path9.join(cwd, ".mcp.json");
|
|
911
1134
|
if (configureMcpServer(configPath)) {
|
|
912
1135
|
configured.push("Claude Code");
|
|
913
1136
|
} else {
|
|
@@ -915,7 +1138,7 @@ function setupMcp(cwd, client) {
|
|
|
915
1138
|
}
|
|
916
1139
|
}
|
|
917
1140
|
if (client === "all" || client === "gemini") {
|
|
918
|
-
const configPath =
|
|
1141
|
+
const configPath = path9.join(cwd, ".gemini", "settings.json");
|
|
919
1142
|
if (configureMcpServer(configPath)) {
|
|
920
1143
|
configured.push("Gemini CLI");
|
|
921
1144
|
} else {
|
|
@@ -923,44 +1146,96 @@ function setupMcp(cwd, client) {
|
|
|
923
1146
|
}
|
|
924
1147
|
trustedFolder = addGeminiTrustedFolder(cwd);
|
|
925
1148
|
}
|
|
1149
|
+
if (client === "all" || client === "codex") {
|
|
1150
|
+
const configPath = path9.join(cwd, ".codex", "config.toml");
|
|
1151
|
+
const alreadyConfigured = (() => {
|
|
1152
|
+
if (!fs3.existsSync(configPath)) return false;
|
|
1153
|
+
const content = fs3.readFileSync(configPath, "utf-8");
|
|
1154
|
+
return content.includes("[mcp_servers.harness]");
|
|
1155
|
+
})();
|
|
1156
|
+
if (alreadyConfigured) {
|
|
1157
|
+
skipped.push("Codex CLI");
|
|
1158
|
+
} else {
|
|
1159
|
+
writeTomlMcpEntry(configPath, "harness", {
|
|
1160
|
+
command: "harness",
|
|
1161
|
+
args: ["mcp"],
|
|
1162
|
+
enabled: true
|
|
1163
|
+
});
|
|
1164
|
+
configured.push("Codex CLI");
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
if (client === "all" || client === "cursor") {
|
|
1168
|
+
const configPath = path9.join(cwd, ".cursor", "mcp.json");
|
|
1169
|
+
const existing = readJsonFile(configPath);
|
|
1170
|
+
if (existing?.mcpServers?.["harness"]) {
|
|
1171
|
+
skipped.push("Cursor");
|
|
1172
|
+
} else {
|
|
1173
|
+
writeMcpEntry(configPath, "harness", { command: "harness", args: ["mcp"] });
|
|
1174
|
+
configured.push("Cursor");
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
926
1177
|
return { configured, skipped, trustedFolder };
|
|
927
1178
|
}
|
|
1179
|
+
async function resolveCursorWithPicker(cwd, pick) {
|
|
1180
|
+
const configured = [];
|
|
1181
|
+
const skipped = [];
|
|
1182
|
+
const cursorConfigPath = path9.join(cwd, ".cursor", "mcp.json");
|
|
1183
|
+
const existing = readJsonFile(cursorConfigPath);
|
|
1184
|
+
if (existing?.mcpServers?.["harness"] && !pick) {
|
|
1185
|
+
skipped.push("Cursor");
|
|
1186
|
+
} else {
|
|
1187
|
+
const tools = pick ? await runCursorToolPicker() : CURSOR_CURATED_TOOLS;
|
|
1188
|
+
writeCursorMcpEntryWithTools(cursorConfigPath, tools);
|
|
1189
|
+
configured.push("Cursor");
|
|
1190
|
+
}
|
|
1191
|
+
return { configured, skipped };
|
|
1192
|
+
}
|
|
1193
|
+
function printMcpResult(configured, skipped, trustedFolder) {
|
|
1194
|
+
console.log("");
|
|
1195
|
+
if (configured.length > 0) {
|
|
1196
|
+
logger.success("MCP server configured!");
|
|
1197
|
+
console.log("");
|
|
1198
|
+
for (const name of configured) {
|
|
1199
|
+
console.log(` ${chalk.green("+")} ${name}`);
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
if (trustedFolder) {
|
|
1203
|
+
console.log("");
|
|
1204
|
+
logger.info("Added project to Gemini trusted folders (~/.gemini/trustedFolders.json)");
|
|
1205
|
+
}
|
|
1206
|
+
if (skipped.length > 0) {
|
|
1207
|
+
console.log("");
|
|
1208
|
+
logger.info("Already configured:");
|
|
1209
|
+
for (const name of skipped) {
|
|
1210
|
+
console.log(` ${chalk.dim("-")} ${name}`);
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
console.log("");
|
|
1214
|
+
console.log(chalk.bold("The harness MCP server provides:"));
|
|
1215
|
+
console.log(
|
|
1216
|
+
` - ${ALL_MCP_TOOLS.length} tools for validation, entropy detection, skill execution, graph querying, and more`
|
|
1217
|
+
);
|
|
1218
|
+
console.log(
|
|
1219
|
+
" - 8 resources for project context, skills, rules, learnings, state, and graph data"
|
|
1220
|
+
);
|
|
1221
|
+
console.log("");
|
|
1222
|
+
console.log(`Run ${chalk.cyan("harness skill list")} to see available skills.`);
|
|
1223
|
+
console.log("");
|
|
1224
|
+
}
|
|
928
1225
|
function createSetupMcpCommand() {
|
|
929
|
-
return new Command7("setup-mcp").description("Configure MCP server for AI agent integration").option("--client <client>", "Client to configure (claude, gemini, all)", "all").action(async (opts, cmd) => {
|
|
1226
|
+
return new Command7("setup-mcp").description("Configure MCP server for AI agent integration").option("--client <client>", "Client to configure (claude, gemini, codex, cursor, all)", "all").option("--pick", "Launch interactive tool picker (Cursor only)").option("--yes", "Bypass interactive picker and use curated 25-tool set (Cursor only)").action(async (opts, cmd) => {
|
|
930
1227
|
const globalOpts = cmd.optsWithGlobals();
|
|
931
1228
|
const cwd = process.cwd();
|
|
932
|
-
|
|
1229
|
+
let configured;
|
|
1230
|
+
let skipped;
|
|
1231
|
+
let trustedFolder = false;
|
|
1232
|
+
if (opts.client === "cursor" && (opts.pick || opts.yes)) {
|
|
1233
|
+
({ configured, skipped } = await resolveCursorWithPicker(cwd, opts.pick));
|
|
1234
|
+
} else {
|
|
1235
|
+
({ configured, skipped, trustedFolder } = setupMcp(cwd, opts.client));
|
|
1236
|
+
}
|
|
933
1237
|
if (!globalOpts.quiet) {
|
|
934
|
-
|
|
935
|
-
if (configured.length > 0) {
|
|
936
|
-
logger.success("MCP server configured!");
|
|
937
|
-
console.log("");
|
|
938
|
-
for (const name of configured) {
|
|
939
|
-
console.log(` ${chalk.green("+")} ${name}`);
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
|
-
if (trustedFolder) {
|
|
943
|
-
console.log("");
|
|
944
|
-
logger.info("Added project to Gemini trusted folders (~/.gemini/trustedFolders.json)");
|
|
945
|
-
}
|
|
946
|
-
if (skipped.length > 0) {
|
|
947
|
-
console.log("");
|
|
948
|
-
logger.info("Already configured:");
|
|
949
|
-
for (const name of skipped) {
|
|
950
|
-
console.log(` ${chalk.dim("-")} ${name}`);
|
|
951
|
-
}
|
|
952
|
-
}
|
|
953
|
-
console.log("");
|
|
954
|
-
console.log(chalk.bold("The harness MCP server provides:"));
|
|
955
|
-
console.log(
|
|
956
|
-
" - 31 tools for validation, entropy detection, skill execution, graph querying, and more"
|
|
957
|
-
);
|
|
958
|
-
console.log(
|
|
959
|
-
" - 8 resources for project context, skills, rules, learnings, state, and graph data"
|
|
960
|
-
);
|
|
961
|
-
console.log("");
|
|
962
|
-
console.log(`Run ${chalk.cyan("harness skill list")} to see available skills.`);
|
|
963
|
-
console.log("");
|
|
1238
|
+
printMcpResult(configured, skipped, trustedFolder);
|
|
964
1239
|
}
|
|
965
1240
|
process.exit(ExitCode.SUCCESS);
|
|
966
1241
|
});
|
|
@@ -969,10 +1244,10 @@ function createSetupMcpCommand() {
|
|
|
969
1244
|
// src/commands/init.ts
|
|
970
1245
|
async function runInit(options) {
|
|
971
1246
|
const cwd = options.cwd ?? process.cwd();
|
|
972
|
-
const name = options.name ??
|
|
1247
|
+
const name = options.name ?? path10.basename(cwd);
|
|
973
1248
|
const force = options.force ?? false;
|
|
974
|
-
const configPath =
|
|
975
|
-
if (!force &&
|
|
1249
|
+
const configPath = path10.join(cwd, "harness.config.json");
|
|
1250
|
+
if (!force && fs4.existsSync(configPath)) {
|
|
976
1251
|
return Err(
|
|
977
1252
|
new CLIError("Project already initialized. Use --force to overwrite.", ExitCode.ERROR)
|
|
978
1253
|
);
|
|
@@ -1090,7 +1365,7 @@ function createInitCommand() {
|
|
|
1090
1365
|
|
|
1091
1366
|
// src/commands/cleanup.ts
|
|
1092
1367
|
import { Command as Command9 } from "commander";
|
|
1093
|
-
import * as
|
|
1368
|
+
import * as path11 from "path";
|
|
1094
1369
|
async function runCleanup(options) {
|
|
1095
1370
|
const cwd = options.cwd ?? process.cwd();
|
|
1096
1371
|
const type = options.type ?? "all";
|
|
@@ -1105,11 +1380,11 @@ async function runCleanup(options) {
|
|
|
1105
1380
|
patternViolations: [],
|
|
1106
1381
|
totalIssues: 0
|
|
1107
1382
|
};
|
|
1108
|
-
const rootDir =
|
|
1109
|
-
const docsDir =
|
|
1383
|
+
const rootDir = path11.resolve(cwd, config.rootDir);
|
|
1384
|
+
const docsDir = path11.resolve(cwd, config.docsDir);
|
|
1110
1385
|
const entropyConfig = {
|
|
1111
1386
|
rootDir,
|
|
1112
|
-
entryPoints: [
|
|
1387
|
+
entryPoints: [path11.join(rootDir, "src/index.ts")],
|
|
1113
1388
|
docPaths: [docsDir],
|
|
1114
1389
|
analyze: {
|
|
1115
1390
|
drift: type === "all" || type === "drift",
|
|
@@ -1209,7 +1484,7 @@ function createCleanupCommand() {
|
|
|
1209
1484
|
|
|
1210
1485
|
// src/commands/fix-drift.ts
|
|
1211
1486
|
import { Command as Command10 } from "commander";
|
|
1212
|
-
import * as
|
|
1487
|
+
import * as path12 from "path";
|
|
1213
1488
|
async function runFixDrift(options) {
|
|
1214
1489
|
const cwd = options.cwd ?? process.cwd();
|
|
1215
1490
|
const dryRun = options.dryRun !== false;
|
|
@@ -1218,11 +1493,11 @@ async function runFixDrift(options) {
|
|
|
1218
1493
|
return Err(configResult.error);
|
|
1219
1494
|
}
|
|
1220
1495
|
const config = configResult.value;
|
|
1221
|
-
const rootDir =
|
|
1222
|
-
const docsDir =
|
|
1496
|
+
const rootDir = path12.resolve(cwd, config.rootDir);
|
|
1497
|
+
const docsDir = path12.resolve(cwd, config.docsDir);
|
|
1223
1498
|
const entropyConfig = {
|
|
1224
1499
|
rootDir,
|
|
1225
|
-
entryPoints: [
|
|
1500
|
+
entryPoints: [path12.join(rootDir, "src/index.ts")],
|
|
1226
1501
|
docPaths: [docsDir],
|
|
1227
1502
|
analyze: {
|
|
1228
1503
|
drift: true,
|
|
@@ -1375,7 +1650,7 @@ import { Command as Command13 } from "commander";
|
|
|
1375
1650
|
|
|
1376
1651
|
// src/commands/agent/run.ts
|
|
1377
1652
|
import { Command as Command11 } from "commander";
|
|
1378
|
-
import * as
|
|
1653
|
+
import * as path13 from "path";
|
|
1379
1654
|
import * as childProcess from "child_process";
|
|
1380
1655
|
async function runAgentTask(task, options) {
|
|
1381
1656
|
const configResult = resolveConfig(options.configPath);
|
|
@@ -1447,7 +1722,7 @@ function createCommandExecutor() {
|
|
|
1447
1722
|
}
|
|
1448
1723
|
async function runPersonaMode(opts, quiet) {
|
|
1449
1724
|
const personasDir = resolvePersonasDir();
|
|
1450
|
-
const filePath =
|
|
1725
|
+
const filePath = path13.join(personasDir, `${opts.persona}.yaml`);
|
|
1451
1726
|
const personaResult = loadPersona(filePath);
|
|
1452
1727
|
if (!personaResult.ok) {
|
|
1453
1728
|
logger.error(personaResult.error.message);
|
|
@@ -1619,8 +1894,8 @@ function createAgentCommand() {
|
|
|
1619
1894
|
|
|
1620
1895
|
// src/commands/add.ts
|
|
1621
1896
|
import { Command as Command14 } from "commander";
|
|
1622
|
-
import * as
|
|
1623
|
-
import * as
|
|
1897
|
+
import * as fs5 from "fs";
|
|
1898
|
+
import * as path14 from "path";
|
|
1624
1899
|
var LAYER_INDEX_TEMPLATE = (name) => `// ${name} layer
|
|
1625
1900
|
// Add your ${name} exports here
|
|
1626
1901
|
|
|
@@ -1664,62 +1939,62 @@ async function runAdd(componentType, name, options) {
|
|
|
1664
1939
|
try {
|
|
1665
1940
|
switch (componentType) {
|
|
1666
1941
|
case "layer": {
|
|
1667
|
-
const layerDir =
|
|
1668
|
-
if (!
|
|
1669
|
-
|
|
1942
|
+
const layerDir = path14.join(cwd, "src", name);
|
|
1943
|
+
if (!fs5.existsSync(layerDir)) {
|
|
1944
|
+
fs5.mkdirSync(layerDir, { recursive: true });
|
|
1670
1945
|
created.push(`src/${name}/`);
|
|
1671
1946
|
}
|
|
1672
|
-
const indexPath =
|
|
1673
|
-
if (!
|
|
1674
|
-
|
|
1947
|
+
const indexPath = path14.join(layerDir, "index.ts");
|
|
1948
|
+
if (!fs5.existsSync(indexPath)) {
|
|
1949
|
+
fs5.writeFileSync(indexPath, LAYER_INDEX_TEMPLATE(name));
|
|
1675
1950
|
created.push(`src/${name}/index.ts`);
|
|
1676
1951
|
}
|
|
1677
1952
|
break;
|
|
1678
1953
|
}
|
|
1679
1954
|
case "module": {
|
|
1680
|
-
const modulePath =
|
|
1681
|
-
if (
|
|
1955
|
+
const modulePath = path14.join(cwd, "src", `${name}.ts`);
|
|
1956
|
+
if (fs5.existsSync(modulePath)) {
|
|
1682
1957
|
return Err(new CLIError(`Module ${name} already exists`, ExitCode.ERROR));
|
|
1683
1958
|
}
|
|
1684
|
-
|
|
1959
|
+
fs5.writeFileSync(modulePath, MODULE_TEMPLATE(name));
|
|
1685
1960
|
created.push(`src/${name}.ts`);
|
|
1686
1961
|
break;
|
|
1687
1962
|
}
|
|
1688
1963
|
case "doc": {
|
|
1689
1964
|
const configDocsDir = configResult.ok ? configResult.value.docsDir : "./docs";
|
|
1690
|
-
const docsDir =
|
|
1691
|
-
if (!
|
|
1692
|
-
|
|
1965
|
+
const docsDir = path14.resolve(cwd, configDocsDir);
|
|
1966
|
+
if (!fs5.existsSync(docsDir)) {
|
|
1967
|
+
fs5.mkdirSync(docsDir, { recursive: true });
|
|
1693
1968
|
}
|
|
1694
|
-
const docPath =
|
|
1695
|
-
if (
|
|
1969
|
+
const docPath = path14.join(docsDir, `${name}.md`);
|
|
1970
|
+
if (fs5.existsSync(docPath)) {
|
|
1696
1971
|
return Err(new CLIError(`Doc ${name} already exists`, ExitCode.ERROR));
|
|
1697
1972
|
}
|
|
1698
|
-
|
|
1973
|
+
fs5.writeFileSync(docPath, DOC_TEMPLATE(name));
|
|
1699
1974
|
created.push(`${configDocsDir.replace(/^\.[\\/]/, "")}/${name}.md`);
|
|
1700
1975
|
break;
|
|
1701
1976
|
}
|
|
1702
1977
|
case "skill": {
|
|
1703
|
-
const { generateSkillFiles: generateSkillFiles2 } = await import("./create-skill-
|
|
1978
|
+
const { generateSkillFiles: generateSkillFiles2 } = await import("./create-skill-NDXQSTIK.js");
|
|
1704
1979
|
generateSkillFiles2({
|
|
1705
1980
|
name,
|
|
1706
1981
|
description: `${name} skill`,
|
|
1707
|
-
outputDir:
|
|
1982
|
+
outputDir: path14.join(cwd, "agents", "skills", "claude-code")
|
|
1708
1983
|
});
|
|
1709
1984
|
created.push(`agents/skills/claude-code/${name}/skill.yaml`);
|
|
1710
1985
|
created.push(`agents/skills/claude-code/${name}/SKILL.md`);
|
|
1711
1986
|
break;
|
|
1712
1987
|
}
|
|
1713
1988
|
case "persona": {
|
|
1714
|
-
const personasDir =
|
|
1715
|
-
if (!
|
|
1716
|
-
|
|
1989
|
+
const personasDir = path14.join(cwd, "agents", "personas");
|
|
1990
|
+
if (!fs5.existsSync(personasDir)) {
|
|
1991
|
+
fs5.mkdirSync(personasDir, { recursive: true });
|
|
1717
1992
|
}
|
|
1718
|
-
const personaPath =
|
|
1719
|
-
if (
|
|
1993
|
+
const personaPath = path14.join(personasDir, `${name}.yaml`);
|
|
1994
|
+
if (fs5.existsSync(personaPath)) {
|
|
1720
1995
|
return Err(new CLIError(`Persona ${name} already exists`, ExitCode.ERROR));
|
|
1721
1996
|
}
|
|
1722
|
-
|
|
1997
|
+
fs5.writeFileSync(
|
|
1723
1998
|
personaPath,
|
|
1724
1999
|
`name: ${name}
|
|
1725
2000
|
description: ${name} persona
|
|
@@ -1905,46 +2180,46 @@ function createListCommand() {
|
|
|
1905
2180
|
|
|
1906
2181
|
// src/commands/persona/generate.ts
|
|
1907
2182
|
import { Command as Command19 } from "commander";
|
|
1908
|
-
import * as
|
|
1909
|
-
import * as
|
|
2183
|
+
import * as fs6 from "fs";
|
|
2184
|
+
import * as path15 from "path";
|
|
1910
2185
|
function createGenerateCommand2() {
|
|
1911
2186
|
return new Command19("generate").description("Generate artifacts from a persona config").argument("<name>", "Persona name (e.g., architecture-enforcer)").option("--output-dir <dir>", "Output directory", ".").option("--only <type>", "Generate only: ci, agents-md, runtime").action(async (name, opts, cmd) => {
|
|
1912
2187
|
const globalOpts = cmd.optsWithGlobals();
|
|
1913
2188
|
const personasDir = resolvePersonasDir();
|
|
1914
|
-
const filePath =
|
|
2189
|
+
const filePath = path15.join(personasDir, `${name}.yaml`);
|
|
1915
2190
|
const personaResult = loadPersona(filePath);
|
|
1916
2191
|
if (!personaResult.ok) {
|
|
1917
2192
|
logger.error(personaResult.error.message);
|
|
1918
2193
|
process.exit(ExitCode.ERROR);
|
|
1919
2194
|
}
|
|
1920
2195
|
const persona = personaResult.value;
|
|
1921
|
-
const outputDir =
|
|
2196
|
+
const outputDir = path15.resolve(opts.outputDir);
|
|
1922
2197
|
const slug = toKebabCase(persona.name);
|
|
1923
2198
|
const only = opts.only;
|
|
1924
2199
|
const generated = [];
|
|
1925
2200
|
if (!only || only === "runtime") {
|
|
1926
2201
|
const result = generateRuntime(persona);
|
|
1927
2202
|
if (result.ok) {
|
|
1928
|
-
const outPath =
|
|
1929
|
-
|
|
1930
|
-
|
|
2203
|
+
const outPath = path15.join(outputDir, `${slug}.runtime.json`);
|
|
2204
|
+
fs6.mkdirSync(path15.dirname(outPath), { recursive: true });
|
|
2205
|
+
fs6.writeFileSync(outPath, result.value);
|
|
1931
2206
|
generated.push(outPath);
|
|
1932
2207
|
}
|
|
1933
2208
|
}
|
|
1934
2209
|
if (!only || only === "agents-md") {
|
|
1935
2210
|
const result = generateAgentsMd(persona);
|
|
1936
2211
|
if (result.ok) {
|
|
1937
|
-
const outPath =
|
|
1938
|
-
|
|
2212
|
+
const outPath = path15.join(outputDir, `${slug}.agents.md`);
|
|
2213
|
+
fs6.writeFileSync(outPath, result.value);
|
|
1939
2214
|
generated.push(outPath);
|
|
1940
2215
|
}
|
|
1941
2216
|
}
|
|
1942
2217
|
if (!only || only === "ci") {
|
|
1943
2218
|
const result = generateCIWorkflow(persona, "github");
|
|
1944
2219
|
if (result.ok) {
|
|
1945
|
-
const outPath =
|
|
1946
|
-
|
|
1947
|
-
|
|
2220
|
+
const outPath = path15.join(outputDir, ".github", "workflows", `${slug}.yml`);
|
|
2221
|
+
fs6.mkdirSync(path15.dirname(outPath), { recursive: true });
|
|
2222
|
+
fs6.writeFileSync(outPath, result.value);
|
|
1948
2223
|
generated.push(outPath);
|
|
1949
2224
|
}
|
|
1950
2225
|
}
|
|
@@ -1969,13 +2244,13 @@ import { Command as Command28 } from "commander";
|
|
|
1969
2244
|
|
|
1970
2245
|
// src/commands/skill/list.ts
|
|
1971
2246
|
import { Command as Command21 } from "commander";
|
|
1972
|
-
import * as
|
|
1973
|
-
import * as
|
|
2247
|
+
import * as fs8 from "fs";
|
|
2248
|
+
import * as path17 from "path";
|
|
1974
2249
|
import { parse } from "yaml";
|
|
1975
2250
|
|
|
1976
2251
|
// src/registry/lockfile.ts
|
|
1977
|
-
import * as
|
|
1978
|
-
import * as
|
|
2252
|
+
import * as fs7 from "fs";
|
|
2253
|
+
import * as path16 from "path";
|
|
1979
2254
|
function createEmptyLockfile() {
|
|
1980
2255
|
return { version: 1, skills: {} };
|
|
1981
2256
|
}
|
|
@@ -1995,10 +2270,10 @@ function sortedStringify(obj) {
|
|
|
1995
2270
|
);
|
|
1996
2271
|
}
|
|
1997
2272
|
function readLockfile2(filePath) {
|
|
1998
|
-
if (!
|
|
2273
|
+
if (!fs7.existsSync(filePath)) {
|
|
1999
2274
|
return createEmptyLockfile();
|
|
2000
2275
|
}
|
|
2001
|
-
const raw =
|
|
2276
|
+
const raw = fs7.readFileSync(filePath, "utf-8");
|
|
2002
2277
|
let parsed;
|
|
2003
2278
|
try {
|
|
2004
2279
|
parsed = JSON.parse(raw);
|
|
@@ -2015,9 +2290,9 @@ function readLockfile2(filePath) {
|
|
|
2015
2290
|
return parsed;
|
|
2016
2291
|
}
|
|
2017
2292
|
function writeLockfile2(filePath, lockfile) {
|
|
2018
|
-
const dir =
|
|
2019
|
-
|
|
2020
|
-
|
|
2293
|
+
const dir = path16.dirname(filePath);
|
|
2294
|
+
fs7.mkdirSync(dir, { recursive: true });
|
|
2295
|
+
fs7.writeFileSync(filePath, sortedStringify(lockfile) + "\n", "utf-8");
|
|
2021
2296
|
}
|
|
2022
2297
|
function updateLockfileEntry(lockfile, name, entry) {
|
|
2023
2298
|
return {
|
|
@@ -2041,14 +2316,14 @@ function removeLockfileEntry(lockfile, name) {
|
|
|
2041
2316
|
|
|
2042
2317
|
// src/commands/skill/list.ts
|
|
2043
2318
|
function scanDirectory(dirPath, source) {
|
|
2044
|
-
if (!
|
|
2045
|
-
const entries =
|
|
2319
|
+
if (!fs8.existsSync(dirPath)) return [];
|
|
2320
|
+
const entries = fs8.readdirSync(dirPath, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
2046
2321
|
const skills = [];
|
|
2047
2322
|
for (const name of entries) {
|
|
2048
|
-
const yamlPath =
|
|
2049
|
-
if (!
|
|
2323
|
+
const yamlPath = path17.join(dirPath, name, "skill.yaml");
|
|
2324
|
+
if (!fs8.existsSync(yamlPath)) continue;
|
|
2050
2325
|
try {
|
|
2051
|
-
const raw =
|
|
2326
|
+
const raw = fs8.readFileSync(yamlPath, "utf-8");
|
|
2052
2327
|
const parsed = parse(raw);
|
|
2053
2328
|
const result = SkillMetadataSchema.safeParse(parsed);
|
|
2054
2329
|
if (result.success) {
|
|
@@ -2066,10 +2341,10 @@ function scanDirectory(dirPath, source) {
|
|
|
2066
2341
|
}
|
|
2067
2342
|
function collectCommunitySkills(seen, allSkills) {
|
|
2068
2343
|
const globalDir = resolveGlobalSkillsDir();
|
|
2069
|
-
const skillsDir =
|
|
2070
|
-
const communityBase =
|
|
2071
|
-
const communityPlatformDir =
|
|
2072
|
-
const lockfilePath =
|
|
2344
|
+
const skillsDir = path17.dirname(globalDir);
|
|
2345
|
+
const communityBase = path17.join(skillsDir, "community");
|
|
2346
|
+
const communityPlatformDir = path17.join(communityBase, "claude-code");
|
|
2347
|
+
const lockfilePath = path17.join(communityBase, "skills-lock.json");
|
|
2073
2348
|
const lockfile = readLockfile2(lockfilePath);
|
|
2074
2349
|
const communitySkills = scanDirectory(communityPlatformDir, "community");
|
|
2075
2350
|
for (const skill of communitySkills) {
|
|
@@ -2155,20 +2430,20 @@ function createListCommand2() {
|
|
|
2155
2430
|
|
|
2156
2431
|
// src/commands/skill/run.ts
|
|
2157
2432
|
import { Command as Command22 } from "commander";
|
|
2158
|
-
import * as
|
|
2159
|
-
import * as
|
|
2433
|
+
import * as fs9 from "fs";
|
|
2434
|
+
import * as path18 from "path";
|
|
2160
2435
|
import { parse as parse2 } from "yaml";
|
|
2161
2436
|
|
|
2162
2437
|
// src/skill/complexity.ts
|
|
2163
2438
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
2164
2439
|
function evaluateSignals(signals) {
|
|
2165
|
-
if (signals.fileCount >= 3) return "
|
|
2166
|
-
if (signals.newDir) return "
|
|
2167
|
-
if (signals.newDep) return "
|
|
2168
|
-
if (signals.fileCount <= 1) return "
|
|
2169
|
-
if (signals.testOnly) return "
|
|
2170
|
-
if (signals.docsOnly) return "
|
|
2171
|
-
return "
|
|
2440
|
+
if (signals.fileCount >= 3) return "thorough";
|
|
2441
|
+
if (signals.newDir) return "thorough";
|
|
2442
|
+
if (signals.newDep) return "thorough";
|
|
2443
|
+
if (signals.fileCount <= 1) return "fast";
|
|
2444
|
+
if (signals.testOnly) return "fast";
|
|
2445
|
+
if (signals.docsOnly) return "fast";
|
|
2446
|
+
return "thorough";
|
|
2172
2447
|
}
|
|
2173
2448
|
function detectComplexity(projectPath) {
|
|
2174
2449
|
try {
|
|
@@ -2201,7 +2476,7 @@ function detectComplexity(projectPath) {
|
|
|
2201
2476
|
};
|
|
2202
2477
|
return evaluateSignals(signals);
|
|
2203
2478
|
} catch {
|
|
2204
|
-
return "
|
|
2479
|
+
return "thorough";
|
|
2205
2480
|
}
|
|
2206
2481
|
}
|
|
2207
2482
|
|
|
@@ -2211,8 +2486,8 @@ function buildPreamble(options) {
|
|
|
2211
2486
|
if (options.complexity && options.phases && options.phases.length > 0) {
|
|
2212
2487
|
const lines = [`## Active Phases (complexity: ${options.complexity})`];
|
|
2213
2488
|
for (const phase of options.phases) {
|
|
2214
|
-
if (options.complexity === "
|
|
2215
|
-
lines.push(`- ~~${phase.name.toUpperCase()}~~ (skipped in
|
|
2489
|
+
if (options.complexity === "fast" && !phase.required) {
|
|
2490
|
+
lines.push(`- ~~${phase.name.toUpperCase()}~~ (skipped in fast mode)`);
|
|
2216
2491
|
} else {
|
|
2217
2492
|
lines.push(`- ${phase.name.toUpperCase()} (${phase.required ? "required" : "optional"})`);
|
|
2218
2493
|
}
|
|
@@ -2244,10 +2519,10 @@ ${options.priorState}`);
|
|
|
2244
2519
|
|
|
2245
2520
|
// src/commands/skill/run.ts
|
|
2246
2521
|
function loadSkillMetadata(skillDir) {
|
|
2247
|
-
const yamlPath =
|
|
2248
|
-
if (!
|
|
2522
|
+
const yamlPath = path18.join(skillDir, "skill.yaml");
|
|
2523
|
+
if (!fs9.existsSync(yamlPath)) return null;
|
|
2249
2524
|
try {
|
|
2250
|
-
const result = SkillMetadataSchema.safeParse(parse2(
|
|
2525
|
+
const result = SkillMetadataSchema.safeParse(parse2(fs9.readFileSync(yamlPath, "utf-8")));
|
|
2251
2526
|
return result.success ? result.data : null;
|
|
2252
2527
|
} catch {
|
|
2253
2528
|
return null;
|
|
@@ -2255,26 +2530,26 @@ function loadSkillMetadata(skillDir) {
|
|
|
2255
2530
|
}
|
|
2256
2531
|
function resolveComplexity(metadata, requested, projectPath) {
|
|
2257
2532
|
if (!metadata?.phases || metadata.phases.length === 0) return void 0;
|
|
2258
|
-
if (requested === "
|
|
2533
|
+
if (requested === "standard") return detectComplexity(projectPath);
|
|
2259
2534
|
return requested;
|
|
2260
2535
|
}
|
|
2261
2536
|
function loadPrinciples(projectPath) {
|
|
2262
|
-
const principlesPath =
|
|
2263
|
-
return
|
|
2537
|
+
const principlesPath = path18.join(projectPath, "docs", "principles.md");
|
|
2538
|
+
return fs9.existsSync(principlesPath) ? fs9.readFileSync(principlesPath, "utf-8") : void 0;
|
|
2264
2539
|
}
|
|
2265
2540
|
function readMostRecentFileInDir(dirPath) {
|
|
2266
|
-
const files =
|
|
2267
|
-
if (files.length > 0) return
|
|
2541
|
+
const files = fs9.readdirSync(dirPath).map((f) => ({ name: f, mtime: fs9.statSync(path18.join(dirPath, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
|
|
2542
|
+
if (files.length > 0) return fs9.readFileSync(path18.join(dirPath, files[0].name), "utf-8");
|
|
2268
2543
|
return void 0;
|
|
2269
2544
|
}
|
|
2270
2545
|
function loadPriorState(metadata, projectPath) {
|
|
2271
2546
|
if (!metadata?.state.persistent || metadata.state.files.length === 0) return void 0;
|
|
2272
2547
|
for (const stateFilePath of metadata.state.files) {
|
|
2273
|
-
const fullPath =
|
|
2274
|
-
if (!
|
|
2275
|
-
const stat =
|
|
2548
|
+
const fullPath = path18.join(projectPath, stateFilePath);
|
|
2549
|
+
if (!fs9.existsSync(fullPath)) continue;
|
|
2550
|
+
const stat = fs9.statSync(fullPath);
|
|
2276
2551
|
if (stat.isDirectory()) return readMostRecentFileInDir(fullPath);
|
|
2277
|
-
return
|
|
2552
|
+
return fs9.readFileSync(fullPath, "utf-8");
|
|
2278
2553
|
}
|
|
2279
2554
|
return void 0;
|
|
2280
2555
|
}
|
|
@@ -2294,9 +2569,9 @@ function resolvePhaseState(metadata, projectPath, phase) {
|
|
|
2294
2569
|
}
|
|
2295
2570
|
function appendProjectState(content, metadata, projectPath, hasPathOpt) {
|
|
2296
2571
|
if (!metadata?.state.persistent || !hasPathOpt) return content;
|
|
2297
|
-
const stateFile =
|
|
2298
|
-
if (!
|
|
2299
|
-
const stateContent =
|
|
2572
|
+
const stateFile = path18.join(projectPath, ".harness", "state.json");
|
|
2573
|
+
if (!fs9.existsSync(stateFile)) return content;
|
|
2574
|
+
const stateContent = fs9.readFileSync(stateFile, "utf-8");
|
|
2300
2575
|
return content + `
|
|
2301
2576
|
|
|
2302
2577
|
---
|
|
@@ -2307,19 +2582,19 @@ ${stateContent}
|
|
|
2307
2582
|
`;
|
|
2308
2583
|
}
|
|
2309
2584
|
function createRunCommand2() {
|
|
2310
|
-
return new Command22("run").description("Run a skill (outputs SKILL.md content with context preamble)").argument("<name>", "Skill name (e.g., harness-tdd)").option("--path <path>", "Project root path for context injection").option("--complexity <level>", "
|
|
2585
|
+
return new Command22("run").description("Run a skill (outputs SKILL.md content with context preamble)").argument("<name>", "Skill name (e.g., harness-tdd)").option("--path <path>", "Project root path for context injection").option("--complexity <level>", "Rigor level: fast, standard, thorough", "standard").option("--phase <name>", "Start at a specific phase (for re-entry)").option("--party", "Enable multi-perspective evaluation").action(async (name, opts, _cmd) => {
|
|
2311
2586
|
const skillsDir = resolveSkillsDir();
|
|
2312
|
-
const skillDir =
|
|
2313
|
-
if (!
|
|
2587
|
+
const skillDir = path18.join(skillsDir, name);
|
|
2588
|
+
if (!fs9.existsSync(skillDir)) {
|
|
2314
2589
|
logger.error(`Skill not found: ${name}`);
|
|
2315
2590
|
process.exit(ExitCode.ERROR);
|
|
2316
2591
|
return;
|
|
2317
2592
|
}
|
|
2318
2593
|
const metadata = loadSkillMetadata(skillDir);
|
|
2319
|
-
const projectPath = opts.path ?
|
|
2594
|
+
const projectPath = opts.path ? path18.resolve(opts.path) : process.cwd();
|
|
2320
2595
|
const complexity = resolveComplexity(
|
|
2321
2596
|
metadata,
|
|
2322
|
-
opts.complexity ?? "
|
|
2597
|
+
opts.complexity ?? "standard",
|
|
2323
2598
|
projectPath
|
|
2324
2599
|
);
|
|
2325
2600
|
const principles = loadPrinciples(projectPath);
|
|
@@ -2343,14 +2618,14 @@ function createRunCommand2() {
|
|
|
2343
2618
|
...stateWarning !== void 0 && { stateWarning },
|
|
2344
2619
|
party: opts.party
|
|
2345
2620
|
});
|
|
2346
|
-
const skillMdPath =
|
|
2347
|
-
if (!
|
|
2621
|
+
const skillMdPath = path18.join(skillDir, "SKILL.md");
|
|
2622
|
+
if (!fs9.existsSync(skillMdPath)) {
|
|
2348
2623
|
logger.error(`SKILL.md not found for skill: ${name}`);
|
|
2349
2624
|
process.exit(ExitCode.ERROR);
|
|
2350
2625
|
return;
|
|
2351
2626
|
}
|
|
2352
2627
|
const content = appendProjectState(
|
|
2353
|
-
|
|
2628
|
+
fs9.readFileSync(skillMdPath, "utf-8"),
|
|
2354
2629
|
metadata,
|
|
2355
2630
|
projectPath,
|
|
2356
2631
|
!!opts.path
|
|
@@ -2362,8 +2637,8 @@ function createRunCommand2() {
|
|
|
2362
2637
|
|
|
2363
2638
|
// src/commands/skill/validate.ts
|
|
2364
2639
|
import { Command as Command23 } from "commander";
|
|
2365
|
-
import * as
|
|
2366
|
-
import * as
|
|
2640
|
+
import * as fs10 from "fs";
|
|
2641
|
+
import * as path19 from "path";
|
|
2367
2642
|
import { parse as parse3 } from "yaml";
|
|
2368
2643
|
var REQUIRED_SECTIONS = [
|
|
2369
2644
|
"## When to Use",
|
|
@@ -2373,11 +2648,11 @@ var REQUIRED_SECTIONS = [
|
|
|
2373
2648
|
"## Examples"
|
|
2374
2649
|
];
|
|
2375
2650
|
function validateSkillMd(name, skillMdPath, skillType, errors) {
|
|
2376
|
-
if (!
|
|
2651
|
+
if (!fs10.existsSync(skillMdPath)) {
|
|
2377
2652
|
errors.push(`${name}: missing SKILL.md`);
|
|
2378
2653
|
return;
|
|
2379
2654
|
}
|
|
2380
|
-
const mdContent =
|
|
2655
|
+
const mdContent = fs10.readFileSync(skillMdPath, "utf-8");
|
|
2381
2656
|
for (const section of REQUIRED_SECTIONS) {
|
|
2382
2657
|
if (!mdContent.includes(section)) {
|
|
2383
2658
|
errors.push(`${name}/SKILL.md: missing section "${section}"`);
|
|
@@ -2394,20 +2669,20 @@ function validateSkillMd(name, skillMdPath, skillType, errors) {
|
|
|
2394
2669
|
}
|
|
2395
2670
|
}
|
|
2396
2671
|
function validateSkillEntry(name, skillsDir, errors) {
|
|
2397
|
-
const skillDir =
|
|
2398
|
-
const yamlPath =
|
|
2399
|
-
if (!
|
|
2672
|
+
const skillDir = path19.join(skillsDir, name);
|
|
2673
|
+
const yamlPath = path19.join(skillDir, "skill.yaml");
|
|
2674
|
+
if (!fs10.existsSync(yamlPath)) {
|
|
2400
2675
|
errors.push(`${name}: missing skill.yaml`);
|
|
2401
2676
|
return false;
|
|
2402
2677
|
}
|
|
2403
2678
|
try {
|
|
2404
|
-
const raw =
|
|
2679
|
+
const raw = fs10.readFileSync(yamlPath, "utf-8");
|
|
2405
2680
|
const result = SkillMetadataSchema.safeParse(parse3(raw));
|
|
2406
2681
|
if (!result.success) {
|
|
2407
2682
|
errors.push(`${name}/skill.yaml: ${result.error.message}`);
|
|
2408
2683
|
return false;
|
|
2409
2684
|
}
|
|
2410
|
-
validateSkillMd(name,
|
|
2685
|
+
validateSkillMd(name, path19.join(skillDir, "SKILL.md"), result.data.type, errors);
|
|
2411
2686
|
return true;
|
|
2412
2687
|
} catch (e) {
|
|
2413
2688
|
errors.push(`${name}: parse error \u2014 ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -2418,12 +2693,12 @@ function createValidateCommand3() {
|
|
|
2418
2693
|
return new Command23("validate").description("Validate all skill.yaml files and SKILL.md structure").action(async (_opts, cmd) => {
|
|
2419
2694
|
const globalOpts = cmd.optsWithGlobals();
|
|
2420
2695
|
const skillsDir = resolveSkillsDir();
|
|
2421
|
-
if (!
|
|
2696
|
+
if (!fs10.existsSync(skillsDir)) {
|
|
2422
2697
|
logger.info("No skills directory found.");
|
|
2423
2698
|
process.exit(ExitCode.SUCCESS);
|
|
2424
2699
|
return;
|
|
2425
2700
|
}
|
|
2426
|
-
const entries =
|
|
2701
|
+
const entries = fs10.readdirSync(skillsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
2427
2702
|
const errors = [];
|
|
2428
2703
|
let validated = 0;
|
|
2429
2704
|
for (const name of entries) {
|
|
@@ -2446,27 +2721,27 @@ function createValidateCommand3() {
|
|
|
2446
2721
|
|
|
2447
2722
|
// src/commands/skill/info.ts
|
|
2448
2723
|
import { Command as Command24 } from "commander";
|
|
2449
|
-
import * as
|
|
2450
|
-
import * as
|
|
2724
|
+
import * as fs11 from "fs";
|
|
2725
|
+
import * as path20 from "path";
|
|
2451
2726
|
import { parse as parse4 } from "yaml";
|
|
2452
2727
|
function createInfoCommand() {
|
|
2453
2728
|
return new Command24("info").description("Show metadata for a skill").argument("<name>", "Skill name (e.g., harness-tdd)").action(async (name, _opts, cmd) => {
|
|
2454
2729
|
const globalOpts = cmd.optsWithGlobals();
|
|
2455
2730
|
const skillsDir = resolveSkillsDir();
|
|
2456
|
-
const skillDir =
|
|
2457
|
-
if (!
|
|
2731
|
+
const skillDir = path20.join(skillsDir, name);
|
|
2732
|
+
if (!fs11.existsSync(skillDir)) {
|
|
2458
2733
|
logger.error(`Skill not found: ${name}`);
|
|
2459
2734
|
process.exit(ExitCode.ERROR);
|
|
2460
2735
|
return;
|
|
2461
2736
|
}
|
|
2462
|
-
const yamlPath =
|
|
2463
|
-
if (!
|
|
2737
|
+
const yamlPath = path20.join(skillDir, "skill.yaml");
|
|
2738
|
+
if (!fs11.existsSync(yamlPath)) {
|
|
2464
2739
|
logger.error(`skill.yaml not found for skill: ${name}`);
|
|
2465
2740
|
process.exit(ExitCode.ERROR);
|
|
2466
2741
|
return;
|
|
2467
2742
|
}
|
|
2468
2743
|
try {
|
|
2469
|
-
const raw =
|
|
2744
|
+
const raw = fs11.readFileSync(yamlPath, "utf-8");
|
|
2470
2745
|
const parsed = parse4(raw);
|
|
2471
2746
|
const result = SkillMetadataSchema.safeParse(parsed);
|
|
2472
2747
|
if (!result.success) {
|
|
@@ -2509,8 +2784,8 @@ function createInfoCommand() {
|
|
|
2509
2784
|
import { Command as Command25 } from "commander";
|
|
2510
2785
|
|
|
2511
2786
|
// src/registry/npm-client.ts
|
|
2512
|
-
import * as
|
|
2513
|
-
import * as
|
|
2787
|
+
import * as fs12 from "fs";
|
|
2788
|
+
import * as path21 from "path";
|
|
2514
2789
|
import * as os2 from "os";
|
|
2515
2790
|
var NPM_REGISTRY = "https://registry.npmjs.org";
|
|
2516
2791
|
var FETCH_TIMEOUT_MS = 3e4;
|
|
@@ -2533,10 +2808,10 @@ function extractSkillName(packageName) {
|
|
|
2533
2808
|
function readNpmrcToken(registryUrl) {
|
|
2534
2809
|
const { hostname, pathname } = new URL(registryUrl);
|
|
2535
2810
|
const registryPath = `//${hostname}${pathname.replace(/\/$/, "")}/:_authToken=`;
|
|
2536
|
-
const candidates = [
|
|
2811
|
+
const candidates = [path21.join(process.cwd(), ".npmrc"), path21.join(os2.homedir(), ".npmrc")];
|
|
2537
2812
|
for (const npmrcPath of candidates) {
|
|
2538
2813
|
try {
|
|
2539
|
-
const content =
|
|
2814
|
+
const content = fs12.readFileSync(npmrcPath, "utf-8");
|
|
2540
2815
|
for (const line of content.split("\n")) {
|
|
2541
2816
|
const trimmed = line.trim();
|
|
2542
2817
|
if (trimmed.startsWith(registryPath)) {
|
|
@@ -2681,8 +2956,8 @@ Found ${results.length} skill(s):
|
|
|
2681
2956
|
|
|
2682
2957
|
// src/commands/skill/create.ts
|
|
2683
2958
|
import { Command as Command26 } from "commander";
|
|
2684
|
-
import * as
|
|
2685
|
-
import * as
|
|
2959
|
+
import * as path22 from "path";
|
|
2960
|
+
import * as fs13 from "fs";
|
|
2686
2961
|
import YAML from "yaml";
|
|
2687
2962
|
var KEBAB_CASE_RE = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
2688
2963
|
function buildReadme(name, description) {
|
|
@@ -2766,22 +3041,22 @@ function runCreate(name, opts) {
|
|
|
2766
3041
|
if (!KEBAB_CASE_RE.test(name)) {
|
|
2767
3042
|
throw new Error(`Invalid skill name "${name}". Must be kebab-case (e.g., my-skill).`);
|
|
2768
3043
|
}
|
|
2769
|
-
const baseDir = opts.outputDir ??
|
|
2770
|
-
const skillDir =
|
|
2771
|
-
if (
|
|
3044
|
+
const baseDir = opts.outputDir ?? path22.join(process.cwd(), "agents", "skills", "claude-code");
|
|
3045
|
+
const skillDir = path22.join(baseDir, name);
|
|
3046
|
+
if (fs13.existsSync(skillDir)) {
|
|
2772
3047
|
throw new Error(`Skill directory already exists: ${skillDir}`);
|
|
2773
3048
|
}
|
|
2774
|
-
|
|
3049
|
+
fs13.mkdirSync(skillDir, { recursive: true });
|
|
2775
3050
|
const description = opts.description || `A community skill: ${name}`;
|
|
2776
3051
|
const skillYaml = buildSkillYaml(name, opts);
|
|
2777
|
-
const skillYamlPath =
|
|
2778
|
-
|
|
3052
|
+
const skillYamlPath = path22.join(skillDir, "skill.yaml");
|
|
3053
|
+
fs13.writeFileSync(skillYamlPath, YAML.stringify(skillYaml));
|
|
2779
3054
|
const skillMd = buildSkillMd(name, description);
|
|
2780
|
-
const skillMdPath =
|
|
2781
|
-
|
|
3055
|
+
const skillMdPath = path22.join(skillDir, "SKILL.md");
|
|
3056
|
+
fs13.writeFileSync(skillMdPath, skillMd);
|
|
2782
3057
|
const readme = buildReadme(name, description);
|
|
2783
|
-
const readmePath =
|
|
2784
|
-
|
|
3058
|
+
const readmePath = path22.join(skillDir, "README.md");
|
|
3059
|
+
fs13.writeFileSync(readmePath, readme);
|
|
2785
3060
|
return {
|
|
2786
3061
|
name,
|
|
2787
3062
|
directory: skillDir,
|
|
@@ -2809,7 +3084,7 @@ function createCreateCommand() {
|
|
|
2809
3084
|
logger.info(`
|
|
2810
3085
|
Next steps:`);
|
|
2811
3086
|
logger.info(
|
|
2812
|
-
` 1. Edit ${
|
|
3087
|
+
` 1. Edit ${path22.join(result.directory, "SKILL.md")} with your skill content`
|
|
2813
3088
|
);
|
|
2814
3089
|
logger.info(` 2. Run: harness skill validate ${name}`);
|
|
2815
3090
|
logger.info(` 3. Run: harness skills publish`);
|
|
@@ -2823,28 +3098,28 @@ Next steps:`);
|
|
|
2823
3098
|
|
|
2824
3099
|
// src/commands/skill/publish.ts
|
|
2825
3100
|
import { Command as Command27 } from "commander";
|
|
2826
|
-
import * as
|
|
2827
|
-
import * as
|
|
3101
|
+
import * as fs16 from "fs";
|
|
3102
|
+
import * as path25 from "path";
|
|
2828
3103
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
2829
3104
|
|
|
2830
3105
|
// src/registry/validator.ts
|
|
2831
|
-
import * as
|
|
2832
|
-
import * as
|
|
3106
|
+
import * as fs15 from "fs";
|
|
3107
|
+
import * as path24 from "path";
|
|
2833
3108
|
import { parse as parse5 } from "yaml";
|
|
2834
3109
|
import semver from "semver";
|
|
2835
3110
|
|
|
2836
3111
|
// src/registry/bundled-skills.ts
|
|
2837
|
-
import * as
|
|
2838
|
-
import * as
|
|
3112
|
+
import * as fs14 from "fs";
|
|
3113
|
+
import * as path23 from "path";
|
|
2839
3114
|
function getBundledSkillNames(bundledSkillsDir) {
|
|
2840
|
-
if (!
|
|
3115
|
+
if (!fs14.existsSync(bundledSkillsDir)) {
|
|
2841
3116
|
return /* @__PURE__ */ new Set();
|
|
2842
3117
|
}
|
|
2843
|
-
const entries =
|
|
3118
|
+
const entries = fs14.readdirSync(bundledSkillsDir);
|
|
2844
3119
|
const names = /* @__PURE__ */ new Set();
|
|
2845
3120
|
for (const entry of entries) {
|
|
2846
3121
|
try {
|
|
2847
|
-
const stat =
|
|
3122
|
+
const stat = fs14.statSync(path23.join(bundledSkillsDir, String(entry)));
|
|
2848
3123
|
if (stat.isDirectory()) {
|
|
2849
3124
|
names.add(String(entry));
|
|
2850
3125
|
}
|
|
@@ -2857,14 +3132,14 @@ function getBundledSkillNames(bundledSkillsDir) {
|
|
|
2857
3132
|
// src/registry/validator.ts
|
|
2858
3133
|
async function validateForPublish(skillDir, registryUrl) {
|
|
2859
3134
|
const errors = [];
|
|
2860
|
-
const skillYamlPath =
|
|
2861
|
-
if (!
|
|
3135
|
+
const skillYamlPath = path24.join(skillDir, "skill.yaml");
|
|
3136
|
+
if (!fs15.existsSync(skillYamlPath)) {
|
|
2862
3137
|
errors.push("skill.yaml not found. Create one with: harness skill create <name>");
|
|
2863
3138
|
return { valid: false, errors };
|
|
2864
3139
|
}
|
|
2865
3140
|
let skillMeta;
|
|
2866
3141
|
try {
|
|
2867
|
-
const raw =
|
|
3142
|
+
const raw = fs15.readFileSync(skillYamlPath, "utf-8");
|
|
2868
3143
|
const parsed = parse5(raw);
|
|
2869
3144
|
const result = SkillMetadataSchema.safeParse(parsed);
|
|
2870
3145
|
if (!result.success) {
|
|
@@ -2886,11 +3161,11 @@ async function validateForPublish(skillDir, registryUrl) {
|
|
|
2886
3161
|
if (!skillMeta.triggers || skillMeta.triggers.length === 0) {
|
|
2887
3162
|
errors.push("At least one trigger is required. Add triggers to skill.yaml.");
|
|
2888
3163
|
}
|
|
2889
|
-
const skillMdPath =
|
|
2890
|
-
if (!
|
|
3164
|
+
const skillMdPath = path24.join(skillDir, "SKILL.md");
|
|
3165
|
+
if (!fs15.existsSync(skillMdPath)) {
|
|
2891
3166
|
errors.push("SKILL.md not found. Create it with content describing your skill.");
|
|
2892
3167
|
} else {
|
|
2893
|
-
const content =
|
|
3168
|
+
const content = fs15.readFileSync(skillMdPath, "utf-8");
|
|
2894
3169
|
if (!content.includes("## When to Use")) {
|
|
2895
3170
|
errors.push('SKILL.md must contain a "## When to Use" section.');
|
|
2896
3171
|
}
|
|
@@ -2970,11 +3245,11 @@ ${errorList}`);
|
|
|
2970
3245
|
}
|
|
2971
3246
|
const meta = validation.skillMeta;
|
|
2972
3247
|
const pkg = derivePackageJson(meta);
|
|
2973
|
-
const pkgPath =
|
|
2974
|
-
|
|
2975
|
-
const readmePath =
|
|
2976
|
-
if (!
|
|
2977
|
-
const skillMdContent =
|
|
3248
|
+
const pkgPath = path25.join(skillDir, "package.json");
|
|
3249
|
+
fs16.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
3250
|
+
const readmePath = path25.join(skillDir, "README.md");
|
|
3251
|
+
if (!fs16.existsSync(readmePath)) {
|
|
3252
|
+
const skillMdContent = fs16.readFileSync(path25.join(skillDir, "SKILL.md"), "utf-8");
|
|
2978
3253
|
const readme = `# ${pkg.name}
|
|
2979
3254
|
|
|
2980
3255
|
${meta.description}
|
|
@@ -2988,7 +3263,7 @@ harness install ${meta.name}
|
|
|
2988
3263
|
---
|
|
2989
3264
|
|
|
2990
3265
|
${skillMdContent}`;
|
|
2991
|
-
|
|
3266
|
+
fs16.writeFileSync(readmePath, readme);
|
|
2992
3267
|
}
|
|
2993
3268
|
if (opts.dryRun) {
|
|
2994
3269
|
return {
|
|
@@ -3055,11 +3330,11 @@ import { Command as Command33 } from "commander";
|
|
|
3055
3330
|
|
|
3056
3331
|
// src/commands/state/show.ts
|
|
3057
3332
|
import { Command as Command29 } from "commander";
|
|
3058
|
-
import * as
|
|
3333
|
+
import * as path26 from "path";
|
|
3059
3334
|
function createShowCommand() {
|
|
3060
3335
|
return new Command29("show").description("Show current project state").option("--path <path>", "Project root path", ".").option("--stream <name>", "Target a specific stream").action(async (opts, cmd) => {
|
|
3061
3336
|
const globalOpts = cmd.optsWithGlobals();
|
|
3062
|
-
const projectPath =
|
|
3337
|
+
const projectPath = path26.resolve(opts.path);
|
|
3063
3338
|
const result = await loadState(projectPath, opts.stream);
|
|
3064
3339
|
if (!result.ok) {
|
|
3065
3340
|
logger.error(result.error.message);
|
|
@@ -3100,12 +3375,12 @@ Decisions: ${state.decisions.length}`);
|
|
|
3100
3375
|
|
|
3101
3376
|
// src/commands/state/reset.ts
|
|
3102
3377
|
import { Command as Command30 } from "commander";
|
|
3103
|
-
import * as
|
|
3104
|
-
import * as
|
|
3378
|
+
import * as fs17 from "fs";
|
|
3379
|
+
import * as path27 from "path";
|
|
3105
3380
|
import * as readline from "readline";
|
|
3106
3381
|
function createResetCommand() {
|
|
3107
3382
|
return new Command30("reset").description("Reset project state (deletes .harness/state.json)").option("--path <path>", "Project root path", ".").option("--stream <name>", "Target a specific stream").option("--yes", "Skip confirmation prompt").action(async (opts, _cmd) => {
|
|
3108
|
-
const projectPath =
|
|
3383
|
+
const projectPath = path27.resolve(opts.path);
|
|
3109
3384
|
let statePath;
|
|
3110
3385
|
if (opts.stream) {
|
|
3111
3386
|
const streamResult = await resolveStreamPath(projectPath, { stream: opts.stream });
|
|
@@ -3114,19 +3389,19 @@ function createResetCommand() {
|
|
|
3114
3389
|
process.exit(ExitCode.ERROR);
|
|
3115
3390
|
return;
|
|
3116
3391
|
}
|
|
3117
|
-
statePath =
|
|
3392
|
+
statePath = path27.join(streamResult.value, "state.json");
|
|
3118
3393
|
} else {
|
|
3119
|
-
statePath =
|
|
3394
|
+
statePath = path27.join(projectPath, ".harness", "state.json");
|
|
3120
3395
|
}
|
|
3121
|
-
if (!
|
|
3396
|
+
if (!fs17.existsSync(statePath)) {
|
|
3122
3397
|
logger.info("No state file found. Nothing to reset.");
|
|
3123
3398
|
process.exit(ExitCode.SUCCESS);
|
|
3124
3399
|
return;
|
|
3125
3400
|
}
|
|
3126
3401
|
if (!opts.yes) {
|
|
3127
3402
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
3128
|
-
const answer = await new Promise((
|
|
3129
|
-
rl.question("Reset project state? This cannot be undone. [y/N] ",
|
|
3403
|
+
const answer = await new Promise((resolve31) => {
|
|
3404
|
+
rl.question("Reset project state? This cannot be undone. [y/N] ", resolve31);
|
|
3130
3405
|
});
|
|
3131
3406
|
rl.close();
|
|
3132
3407
|
if (answer.toLowerCase() !== "y" && answer.toLowerCase() !== "yes") {
|
|
@@ -3136,7 +3411,7 @@ function createResetCommand() {
|
|
|
3136
3411
|
}
|
|
3137
3412
|
}
|
|
3138
3413
|
try {
|
|
3139
|
-
|
|
3414
|
+
fs17.unlinkSync(statePath);
|
|
3140
3415
|
logger.success("Project state reset.");
|
|
3141
3416
|
} catch (e) {
|
|
3142
3417
|
logger.error(`Failed to reset state: ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -3149,10 +3424,10 @@ function createResetCommand() {
|
|
|
3149
3424
|
|
|
3150
3425
|
// src/commands/state/learn.ts
|
|
3151
3426
|
import { Command as Command31 } from "commander";
|
|
3152
|
-
import * as
|
|
3427
|
+
import * as path28 from "path";
|
|
3153
3428
|
function createLearnCommand() {
|
|
3154
3429
|
return new Command31("learn").description("Append a learning to .harness/learnings.md").argument("<message>", "The learning to record").option("--path <path>", "Project root path", ".").option("--stream <name>", "Target a specific stream").action(async (message, opts, _cmd) => {
|
|
3155
|
-
const projectPath =
|
|
3430
|
+
const projectPath = path28.resolve(opts.path);
|
|
3156
3431
|
const result = await appendLearning(projectPath, message, void 0, void 0, opts.stream);
|
|
3157
3432
|
if (!result.ok) {
|
|
3158
3433
|
logger.error(result.error.message);
|
|
@@ -3166,12 +3441,12 @@ function createLearnCommand() {
|
|
|
3166
3441
|
|
|
3167
3442
|
// src/commands/state/streams.ts
|
|
3168
3443
|
import { Command as Command32 } from "commander";
|
|
3169
|
-
import * as
|
|
3444
|
+
import * as path29 from "path";
|
|
3170
3445
|
function createStreamsCommand() {
|
|
3171
3446
|
const command = new Command32("streams").description("Manage state streams");
|
|
3172
3447
|
command.command("list").description("List all known streams").option("--path <path>", "Project root path", ".").action(async (opts, cmd) => {
|
|
3173
3448
|
const globalOpts = cmd.optsWithGlobals();
|
|
3174
|
-
const projectPath =
|
|
3449
|
+
const projectPath = path29.resolve(opts.path);
|
|
3175
3450
|
const indexResult = await loadStreamIndex(projectPath);
|
|
3176
3451
|
const result = await listStreams(projectPath);
|
|
3177
3452
|
if (!result.ok) {
|
|
@@ -3195,7 +3470,7 @@ function createStreamsCommand() {
|
|
|
3195
3470
|
process.exit(ExitCode.SUCCESS);
|
|
3196
3471
|
});
|
|
3197
3472
|
command.command("create <name>").description("Create a new stream").option("--path <path>", "Project root path", ".").option("--branch <branch>", "Associate with a git branch").action(async (name, opts) => {
|
|
3198
|
-
const projectPath =
|
|
3473
|
+
const projectPath = path29.resolve(opts.path);
|
|
3199
3474
|
const result = await createStream(projectPath, name, opts.branch);
|
|
3200
3475
|
if (!result.ok) {
|
|
3201
3476
|
logger.error(result.error.message);
|
|
@@ -3206,7 +3481,7 @@ function createStreamsCommand() {
|
|
|
3206
3481
|
process.exit(ExitCode.SUCCESS);
|
|
3207
3482
|
});
|
|
3208
3483
|
command.command("archive <name>").description("Archive a stream").option("--path <path>", "Project root path", ".").action(async (name, opts) => {
|
|
3209
|
-
const projectPath =
|
|
3484
|
+
const projectPath = path29.resolve(opts.path);
|
|
3210
3485
|
const result = await archiveStream(projectPath, name);
|
|
3211
3486
|
if (!result.ok) {
|
|
3212
3487
|
logger.error(result.error.message);
|
|
@@ -3217,7 +3492,7 @@ function createStreamsCommand() {
|
|
|
3217
3492
|
process.exit(ExitCode.SUCCESS);
|
|
3218
3493
|
});
|
|
3219
3494
|
command.command("activate <name>").description("Set the active stream").option("--path <path>", "Project root path", ".").action(async (name, opts) => {
|
|
3220
|
-
const projectPath =
|
|
3495
|
+
const projectPath = path29.resolve(opts.path);
|
|
3221
3496
|
const result = await setActiveStream(projectPath, name);
|
|
3222
3497
|
if (!result.ok) {
|
|
3223
3498
|
logger.error(result.error.message);
|
|
@@ -3242,23 +3517,23 @@ function createStateCommand() {
|
|
|
3242
3517
|
|
|
3243
3518
|
// src/commands/setup.ts
|
|
3244
3519
|
import { Command as Command34 } from "commander";
|
|
3245
|
-
import * as
|
|
3520
|
+
import * as fs19 from "fs";
|
|
3246
3521
|
import * as os4 from "os";
|
|
3247
|
-
import * as
|
|
3522
|
+
import * as path31 from "path";
|
|
3248
3523
|
import chalk3 from "chalk";
|
|
3249
3524
|
|
|
3250
3525
|
// src/utils/first-run.ts
|
|
3251
|
-
import * as
|
|
3526
|
+
import * as fs18 from "fs";
|
|
3252
3527
|
import * as os3 from "os";
|
|
3253
|
-
import * as
|
|
3254
|
-
var HARNESS_DIR =
|
|
3255
|
-
var MARKER_FILE =
|
|
3528
|
+
import * as path30 from "path";
|
|
3529
|
+
var HARNESS_DIR = path30.join(os3.homedir(), ".harness");
|
|
3530
|
+
var MARKER_FILE = path30.join(HARNESS_DIR, ".setup-complete");
|
|
3256
3531
|
function isFirstRun() {
|
|
3257
|
-
return !
|
|
3532
|
+
return !fs18.existsSync(MARKER_FILE);
|
|
3258
3533
|
}
|
|
3259
3534
|
function markSetupComplete() {
|
|
3260
|
-
|
|
3261
|
-
|
|
3535
|
+
fs18.mkdirSync(HARNESS_DIR, { recursive: true });
|
|
3536
|
+
fs18.writeFileSync(MARKER_FILE, "", "utf-8");
|
|
3262
3537
|
}
|
|
3263
3538
|
function printFirstRunWelcome() {
|
|
3264
3539
|
try {
|
|
@@ -3281,6 +3556,74 @@ function checkNodeVersion() {
|
|
|
3281
3556
|
};
|
|
3282
3557
|
}
|
|
3283
3558
|
|
|
3559
|
+
// src/integrations/registry.ts
|
|
3560
|
+
var INTEGRATION_REGISTRY = [
|
|
3561
|
+
// --- Tier 0: zero-config (free, no API key) ---
|
|
3562
|
+
{
|
|
3563
|
+
name: "context7",
|
|
3564
|
+
displayName: "Context7",
|
|
3565
|
+
description: "Live version-pinned docs for 9,000+ libraries",
|
|
3566
|
+
tier: 0,
|
|
3567
|
+
mcpConfig: {
|
|
3568
|
+
command: "npx",
|
|
3569
|
+
args: ["-y", "@upstash/context7-mcp"]
|
|
3570
|
+
},
|
|
3571
|
+
platforms: ["claude-code", "gemini-cli"]
|
|
3572
|
+
},
|
|
3573
|
+
{
|
|
3574
|
+
name: "sequential-thinking",
|
|
3575
|
+
displayName: "Sequential Thinking",
|
|
3576
|
+
description: "Structured multi-step reasoning",
|
|
3577
|
+
tier: 0,
|
|
3578
|
+
mcpConfig: {
|
|
3579
|
+
command: "npx",
|
|
3580
|
+
args: ["-y", "@modelcontextprotocol/server-sequential-thinking"]
|
|
3581
|
+
},
|
|
3582
|
+
platforms: ["claude-code", "gemini-cli"]
|
|
3583
|
+
},
|
|
3584
|
+
{
|
|
3585
|
+
name: "playwright",
|
|
3586
|
+
displayName: "Playwright",
|
|
3587
|
+
description: "Browser automation for E2E testing",
|
|
3588
|
+
tier: 0,
|
|
3589
|
+
mcpConfig: {
|
|
3590
|
+
command: "npx",
|
|
3591
|
+
args: ["-y", "@playwright/mcp"]
|
|
3592
|
+
},
|
|
3593
|
+
platforms: ["claude-code", "gemini-cli"]
|
|
3594
|
+
},
|
|
3595
|
+
// --- Tier 1: API-key required ---
|
|
3596
|
+
{
|
|
3597
|
+
name: "perplexity",
|
|
3598
|
+
displayName: "Perplexity",
|
|
3599
|
+
description: "Real-time web search and deep research",
|
|
3600
|
+
tier: 1,
|
|
3601
|
+
envVar: "PERPLEXITY_API_KEY",
|
|
3602
|
+
mcpConfig: {
|
|
3603
|
+
command: "npx",
|
|
3604
|
+
args: ["-y", "perplexity-mcp"],
|
|
3605
|
+
env: { PERPLEXITY_API_KEY: "${PERPLEXITY_API_KEY}" }
|
|
3606
|
+
// harness-ignore SEC-SEC-002: env var placeholder, not a hardcoded secret
|
|
3607
|
+
},
|
|
3608
|
+
installHint: "Get an API key at https://perplexity.ai/settings/api",
|
|
3609
|
+
platforms: ["claude-code", "gemini-cli"]
|
|
3610
|
+
},
|
|
3611
|
+
{
|
|
3612
|
+
name: "augment-code",
|
|
3613
|
+
displayName: "Augment Code",
|
|
3614
|
+
description: "Semantic code search across codebase",
|
|
3615
|
+
tier: 1,
|
|
3616
|
+
envVar: "AUGMENT_SESSION_AUTH",
|
|
3617
|
+
mcpConfig: {
|
|
3618
|
+
command: "auggie",
|
|
3619
|
+
args: ["--mcp", "--mcp-auto-workspace"],
|
|
3620
|
+
env: { AUGMENT_SESSION_AUTH: "${AUGMENT_SESSION_AUTH}" }
|
|
3621
|
+
},
|
|
3622
|
+
installHint: "Install auggie CLI: npm i -g @augmentcode/auggie@latest && auggie login",
|
|
3623
|
+
platforms: ["claude-code", "gemini-cli"]
|
|
3624
|
+
}
|
|
3625
|
+
];
|
|
3626
|
+
|
|
3284
3627
|
// src/commands/setup.ts
|
|
3285
3628
|
function checkNodeVersion2() {
|
|
3286
3629
|
const result = checkNodeVersion();
|
|
@@ -3293,7 +3636,7 @@ function runSlashCommandGeneration() {
|
|
|
3293
3636
|
try {
|
|
3294
3637
|
const results = generateSlashCommands({
|
|
3295
3638
|
global: true,
|
|
3296
|
-
platforms: ["claude-code", "gemini-cli"],
|
|
3639
|
+
platforms: ["claude-code", "gemini-cli", "codex", "cursor"],
|
|
3297
3640
|
yes: true,
|
|
3298
3641
|
includeGlobal: false,
|
|
3299
3642
|
skillsDir: "",
|
|
@@ -3307,9 +3650,9 @@ function runSlashCommandGeneration() {
|
|
|
3307
3650
|
}
|
|
3308
3651
|
}
|
|
3309
3652
|
function detectClient(dirName) {
|
|
3310
|
-
return
|
|
3653
|
+
return fs19.existsSync(path31.join(os4.homedir(), dirName));
|
|
3311
3654
|
}
|
|
3312
|
-
function runMcpSetup(cwd) {
|
|
3655
|
+
async function runMcpSetup(cwd) {
|
|
3313
3656
|
const results = [];
|
|
3314
3657
|
const clients = [
|
|
3315
3658
|
{ name: "Claude Code", dir: ".claude", client: "claude", configTarget: ".mcp.json" },
|
|
@@ -3318,7 +3661,9 @@ function runMcpSetup(cwd) {
|
|
|
3318
3661
|
dir: ".gemini",
|
|
3319
3662
|
client: "gemini",
|
|
3320
3663
|
configTarget: ".gemini/settings.json"
|
|
3321
|
-
}
|
|
3664
|
+
},
|
|
3665
|
+
{ name: "Codex CLI", dir: ".codex", client: "codex", configTarget: ".codex/config.toml" },
|
|
3666
|
+
{ name: "Cursor", dir: ".cursor", client: "cursor", configTarget: ".cursor/mcp.json" }
|
|
3322
3667
|
];
|
|
3323
3668
|
for (const { name, dir, client, configTarget } of clients) {
|
|
3324
3669
|
if (!detectClient(dir)) {
|
|
@@ -3345,7 +3690,40 @@ function formatStep(result) {
|
|
|
3345
3690
|
const icon = result.status === "pass" ? chalk3.green("\u2713") : result.status === "warn" ? chalk3.yellow("\u26A0") : chalk3.red("\u2717");
|
|
3346
3691
|
return ` ${icon} ${result.message}`;
|
|
3347
3692
|
}
|
|
3348
|
-
function
|
|
3693
|
+
function configureTier0Integrations(cwd) {
|
|
3694
|
+
try {
|
|
3695
|
+
const mcpPath = path31.join(cwd, ".mcp.json");
|
|
3696
|
+
const config = readMcpConfig(mcpPath);
|
|
3697
|
+
const tier0 = INTEGRATION_REGISTRY.filter((i) => i.tier === 0);
|
|
3698
|
+
const added = [];
|
|
3699
|
+
for (const integration of tier0) {
|
|
3700
|
+
if (config.mcpServers[integration.name]) continue;
|
|
3701
|
+
writeMcpEntry(mcpPath, integration.name, integration.mcpConfig);
|
|
3702
|
+
added.push(integration.displayName);
|
|
3703
|
+
}
|
|
3704
|
+
const geminiDir = path31.join(cwd, ".gemini");
|
|
3705
|
+
if (fs19.existsSync(geminiDir)) {
|
|
3706
|
+
const geminiPath = path31.join(geminiDir, "settings.json");
|
|
3707
|
+
const geminiConfig = readMcpConfig(geminiPath);
|
|
3708
|
+
for (const integration of tier0) {
|
|
3709
|
+
if (!geminiConfig.mcpServers[integration.name]) {
|
|
3710
|
+
writeMcpEntry(geminiPath, integration.name, integration.mcpConfig);
|
|
3711
|
+
}
|
|
3712
|
+
}
|
|
3713
|
+
}
|
|
3714
|
+
if (added.length === 0) {
|
|
3715
|
+
return { status: "pass", message: "Tier 0 MCP integrations already configured" };
|
|
3716
|
+
}
|
|
3717
|
+
return {
|
|
3718
|
+
status: "pass",
|
|
3719
|
+
message: `Configured ${added.length} MCP integrations: ${added.join(", ")}`
|
|
3720
|
+
};
|
|
3721
|
+
} catch (error) {
|
|
3722
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
3723
|
+
return { status: "fail", message: `Tier 0 integration configuration failed \u2014 ${msg}` };
|
|
3724
|
+
}
|
|
3725
|
+
}
|
|
3726
|
+
async function runSetup(cwd) {
|
|
3349
3727
|
const steps = [];
|
|
3350
3728
|
const nodeResult = checkNodeVersion2();
|
|
3351
3729
|
steps.push(nodeResult);
|
|
@@ -3354,8 +3732,10 @@ function runSetup(cwd) {
|
|
|
3354
3732
|
}
|
|
3355
3733
|
const slashResult = runSlashCommandGeneration();
|
|
3356
3734
|
steps.push(slashResult);
|
|
3357
|
-
const mcpResults = runMcpSetup(cwd);
|
|
3735
|
+
const mcpResults = await runMcpSetup(cwd);
|
|
3358
3736
|
steps.push(...mcpResults);
|
|
3737
|
+
const tier0Result = configureTier0Integrations(cwd);
|
|
3738
|
+
steps.push(tier0Result);
|
|
3359
3739
|
const success = steps.every((s) => s.status !== "fail");
|
|
3360
3740
|
if (success) {
|
|
3361
3741
|
markSetupComplete();
|
|
@@ -3363,12 +3743,12 @@ function runSetup(cwd) {
|
|
|
3363
3743
|
return { steps, success };
|
|
3364
3744
|
}
|
|
3365
3745
|
function createSetupCommand() {
|
|
3366
|
-
return new Command34("setup").description("Configure harness environment: slash commands, MCP, and more").action(() => {
|
|
3746
|
+
return new Command34("setup").description("Configure harness environment: slash commands, MCP, and more").action(async () => {
|
|
3367
3747
|
const cwd = process.cwd();
|
|
3368
3748
|
console.log("");
|
|
3369
3749
|
console.log(` ${chalk3.bold("harness setup")}`);
|
|
3370
3750
|
console.log("");
|
|
3371
|
-
const { steps, success } = runSetup(cwd);
|
|
3751
|
+
const { steps, success } = await runSetup(cwd);
|
|
3372
3752
|
for (const step of steps) {
|
|
3373
3753
|
console.log(formatStep(step));
|
|
3374
3754
|
}
|
|
@@ -3386,9 +3766,9 @@ function createSetupCommand() {
|
|
|
3386
3766
|
|
|
3387
3767
|
// src/commands/doctor.ts
|
|
3388
3768
|
import { Command as Command35 } from "commander";
|
|
3389
|
-
import * as
|
|
3769
|
+
import * as fs20 from "fs";
|
|
3390
3770
|
import * as os5 from "os";
|
|
3391
|
-
import * as
|
|
3771
|
+
import * as path32 from "path";
|
|
3392
3772
|
import chalk4 from "chalk";
|
|
3393
3773
|
function checkNodeVersion3() {
|
|
3394
3774
|
const result = checkNodeVersion();
|
|
@@ -3408,7 +3788,7 @@ function checkNodeVersion3() {
|
|
|
3408
3788
|
}
|
|
3409
3789
|
function countCommandFiles(dir, ext) {
|
|
3410
3790
|
try {
|
|
3411
|
-
return
|
|
3791
|
+
return fs20.readdirSync(dir).filter((f) => f.endsWith(ext)).length;
|
|
3412
3792
|
} catch {
|
|
3413
3793
|
return 0;
|
|
3414
3794
|
}
|
|
@@ -3417,13 +3797,13 @@ function checkSlashCommands() {
|
|
|
3417
3797
|
const platforms = [
|
|
3418
3798
|
{
|
|
3419
3799
|
name: "Claude Code",
|
|
3420
|
-
dir:
|
|
3800
|
+
dir: path32.join(os5.homedir(), ".claude", "commands", "harness"),
|
|
3421
3801
|
ext: ".md",
|
|
3422
3802
|
client: "claude-code"
|
|
3423
3803
|
},
|
|
3424
3804
|
{
|
|
3425
3805
|
name: "Gemini CLI",
|
|
3426
|
-
dir:
|
|
3806
|
+
dir: path32.join(os5.homedir(), ".gemini", "commands", "harness"),
|
|
3427
3807
|
ext: ".toml",
|
|
3428
3808
|
client: "gemini-cli"
|
|
3429
3809
|
}
|
|
@@ -3445,19 +3825,10 @@ function checkSlashCommands() {
|
|
|
3445
3825
|
};
|
|
3446
3826
|
});
|
|
3447
3827
|
}
|
|
3448
|
-
function readJsonSafe(filePath) {
|
|
3449
|
-
try {
|
|
3450
|
-
if (!fs18.existsSync(filePath)) return null;
|
|
3451
|
-
return JSON.parse(fs18.readFileSync(filePath, "utf-8"));
|
|
3452
|
-
} catch {
|
|
3453
|
-
return null;
|
|
3454
|
-
}
|
|
3455
|
-
}
|
|
3456
3828
|
function checkMcpConfig(cwd) {
|
|
3457
3829
|
const results = [];
|
|
3458
|
-
const
|
|
3459
|
-
|
|
3460
|
-
if (claudeConfig?.mcpServers?.["harness"]) {
|
|
3830
|
+
const claudeConfig = readMcpConfig(path32.join(cwd, ".mcp.json"));
|
|
3831
|
+
if (claudeConfig.mcpServers?.["harness"]) {
|
|
3461
3832
|
results.push({
|
|
3462
3833
|
name: "mcp-claude",
|
|
3463
3834
|
status: "pass",
|
|
@@ -3471,40 +3842,112 @@ function checkMcpConfig(cwd) {
|
|
|
3471
3842
|
fix: "Run: harness setup-mcp --client claude"
|
|
3472
3843
|
});
|
|
3473
3844
|
}
|
|
3474
|
-
const
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3845
|
+
const geminiDir = path32.join(cwd, ".gemini");
|
|
3846
|
+
if (fs20.existsSync(geminiDir)) {
|
|
3847
|
+
const geminiConfig = readMcpConfig(path32.join(geminiDir, "settings.json"));
|
|
3848
|
+
if (geminiConfig.mcpServers?.["harness"]) {
|
|
3849
|
+
results.push({
|
|
3850
|
+
name: "mcp-gemini",
|
|
3851
|
+
status: "pass",
|
|
3852
|
+
message: "MCP configured for Gemini CLI"
|
|
3853
|
+
});
|
|
3854
|
+
} else {
|
|
3855
|
+
results.push({
|
|
3856
|
+
name: "mcp-gemini",
|
|
3857
|
+
status: "fail",
|
|
3858
|
+
message: "MCP not configured for Gemini CLI",
|
|
3859
|
+
fix: "Run: harness setup-mcp --client gemini"
|
|
3860
|
+
});
|
|
3861
|
+
}
|
|
3489
3862
|
}
|
|
3490
3863
|
return results;
|
|
3491
3864
|
}
|
|
3492
|
-
function
|
|
3493
|
-
const
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3865
|
+
function loadMcpPresence(cwd) {
|
|
3866
|
+
const mcpPath = path32.join(cwd, ".mcp.json");
|
|
3867
|
+
const geminiDir = path32.join(cwd, ".gemini");
|
|
3868
|
+
const hasGemini = fs20.existsSync(geminiDir);
|
|
3869
|
+
return {
|
|
3870
|
+
mcpConfig: readMcpConfig(mcpPath),
|
|
3871
|
+
geminiConfig: hasGemini ? readMcpConfig(path32.join(geminiDir, "settings.json")) : null,
|
|
3872
|
+
hasGemini
|
|
3873
|
+
};
|
|
3874
|
+
}
|
|
3875
|
+
function checkTier0Presence(def, presence) {
|
|
3876
|
+
const inClaude = !!presence.mcpConfig.mcpServers?.[def.name];
|
|
3877
|
+
const inGemini = !!presence.geminiConfig?.mcpServers?.[def.name];
|
|
3878
|
+
if (!inClaude) {
|
|
3879
|
+
return {
|
|
3880
|
+
name: `integration-${def.name}`,
|
|
3881
|
+
status: "fail",
|
|
3882
|
+
message: `${def.displayName} not configured. Run \`harness setup\` to fix.`,
|
|
3883
|
+
fix: "Run: harness setup"
|
|
3884
|
+
};
|
|
3885
|
+
}
|
|
3886
|
+
if (presence.hasGemini && !inGemini) {
|
|
3887
|
+
return {
|
|
3888
|
+
name: `integration-${def.name}`,
|
|
3889
|
+
status: "warn",
|
|
3890
|
+
message: `${def.displayName} missing from Gemini CLI config. Run \`harness setup\` to fix.`,
|
|
3891
|
+
fix: "Run: harness setup"
|
|
3892
|
+
};
|
|
3893
|
+
}
|
|
3894
|
+
return {
|
|
3895
|
+
name: `integration-${def.name}`,
|
|
3896
|
+
status: "pass",
|
|
3897
|
+
message: `${def.displayName} configured`
|
|
3898
|
+
};
|
|
3899
|
+
}
|
|
3900
|
+
function checkIntegrations(cwd) {
|
|
3901
|
+
const results = [];
|
|
3902
|
+
const presence = loadMcpPresence(cwd);
|
|
3903
|
+
const configPath = path32.join(cwd, "harness.config.json");
|
|
3904
|
+
const integrationsConfig = readIntegrationsConfig(configPath);
|
|
3905
|
+
for (const def of INTEGRATION_REGISTRY.filter((d) => d.tier === 0)) {
|
|
3906
|
+
results.push(checkTier0Presence(def, presence));
|
|
3907
|
+
}
|
|
3908
|
+
for (const def of INTEGRATION_REGISTRY.filter((d) => d.tier === 1)) {
|
|
3909
|
+
const enabled = integrationsConfig.enabled.includes(def.name);
|
|
3910
|
+
const dismissed = integrationsConfig.dismissed.includes(def.name);
|
|
3911
|
+
if (enabled && def.envVar && !process.env[def.envVar]) {
|
|
3912
|
+
results.push({
|
|
3913
|
+
name: `integration-${def.name}-env`,
|
|
3914
|
+
status: "warn",
|
|
3915
|
+
message: `${def.displayName} enabled but ${def.envVar} not set.`,
|
|
3916
|
+
...def.installHint !== void 0 && { fix: def.installHint }
|
|
3917
|
+
});
|
|
3918
|
+
} else if (!enabled && !dismissed) {
|
|
3919
|
+
results.push({
|
|
3920
|
+
name: `integration-${def.name}`,
|
|
3921
|
+
status: "info",
|
|
3922
|
+
message: `${def.displayName} enables ${def.description.toLowerCase()}. Run \`harness integrations add ${def.name}\`.`
|
|
3923
|
+
});
|
|
3924
|
+
}
|
|
3925
|
+
}
|
|
3926
|
+
return results;
|
|
3927
|
+
}
|
|
3928
|
+
function runDoctor(cwd) {
|
|
3929
|
+
const checks = [];
|
|
3930
|
+
checks.push(checkNodeVersion3());
|
|
3931
|
+
checks.push(...checkSlashCommands());
|
|
3932
|
+
checks.push(...checkMcpConfig(cwd));
|
|
3933
|
+
checks.push(...checkIntegrations(cwd));
|
|
3934
|
+
const allPassed = checks.every((c) => c.status !== "fail");
|
|
3935
|
+
return { checks, allPassed };
|
|
3936
|
+
}
|
|
3937
|
+
function formatCheck(check) {
|
|
3938
|
+
const icons = {
|
|
3939
|
+
pass: chalk4.green("\u2713"),
|
|
3940
|
+
fail: chalk4.red("\u2717"),
|
|
3941
|
+
warn: chalk4.yellow("!"),
|
|
3942
|
+
info: chalk4.blue("\u2139")
|
|
3943
|
+
};
|
|
3944
|
+
const icon = icons[check.status];
|
|
3945
|
+
let line = ` ${icon} ${check.message}`;
|
|
3946
|
+
if ((check.status === "fail" || check.status === "warn") && check.fix) {
|
|
3947
|
+
line += `
|
|
3948
|
+
-> ${check.fix}`;
|
|
3949
|
+
}
|
|
3950
|
+
return line;
|
|
3508
3951
|
}
|
|
3509
3952
|
function createDoctorCommand() {
|
|
3510
3953
|
return new Command35("doctor").description("Check environment health: Node version, slash commands, MCP configuration").action((_opts, cmd) => {
|
|
@@ -3620,8 +4063,8 @@ function createCheckCommand() {
|
|
|
3620
4063
|
|
|
3621
4064
|
// src/commands/ci/init.ts
|
|
3622
4065
|
import { Command as Command37 } from "commander";
|
|
3623
|
-
import * as
|
|
3624
|
-
import * as
|
|
4066
|
+
import * as fs21 from "fs";
|
|
4067
|
+
import * as path33 from "path";
|
|
3625
4068
|
var ALL_CHECKS = [
|
|
3626
4069
|
"validate",
|
|
3627
4070
|
"deps",
|
|
@@ -3722,8 +4165,8 @@ function generateCIConfig(options) {
|
|
|
3722
4165
|
});
|
|
3723
4166
|
}
|
|
3724
4167
|
function detectPlatform() {
|
|
3725
|
-
if (
|
|
3726
|
-
if (
|
|
4168
|
+
if (fs21.existsSync(".github")) return "github";
|
|
4169
|
+
if (fs21.existsSync(".gitlab-ci.yml")) return "gitlab";
|
|
3727
4170
|
return null;
|
|
3728
4171
|
}
|
|
3729
4172
|
function createInitCommand2() {
|
|
@@ -3739,12 +4182,12 @@ function createInitCommand2() {
|
|
|
3739
4182
|
process.exit(result.error.exitCode);
|
|
3740
4183
|
}
|
|
3741
4184
|
const { filename, content } = result.value;
|
|
3742
|
-
const targetPath =
|
|
3743
|
-
const dir =
|
|
3744
|
-
|
|
3745
|
-
|
|
4185
|
+
const targetPath = path33.resolve(filename);
|
|
4186
|
+
const dir = path33.dirname(targetPath);
|
|
4187
|
+
fs21.mkdirSync(dir, { recursive: true });
|
|
4188
|
+
fs21.writeFileSync(targetPath, content);
|
|
3746
4189
|
if (platform === "generic" && process.platform !== "win32") {
|
|
3747
|
-
|
|
4190
|
+
fs21.chmodSync(targetPath, "755");
|
|
3748
4191
|
}
|
|
3749
4192
|
if (globalOpts.json) {
|
|
3750
4193
|
console.log(JSON.stringify({ file: filename, platform }));
|
|
@@ -3764,12 +4207,12 @@ function createCICommand() {
|
|
|
3764
4207
|
}
|
|
3765
4208
|
|
|
3766
4209
|
// src/commands/hooks/index.ts
|
|
3767
|
-
import { Command as
|
|
4210
|
+
import { Command as Command43 } from "commander";
|
|
3768
4211
|
|
|
3769
4212
|
// src/commands/hooks/init.ts
|
|
3770
4213
|
import { Command as Command39 } from "commander";
|
|
3771
|
-
import * as
|
|
3772
|
-
import * as
|
|
4214
|
+
import * as fs22 from "fs";
|
|
4215
|
+
import * as path34 from "path";
|
|
3773
4216
|
import { fileURLToPath } from "url";
|
|
3774
4217
|
|
|
3775
4218
|
// src/hooks/profiles.ts
|
|
@@ -3778,7 +4221,9 @@ var HOOK_SCRIPTS = [
|
|
|
3778
4221
|
{ name: "protect-config", event: "PreToolUse", matcher: "Write|Edit", minProfile: "standard" },
|
|
3779
4222
|
{ name: "quality-gate", event: "PostToolUse", matcher: "Edit|Write", minProfile: "standard" },
|
|
3780
4223
|
{ name: "pre-compact-state", event: "PreCompact", matcher: "*", minProfile: "standard" },
|
|
3781
|
-
{ name: "cost-tracker", event: "Stop", matcher: "*", minProfile: "strict" }
|
|
4224
|
+
{ name: "cost-tracker", event: "Stop", matcher: "*", minProfile: "strict" },
|
|
4225
|
+
{ name: "sentinel-pre", event: "PreToolUse", matcher: "*", minProfile: "strict" },
|
|
4226
|
+
{ name: "sentinel-post", event: "PostToolUse", matcher: "*", minProfile: "strict" }
|
|
3782
4227
|
];
|
|
3783
4228
|
var PROFILE_ORDER = ["minimal", "standard", "strict"];
|
|
3784
4229
|
function hooksForProfile(profile) {
|
|
@@ -3795,11 +4240,11 @@ var PROFILES = {
|
|
|
3795
4240
|
|
|
3796
4241
|
// src/commands/hooks/init.ts
|
|
3797
4242
|
var __filename = fileURLToPath(import.meta.url);
|
|
3798
|
-
var __dirname =
|
|
4243
|
+
var __dirname = path34.dirname(__filename);
|
|
3799
4244
|
var VALID_PROFILES = ["minimal", "standard", "strict"];
|
|
3800
4245
|
function resolveHookSourceDir() {
|
|
3801
|
-
const candidate =
|
|
3802
|
-
if (
|
|
4246
|
+
const candidate = path34.resolve(__dirname, "..", "..", "hooks");
|
|
4247
|
+
if (fs22.existsSync(candidate)) {
|
|
3803
4248
|
return candidate;
|
|
3804
4249
|
}
|
|
3805
4250
|
throw new Error(`Cannot locate hook scripts directory. Expected at: ${candidate}`);
|
|
@@ -3827,12 +4272,12 @@ function mergeSettings(existing, hooksConfig) {
|
|
|
3827
4272
|
}
|
|
3828
4273
|
function initHooks(options) {
|
|
3829
4274
|
const { profile, projectDir } = options;
|
|
3830
|
-
const hooksDestDir =
|
|
3831
|
-
|
|
3832
|
-
if (
|
|
3833
|
-
for (const entry of
|
|
4275
|
+
const hooksDestDir = path34.join(projectDir, ".harness", "hooks");
|
|
4276
|
+
fs22.mkdirSync(hooksDestDir, { recursive: true });
|
|
4277
|
+
if (fs22.existsSync(hooksDestDir)) {
|
|
4278
|
+
for (const entry of fs22.readdirSync(hooksDestDir)) {
|
|
3834
4279
|
if (entry.endsWith(".js")) {
|
|
3835
|
-
|
|
4280
|
+
fs22.unlinkSync(path34.join(hooksDestDir, entry));
|
|
3836
4281
|
}
|
|
3837
4282
|
}
|
|
3838
4283
|
}
|
|
@@ -3841,22 +4286,22 @@ function initHooks(options) {
|
|
|
3841
4286
|
const activeNames = PROFILES[profile];
|
|
3842
4287
|
const activeScripts = HOOK_SCRIPTS.filter((h) => activeNames.includes(h.name));
|
|
3843
4288
|
for (const script of activeScripts) {
|
|
3844
|
-
const srcFile =
|
|
3845
|
-
const destFile =
|
|
3846
|
-
if (
|
|
3847
|
-
|
|
4289
|
+
const srcFile = path34.join(sourceDir, `${script.name}.js`);
|
|
4290
|
+
const destFile = path34.join(hooksDestDir, `${script.name}.js`);
|
|
4291
|
+
if (fs22.existsSync(srcFile)) {
|
|
4292
|
+
fs22.copyFileSync(srcFile, destFile);
|
|
3848
4293
|
copiedScripts.push(script.name);
|
|
3849
4294
|
}
|
|
3850
4295
|
}
|
|
3851
|
-
const profilePath =
|
|
3852
|
-
|
|
3853
|
-
const claudeDir =
|
|
3854
|
-
|
|
3855
|
-
const settingsPath =
|
|
4296
|
+
const profilePath = path34.join(hooksDestDir, "profile.json");
|
|
4297
|
+
fs22.writeFileSync(profilePath, JSON.stringify({ profile }, null, 2) + "\n");
|
|
4298
|
+
const claudeDir = path34.join(projectDir, ".claude");
|
|
4299
|
+
fs22.mkdirSync(claudeDir, { recursive: true });
|
|
4300
|
+
const settingsPath = path34.join(claudeDir, "settings.json");
|
|
3856
4301
|
let existing = {};
|
|
3857
|
-
if (
|
|
4302
|
+
if (fs22.existsSync(settingsPath)) {
|
|
3858
4303
|
try {
|
|
3859
|
-
existing = JSON.parse(
|
|
4304
|
+
existing = JSON.parse(fs22.readFileSync(settingsPath, "utf-8"));
|
|
3860
4305
|
} catch (e) {
|
|
3861
4306
|
throw new Error(
|
|
3862
4307
|
`Malformed .claude/settings.json \u2014 fix the JSON syntax before running hooks init. Parse error: ${e instanceof Error ? e.message : String(e)}`,
|
|
@@ -3866,7 +4311,7 @@ function initHooks(options) {
|
|
|
3866
4311
|
}
|
|
3867
4312
|
const hooksConfig = buildSettingsHooks(profile);
|
|
3868
4313
|
const merged = mergeSettings(existing, hooksConfig);
|
|
3869
|
-
|
|
4314
|
+
fs22.writeFileSync(settingsPath, JSON.stringify(merged, null, 2) + "\n");
|
|
3870
4315
|
return { copiedScripts, settingsPath, profilePath };
|
|
3871
4316
|
}
|
|
3872
4317
|
function createInitCommand3() {
|
|
@@ -3894,7 +4339,7 @@ function createInitCommand3() {
|
|
|
3894
4339
|
`Installed ${result.copiedScripts.length} hook scripts to .harness/hooks/`
|
|
3895
4340
|
);
|
|
3896
4341
|
logger.info(`Profile: ${profile}`);
|
|
3897
|
-
logger.info(`Settings: ${
|
|
4342
|
+
logger.info(`Settings: ${path34.relative(projectDir, result.settingsPath)}`);
|
|
3898
4343
|
logger.dim("Run 'harness hooks list' to see installed hooks");
|
|
3899
4344
|
}
|
|
3900
4345
|
} catch (err) {
|
|
@@ -3907,18 +4352,18 @@ function createInitCommand3() {
|
|
|
3907
4352
|
|
|
3908
4353
|
// src/commands/hooks/list.ts
|
|
3909
4354
|
import { Command as Command40 } from "commander";
|
|
3910
|
-
import * as
|
|
3911
|
-
import * as
|
|
4355
|
+
import * as fs23 from "fs";
|
|
4356
|
+
import * as path35 from "path";
|
|
3912
4357
|
function listHooks(projectDir) {
|
|
3913
|
-
const hooksDir =
|
|
3914
|
-
const profilePath =
|
|
3915
|
-
if (!
|
|
4358
|
+
const hooksDir = path35.join(projectDir, ".harness", "hooks");
|
|
4359
|
+
const profilePath = path35.join(hooksDir, "profile.json");
|
|
4360
|
+
if (!fs23.existsSync(profilePath)) {
|
|
3916
4361
|
return { installed: false, profile: null, hooks: [] };
|
|
3917
4362
|
}
|
|
3918
4363
|
let profile = "standard";
|
|
3919
4364
|
let warning;
|
|
3920
4365
|
try {
|
|
3921
|
-
const data = JSON.parse(
|
|
4366
|
+
const data = JSON.parse(fs23.readFileSync(profilePath, "utf-8"));
|
|
3922
4367
|
if (data.profile && ["minimal", "standard", "strict"].includes(data.profile)) {
|
|
3923
4368
|
profile = data.profile;
|
|
3924
4369
|
}
|
|
@@ -3930,7 +4375,7 @@ function listHooks(projectDir) {
|
|
|
3930
4375
|
name: h.name,
|
|
3931
4376
|
event: h.event,
|
|
3932
4377
|
matcher: h.matcher,
|
|
3933
|
-
scriptPath:
|
|
4378
|
+
scriptPath: path35.join(".harness", "hooks", `${h.name}.js`)
|
|
3934
4379
|
}));
|
|
3935
4380
|
const result = { installed: true, profile, hooks };
|
|
3936
4381
|
if (warning) {
|
|
@@ -3961,27 +4406,27 @@ function createListCommand3() {
|
|
|
3961
4406
|
|
|
3962
4407
|
// src/commands/hooks/remove.ts
|
|
3963
4408
|
import { Command as Command41 } from "commander";
|
|
3964
|
-
import * as
|
|
3965
|
-
import * as
|
|
4409
|
+
import * as fs24 from "fs";
|
|
4410
|
+
import * as path36 from "path";
|
|
3966
4411
|
function removeHooks(projectDir) {
|
|
3967
|
-
const hooksDir =
|
|
3968
|
-
const settingsPath =
|
|
4412
|
+
const hooksDir = path36.join(projectDir, ".harness", "hooks");
|
|
4413
|
+
const settingsPath = path36.join(projectDir, ".claude", "settings.json");
|
|
3969
4414
|
let removed = false;
|
|
3970
4415
|
let settingsCleaned = false;
|
|
3971
|
-
if (
|
|
3972
|
-
|
|
4416
|
+
if (fs24.existsSync(hooksDir)) {
|
|
4417
|
+
fs24.rmSync(hooksDir, { recursive: true, force: true });
|
|
3973
4418
|
removed = true;
|
|
3974
4419
|
}
|
|
3975
|
-
if (
|
|
4420
|
+
if (fs24.existsSync(settingsPath)) {
|
|
3976
4421
|
try {
|
|
3977
|
-
const settings = JSON.parse(
|
|
4422
|
+
const settings = JSON.parse(fs24.readFileSync(settingsPath, "utf-8"));
|
|
3978
4423
|
if (settings.hooks !== void 0) {
|
|
3979
4424
|
delete settings.hooks;
|
|
3980
4425
|
settingsCleaned = true;
|
|
3981
4426
|
if (Object.keys(settings).length === 0) {
|
|
3982
|
-
|
|
4427
|
+
fs24.unlinkSync(settingsPath);
|
|
3983
4428
|
} else {
|
|
3984
|
-
|
|
4429
|
+
fs24.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
3985
4430
|
}
|
|
3986
4431
|
}
|
|
3987
4432
|
} catch {
|
|
@@ -4011,17 +4456,99 @@ function createRemoveCommand() {
|
|
|
4011
4456
|
});
|
|
4012
4457
|
}
|
|
4013
4458
|
|
|
4459
|
+
// src/commands/hooks/add.ts
|
|
4460
|
+
import { Command as Command42 } from "commander";
|
|
4461
|
+
import * as fs25 from "fs";
|
|
4462
|
+
import * as path37 from "path";
|
|
4463
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4464
|
+
var __dirname2 = path37.dirname(fileURLToPath2(import.meta.url));
|
|
4465
|
+
var ALIASES = {
|
|
4466
|
+
sentinel: ["sentinel-pre", "sentinel-post"]
|
|
4467
|
+
};
|
|
4468
|
+
function hookSourceDir() {
|
|
4469
|
+
const d = path37.resolve(__dirname2, "..", "..", "hooks");
|
|
4470
|
+
if (fs25.existsSync(d)) return d;
|
|
4471
|
+
throw new Error(`Hook scripts not found: ${d}`);
|
|
4472
|
+
}
|
|
4473
|
+
function readJson(p) {
|
|
4474
|
+
return fs25.existsSync(p) ? JSON.parse(fs25.readFileSync(p, "utf-8")) : {};
|
|
4475
|
+
}
|
|
4476
|
+
function registerHook(s, ev, matcher, name) {
|
|
4477
|
+
if (!s.hooks[ev]) s.hooks[ev] = [];
|
|
4478
|
+
const cmd = `node .harness/hooks/${name}.js`;
|
|
4479
|
+
if (!s.hooks[ev].some((e) => e.hooks?.some((h) => h.command === cmd))) {
|
|
4480
|
+
s.hooks[ev].push({ matcher, hooks: [{ type: "command", command: cmd }] });
|
|
4481
|
+
}
|
|
4482
|
+
}
|
|
4483
|
+
function addHooks(hookName, projectDir) {
|
|
4484
|
+
const names = ALIASES[hookName] ?? [hookName];
|
|
4485
|
+
const result = { added: [], alreadyInstalled: [], notFound: [] };
|
|
4486
|
+
const srcDir = hookSourceDir();
|
|
4487
|
+
const destDir = path37.join(projectDir, ".harness", "hooks");
|
|
4488
|
+
fs25.mkdirSync(destDir, { recursive: true });
|
|
4489
|
+
const claudeDir = path37.join(projectDir, ".claude");
|
|
4490
|
+
fs25.mkdirSync(claudeDir, { recursive: true });
|
|
4491
|
+
const settingsPath = path37.join(claudeDir, "settings.json");
|
|
4492
|
+
const settings = readJson(settingsPath);
|
|
4493
|
+
if (!settings.hooks) settings.hooks = {};
|
|
4494
|
+
for (const name of names) {
|
|
4495
|
+
const def = HOOK_SCRIPTS.find((h) => h.name === name);
|
|
4496
|
+
if (!def) {
|
|
4497
|
+
result.notFound.push(name);
|
|
4498
|
+
continue;
|
|
4499
|
+
}
|
|
4500
|
+
const src = path37.join(srcDir, `${name}.js`);
|
|
4501
|
+
const dest = path37.join(destDir, `${name}.js`);
|
|
4502
|
+
if (fs25.existsSync(dest)) {
|
|
4503
|
+
result.alreadyInstalled.push(name);
|
|
4504
|
+
} else if (!fs25.existsSync(src)) {
|
|
4505
|
+
result.notFound.push(name);
|
|
4506
|
+
continue;
|
|
4507
|
+
} else {
|
|
4508
|
+
fs25.copyFileSync(src, dest);
|
|
4509
|
+
result.added.push(name);
|
|
4510
|
+
}
|
|
4511
|
+
registerHook(settings, def.event, def.matcher, name);
|
|
4512
|
+
}
|
|
4513
|
+
fs25.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
4514
|
+
return result;
|
|
4515
|
+
}
|
|
4516
|
+
function createAddCommand2() {
|
|
4517
|
+
return new Command42("add").argument("<hook-name>", "Hook name or alias (e.g., sentinel)").description("Add a hook without changing the profile").action(async (hookName, _opts, cmd) => {
|
|
4518
|
+
const projectDir = process.cwd();
|
|
4519
|
+
try {
|
|
4520
|
+
const res = addHooks(hookName, projectDir);
|
|
4521
|
+
if (cmd.optsWithGlobals().json) {
|
|
4522
|
+
console.log(JSON.stringify(res));
|
|
4523
|
+
return;
|
|
4524
|
+
}
|
|
4525
|
+
if (res.notFound.length > 0) {
|
|
4526
|
+
logger.error(`Unknown hook(s): ${res.notFound.join(", ")}`);
|
|
4527
|
+
logger.info(`Available: ${HOOK_SCRIPTS.map((h) => h.name).join(", ")}`);
|
|
4528
|
+
logger.info(`Aliases: ${Object.keys(ALIASES).join(", ")}`);
|
|
4529
|
+
process.exit(2);
|
|
4530
|
+
}
|
|
4531
|
+
for (const n of res.added) logger.success(`Added hook: ${n}`);
|
|
4532
|
+
for (const n of res.alreadyInstalled) logger.info(`Already installed: ${n}`);
|
|
4533
|
+
} catch (err) {
|
|
4534
|
+
logger.error(`Failed to add hook: ${err instanceof Error ? err.message : String(err)}`);
|
|
4535
|
+
process.exit(2);
|
|
4536
|
+
}
|
|
4537
|
+
});
|
|
4538
|
+
}
|
|
4539
|
+
|
|
4014
4540
|
// src/commands/hooks/index.ts
|
|
4015
4541
|
function createHooksCommand() {
|
|
4016
|
-
const command = new
|
|
4542
|
+
const command = new Command43("hooks").description("Manage Claude Code hook configurations");
|
|
4017
4543
|
command.addCommand(createInitCommand3());
|
|
4018
4544
|
command.addCommand(createListCommand3());
|
|
4019
4545
|
command.addCommand(createRemoveCommand());
|
|
4546
|
+
command.addCommand(createAddCommand2());
|
|
4020
4547
|
return command;
|
|
4021
4548
|
}
|
|
4022
4549
|
|
|
4023
4550
|
// src/commands/update.ts
|
|
4024
|
-
import { Command as
|
|
4551
|
+
import { Command as Command44 } from "commander";
|
|
4025
4552
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
4026
4553
|
import { realpathSync } from "fs";
|
|
4027
4554
|
import readline2 from "readline";
|
|
@@ -4081,10 +4608,10 @@ function prompt(question) {
|
|
|
4081
4608
|
input: process.stdin,
|
|
4082
4609
|
output: process.stdout
|
|
4083
4610
|
});
|
|
4084
|
-
return new Promise((
|
|
4611
|
+
return new Promise((resolve31) => {
|
|
4085
4612
|
rl.question(question, (answer) => {
|
|
4086
4613
|
rl.close();
|
|
4087
|
-
|
|
4614
|
+
resolve31(answer.trim().toLowerCase());
|
|
4088
4615
|
});
|
|
4089
4616
|
});
|
|
4090
4617
|
}
|
|
@@ -4104,7 +4631,7 @@ async function offerRegeneration() {
|
|
|
4104
4631
|
}
|
|
4105
4632
|
}
|
|
4106
4633
|
function createUpdateCommand() {
|
|
4107
|
-
return new
|
|
4634
|
+
return new Command44("update").description("Update all @harness-engineering packages to the latest version").option("--version <semver>", "Pin @harness-engineering/cli to a specific version").action(async (opts, cmd) => {
|
|
4108
4635
|
const globalOpts = cmd.optsWithGlobals();
|
|
4109
4636
|
const pm = detectPackageManager();
|
|
4110
4637
|
if (globalOpts.verbose) {
|
|
@@ -4162,9 +4689,9 @@ function createUpdateCommand() {
|
|
|
4162
4689
|
}
|
|
4163
4690
|
|
|
4164
4691
|
// src/commands/generate.ts
|
|
4165
|
-
import { Command as
|
|
4692
|
+
import { Command as Command45 } from "commander";
|
|
4166
4693
|
function createGenerateCommand3() {
|
|
4167
|
-
return new
|
|
4694
|
+
return new Command45("generate").description("Generate all platform integrations (slash commands + agent definitions)").option("--platforms <list>", "Target platforms (comma-separated)", "claude-code,gemini-cli").option("--global", "Write to global directories", false).option("--include-global", "Include built-in global skills", false).option("--output <dir>", "Custom output directory").option("--dry-run", "Show what would change without writing", false).option("--yes", "Skip deletion confirmation prompts", false).action(async (opts, cmd) => {
|
|
4168
4695
|
const globalOpts = cmd.optsWithGlobals();
|
|
4169
4696
|
const platforms = opts.platforms.split(",").map((p) => p.trim());
|
|
4170
4697
|
for (const p of platforms) {
|
|
@@ -4223,10 +4750,10 @@ function createGenerateCommand3() {
|
|
|
4223
4750
|
}
|
|
4224
4751
|
|
|
4225
4752
|
// src/commands/graph/scan.ts
|
|
4226
|
-
import { Command as
|
|
4227
|
-
import * as
|
|
4753
|
+
import { Command as Command46 } from "commander";
|
|
4754
|
+
import * as path38 from "path";
|
|
4228
4755
|
async function runScan(projectPath) {
|
|
4229
|
-
const { GraphStore, CodeIngestor, TopologicalLinker, KnowledgeIngestor, GitIngestor } = await import("./dist-
|
|
4756
|
+
const { GraphStore, CodeIngestor, TopologicalLinker, KnowledgeIngestor, GitIngestor } = await import("./dist-U7EAO6T2.js");
|
|
4230
4757
|
const store = new GraphStore();
|
|
4231
4758
|
const start = Date.now();
|
|
4232
4759
|
await new CodeIngestor(store).ingest(projectPath);
|
|
@@ -4237,13 +4764,13 @@ async function runScan(projectPath) {
|
|
|
4237
4764
|
await new GitIngestor(store).ingest(projectPath);
|
|
4238
4765
|
} catch {
|
|
4239
4766
|
}
|
|
4240
|
-
const graphDir =
|
|
4767
|
+
const graphDir = path38.join(projectPath, ".harness", "graph");
|
|
4241
4768
|
await store.save(graphDir);
|
|
4242
4769
|
return { nodeCount: store.nodeCount, edgeCount: store.edgeCount, durationMs: Date.now() - start };
|
|
4243
4770
|
}
|
|
4244
4771
|
function createScanCommand() {
|
|
4245
|
-
return new
|
|
4246
|
-
const projectPath =
|
|
4772
|
+
return new Command46("scan").description("Scan project and build knowledge graph").argument("[path]", "Project root path", ".").action(async (inputPath, _opts, cmd) => {
|
|
4773
|
+
const projectPath = path38.resolve(inputPath);
|
|
4247
4774
|
const globalOpts = cmd.optsWithGlobals();
|
|
4248
4775
|
try {
|
|
4249
4776
|
const result = await runScan(projectPath);
|
|
@@ -4262,13 +4789,13 @@ function createScanCommand() {
|
|
|
4262
4789
|
}
|
|
4263
4790
|
|
|
4264
4791
|
// src/commands/graph/ingest.ts
|
|
4265
|
-
import { Command as
|
|
4266
|
-
import * as
|
|
4792
|
+
import { Command as Command47 } from "commander";
|
|
4793
|
+
import * as path39 from "path";
|
|
4267
4794
|
async function loadConnectorConfig(projectPath, source) {
|
|
4268
4795
|
try {
|
|
4269
|
-
const
|
|
4270
|
-
const configPath =
|
|
4271
|
-
const config = JSON.parse(await
|
|
4796
|
+
const fs34 = await import("fs/promises");
|
|
4797
|
+
const configPath = path39.join(projectPath, "harness.config.json");
|
|
4798
|
+
const config = JSON.parse(await fs34.readFile(configPath, "utf-8"));
|
|
4272
4799
|
const connector = config.graph?.connectors?.find(
|
|
4273
4800
|
(c) => c.source === source
|
|
4274
4801
|
);
|
|
@@ -4307,8 +4834,8 @@ async function runIngest(projectPath, source, opts) {
|
|
|
4307
4834
|
SyncManager,
|
|
4308
4835
|
JiraConnector,
|
|
4309
4836
|
SlackConnector
|
|
4310
|
-
} = await import("./dist-
|
|
4311
|
-
const graphDir =
|
|
4837
|
+
} = await import("./dist-U7EAO6T2.js");
|
|
4838
|
+
const graphDir = path39.join(projectPath, ".harness", "graph");
|
|
4312
4839
|
const store = new GraphStore();
|
|
4313
4840
|
await store.load(graphDir);
|
|
4314
4841
|
if (opts?.all) {
|
|
@@ -4369,13 +4896,13 @@ async function runIngest(projectPath, source, opts) {
|
|
|
4369
4896
|
return result;
|
|
4370
4897
|
}
|
|
4371
4898
|
function createIngestCommand() {
|
|
4372
|
-
return new
|
|
4899
|
+
return new Command47("ingest").description("Ingest data into the knowledge graph").option("--source <name>", "Source to ingest (code, knowledge, git, jira, slack)").option("--all", "Run all sources (code, knowledge, git, and configured connectors)").option("--full", "Force full re-ingestion").action(async (opts, cmd) => {
|
|
4373
4900
|
if (!opts.source && !opts.all) {
|
|
4374
4901
|
console.error("Error: --source or --all is required");
|
|
4375
4902
|
process.exit(1);
|
|
4376
4903
|
}
|
|
4377
4904
|
const globalOpts = cmd.optsWithGlobals();
|
|
4378
|
-
const projectPath =
|
|
4905
|
+
const projectPath = path39.resolve(globalOpts.config ? path39.dirname(globalOpts.config) : ".");
|
|
4379
4906
|
try {
|
|
4380
4907
|
const result = await runIngest(projectPath, opts.source ?? "", {
|
|
4381
4908
|
full: opts.full,
|
|
@@ -4397,12 +4924,12 @@ function createIngestCommand() {
|
|
|
4397
4924
|
}
|
|
4398
4925
|
|
|
4399
4926
|
// src/commands/graph/query.ts
|
|
4400
|
-
import { Command as
|
|
4401
|
-
import * as
|
|
4927
|
+
import { Command as Command48 } from "commander";
|
|
4928
|
+
import * as path40 from "path";
|
|
4402
4929
|
async function runQuery(projectPath, rootNodeId, opts) {
|
|
4403
|
-
const { GraphStore, ContextQL } = await import("./dist-
|
|
4930
|
+
const { GraphStore, ContextQL } = await import("./dist-U7EAO6T2.js");
|
|
4404
4931
|
const store = new GraphStore();
|
|
4405
|
-
const graphDir =
|
|
4932
|
+
const graphDir = path40.join(projectPath, ".harness", "graph");
|
|
4406
4933
|
const loaded = await store.load(graphDir);
|
|
4407
4934
|
if (!loaded) throw new Error("No graph found. Run `harness scan` first.");
|
|
4408
4935
|
const params = {
|
|
@@ -4416,9 +4943,9 @@ async function runQuery(projectPath, rootNodeId, opts) {
|
|
|
4416
4943
|
return cql.execute(params);
|
|
4417
4944
|
}
|
|
4418
4945
|
function createQueryCommand() {
|
|
4419
|
-
return new
|
|
4946
|
+
return new Command48("query").description("Query the knowledge graph").argument("<rootNodeId>", "Starting node ID").option("--depth <n>", "Max traversal depth", "3").option("--types <types>", "Comma-separated node types to include").option("--edges <edges>", "Comma-separated edge types to include").option("--bidirectional", "Traverse both directions").action(async (rootNodeId, opts, cmd) => {
|
|
4420
4947
|
const globalOpts = cmd.optsWithGlobals();
|
|
4421
|
-
const projectPath =
|
|
4948
|
+
const projectPath = path40.resolve(globalOpts.config ? path40.dirname(globalOpts.config) : ".");
|
|
4422
4949
|
try {
|
|
4423
4950
|
const result = await runQuery(projectPath, rootNodeId, {
|
|
4424
4951
|
depth: parseInt(opts.depth),
|
|
@@ -4444,21 +4971,21 @@ function createQueryCommand() {
|
|
|
4444
4971
|
}
|
|
4445
4972
|
|
|
4446
4973
|
// src/commands/graph/index.ts
|
|
4447
|
-
import { Command as
|
|
4974
|
+
import { Command as Command49 } from "commander";
|
|
4448
4975
|
|
|
4449
4976
|
// src/commands/graph/status.ts
|
|
4450
|
-
import * as
|
|
4977
|
+
import * as path41 from "path";
|
|
4451
4978
|
async function runGraphStatus(projectPath) {
|
|
4452
|
-
const { GraphStore } = await import("./dist-
|
|
4453
|
-
const graphDir =
|
|
4979
|
+
const { GraphStore } = await import("./dist-U7EAO6T2.js");
|
|
4980
|
+
const graphDir = path41.join(projectPath, ".harness", "graph");
|
|
4454
4981
|
const store = new GraphStore();
|
|
4455
4982
|
const loaded = await store.load(graphDir);
|
|
4456
4983
|
if (!loaded) return { status: "no_graph", message: "No graph found. Run `harness scan` first." };
|
|
4457
|
-
const
|
|
4458
|
-
const metaPath =
|
|
4984
|
+
const fs34 = await import("fs/promises");
|
|
4985
|
+
const metaPath = path41.join(graphDir, "metadata.json");
|
|
4459
4986
|
let lastScan = "unknown";
|
|
4460
4987
|
try {
|
|
4461
|
-
const meta = JSON.parse(await
|
|
4988
|
+
const meta = JSON.parse(await fs34.readFile(metaPath, "utf-8"));
|
|
4462
4989
|
lastScan = meta.lastScanTimestamp;
|
|
4463
4990
|
} catch {
|
|
4464
4991
|
}
|
|
@@ -4469,8 +4996,8 @@ async function runGraphStatus(projectPath) {
|
|
|
4469
4996
|
}
|
|
4470
4997
|
let connectorSyncStatus = {};
|
|
4471
4998
|
try {
|
|
4472
|
-
const syncMetaPath =
|
|
4473
|
-
const syncMeta = JSON.parse(await
|
|
4999
|
+
const syncMetaPath = path41.join(graphDir, "sync-metadata.json");
|
|
5000
|
+
const syncMeta = JSON.parse(await fs34.readFile(syncMetaPath, "utf-8"));
|
|
4474
5001
|
for (const [name, data] of Object.entries(syncMeta.connectors ?? {})) {
|
|
4475
5002
|
connectorSyncStatus[name] = data.lastSyncTimestamp;
|
|
4476
5003
|
}
|
|
@@ -4487,10 +5014,10 @@ async function runGraphStatus(projectPath) {
|
|
|
4487
5014
|
}
|
|
4488
5015
|
|
|
4489
5016
|
// src/commands/graph/export.ts
|
|
4490
|
-
import * as
|
|
5017
|
+
import * as path42 from "path";
|
|
4491
5018
|
async function runGraphExport(projectPath, format) {
|
|
4492
|
-
const { GraphStore } = await import("./dist-
|
|
4493
|
-
const graphDir =
|
|
5019
|
+
const { GraphStore } = await import("./dist-U7EAO6T2.js");
|
|
5020
|
+
const graphDir = path42.join(projectPath, ".harness", "graph");
|
|
4494
5021
|
const store = new GraphStore();
|
|
4495
5022
|
const loaded = await store.load(graphDir);
|
|
4496
5023
|
if (!loaded) throw new Error("No graph found. Run `harness scan` first.");
|
|
@@ -4519,13 +5046,13 @@ async function runGraphExport(projectPath, format) {
|
|
|
4519
5046
|
}
|
|
4520
5047
|
|
|
4521
5048
|
// src/commands/graph/index.ts
|
|
4522
|
-
import * as
|
|
5049
|
+
import * as path43 from "path";
|
|
4523
5050
|
function createGraphCommand() {
|
|
4524
|
-
const graph = new
|
|
5051
|
+
const graph = new Command49("graph").description("Knowledge graph management");
|
|
4525
5052
|
graph.command("status").description("Show graph statistics").action(async (_opts, cmd) => {
|
|
4526
5053
|
try {
|
|
4527
5054
|
const globalOpts = cmd.optsWithGlobals();
|
|
4528
|
-
const projectPath =
|
|
5055
|
+
const projectPath = path43.resolve(globalOpts.config ? path43.dirname(globalOpts.config) : ".");
|
|
4529
5056
|
const result = await runGraphStatus(projectPath);
|
|
4530
5057
|
if (globalOpts.json) {
|
|
4531
5058
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -4552,7 +5079,7 @@ function createGraphCommand() {
|
|
|
4552
5079
|
});
|
|
4553
5080
|
graph.command("export").description("Export graph").requiredOption("--format <format>", "Output format (json, mermaid)").action(async (opts, cmd) => {
|
|
4554
5081
|
const globalOpts = cmd.optsWithGlobals();
|
|
4555
|
-
const projectPath =
|
|
5082
|
+
const projectPath = path43.resolve(globalOpts.config ? path43.dirname(globalOpts.config) : ".");
|
|
4556
5083
|
try {
|
|
4557
5084
|
const output = await runGraphExport(projectPath, opts.format);
|
|
4558
5085
|
console.log(output);
|
|
@@ -4565,19 +5092,19 @@ function createGraphCommand() {
|
|
|
4565
5092
|
}
|
|
4566
5093
|
|
|
4567
5094
|
// src/commands/mcp.ts
|
|
4568
|
-
import { Command as
|
|
5095
|
+
import { Command as Command50 } from "commander";
|
|
4569
5096
|
function createMcpCommand() {
|
|
4570
|
-
return new
|
|
4571
|
-
const { startServer: startServer2 } = await import("./mcp-
|
|
4572
|
-
await startServer2();
|
|
5097
|
+
return new Command50("mcp").description("Start the MCP (Model Context Protocol) server on stdio").option("--tools <tools...>", "Only register the specified tools (used by Cursor integration)").action(async (opts) => {
|
|
5098
|
+
const { startServer: startServer2 } = await import("./mcp-DVVUODN7.js");
|
|
5099
|
+
await startServer2(opts.tools);
|
|
4573
5100
|
});
|
|
4574
5101
|
}
|
|
4575
5102
|
|
|
4576
5103
|
// src/commands/impact-preview.ts
|
|
4577
|
-
import { Command as
|
|
5104
|
+
import { Command as Command51 } from "commander";
|
|
4578
5105
|
import { execSync as execSync3 } from "child_process";
|
|
4579
|
-
import * as
|
|
4580
|
-
import * as
|
|
5106
|
+
import * as path44 from "path";
|
|
5107
|
+
import * as fs26 from "fs";
|
|
4581
5108
|
function getStagedFiles(cwd) {
|
|
4582
5109
|
try {
|
|
4583
5110
|
const output = execSync3("git diff --cached --name-only", {
|
|
@@ -4591,7 +5118,7 @@ function getStagedFiles(cwd) {
|
|
|
4591
5118
|
}
|
|
4592
5119
|
function graphExists(projectPath) {
|
|
4593
5120
|
try {
|
|
4594
|
-
return
|
|
5121
|
+
return fs26.existsSync(path44.join(projectPath, ".harness", "graph", "graph.json"));
|
|
4595
5122
|
} catch {
|
|
4596
5123
|
return false;
|
|
4597
5124
|
}
|
|
@@ -4600,7 +5127,7 @@ function extractNodeName(id) {
|
|
|
4600
5127
|
const parts = id.split(":");
|
|
4601
5128
|
if (parts.length > 1) {
|
|
4602
5129
|
const fullPath = parts.slice(1).join(":");
|
|
4603
|
-
return
|
|
5130
|
+
return path44.basename(fullPath);
|
|
4604
5131
|
}
|
|
4605
5132
|
return id;
|
|
4606
5133
|
}
|
|
@@ -4723,7 +5250,7 @@ function formatPerFile(perFileResults) {
|
|
|
4723
5250
|
return lines.join("\n");
|
|
4724
5251
|
}
|
|
4725
5252
|
async function runImpactPreview(options) {
|
|
4726
|
-
const projectPath =
|
|
5253
|
+
const projectPath = path44.resolve(options.path ?? process.cwd());
|
|
4727
5254
|
const stagedFiles = getStagedFiles(projectPath);
|
|
4728
5255
|
if (stagedFiles.length === 0) {
|
|
4729
5256
|
return "Impact Preview: no staged changes";
|
|
@@ -4770,7 +5297,7 @@ async function runImpactPreview(options) {
|
|
|
4770
5297
|
return formatCompact(stagedFiles.length, merged, aggregateCounts);
|
|
4771
5298
|
}
|
|
4772
5299
|
function createImpactPreviewCommand() {
|
|
4773
|
-
const command = new
|
|
5300
|
+
const command = new Command51("impact-preview").description("Show blast radius of staged changes using the knowledge graph").option("--detailed", "Show all affected files instead of top items").option("--per-file", "Show impact per staged file instead of aggregate").option("--path <dir>", "Project root (default: cwd)").action(async (opts) => {
|
|
4774
5301
|
const output = await runImpactPreview({
|
|
4775
5302
|
detailed: opts.detailed,
|
|
4776
5303
|
perFile: opts.perFile,
|
|
@@ -4783,7 +5310,7 @@ function createImpactPreviewCommand() {
|
|
|
4783
5310
|
}
|
|
4784
5311
|
|
|
4785
5312
|
// src/commands/check-arch.ts
|
|
4786
|
-
import { Command as
|
|
5313
|
+
import { Command as Command52 } from "commander";
|
|
4787
5314
|
import { execSync as execSync4 } from "child_process";
|
|
4788
5315
|
function getCommitHash2(cwd) {
|
|
4789
5316
|
try {
|
|
@@ -4878,7 +5405,7 @@ async function runCheckArch(options) {
|
|
|
4878
5405
|
});
|
|
4879
5406
|
}
|
|
4880
5407
|
function createCheckArchCommand() {
|
|
4881
|
-
const command = new
|
|
5408
|
+
const command = new Command52("check-arch").description("Check architecture assertions against baseline and thresholds").option("--update-baseline", "Capture current state as new baseline").option("--module <path>", "Check a single module").action(async (opts, cmd) => {
|
|
4882
5409
|
const globalOpts = cmd.optsWithGlobals();
|
|
4883
5410
|
const mode = globalOpts.json ? OutputMode.JSON : globalOpts.quiet ? OutputMode.QUIET : globalOpts.verbose ? OutputMode.VERBOSE : OutputMode.TEXT;
|
|
4884
5411
|
const formatter = new OutputFormatter(mode);
|
|
@@ -4944,20 +5471,20 @@ function createCheckArchCommand() {
|
|
|
4944
5471
|
}
|
|
4945
5472
|
|
|
4946
5473
|
// src/commands/blueprint.ts
|
|
4947
|
-
import { Command as
|
|
4948
|
-
import * as
|
|
5474
|
+
import { Command as Command53 } from "commander";
|
|
5475
|
+
import * as path45 from "path";
|
|
4949
5476
|
function createBlueprintCommand() {
|
|
4950
|
-
return new
|
|
5477
|
+
return new Command53("blueprint").description("Generate a self-contained, interactive blueprint of the codebase").argument("[path]", "Path to the project root", ".").option("-o, --output <dir>", "Output directory", "docs/blueprint").action(async (projectPath, options) => {
|
|
4951
5478
|
try {
|
|
4952
|
-
const rootDir =
|
|
4953
|
-
const outputDir =
|
|
5479
|
+
const rootDir = path45.resolve(projectPath);
|
|
5480
|
+
const outputDir = path45.resolve(options.output);
|
|
4954
5481
|
logger.info(`Scanning project at ${rootDir}...`);
|
|
4955
5482
|
const scanner = new ProjectScanner(rootDir);
|
|
4956
5483
|
const data = await scanner.scan();
|
|
4957
5484
|
logger.info(`Generating blueprint to ${outputDir}...`);
|
|
4958
5485
|
const generator = new BlueprintGenerator();
|
|
4959
5486
|
await generator.generate(data, { outputDir });
|
|
4960
|
-
logger.success(`Blueprint generated successfully at ${
|
|
5487
|
+
logger.success(`Blueprint generated successfully at ${path45.join(outputDir, "index.html")}`);
|
|
4961
5488
|
} catch (error) {
|
|
4962
5489
|
logger.error(
|
|
4963
5490
|
`Failed to generate blueprint: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -4968,16 +5495,16 @@ function createBlueprintCommand() {
|
|
|
4968
5495
|
}
|
|
4969
5496
|
|
|
4970
5497
|
// src/commands/share.ts
|
|
4971
|
-
import { Command as
|
|
4972
|
-
import * as
|
|
4973
|
-
import * as
|
|
5498
|
+
import { Command as Command54 } from "commander";
|
|
5499
|
+
import * as fs27 from "fs";
|
|
5500
|
+
import * as path46 from "path";
|
|
4974
5501
|
import { parse as parseYaml } from "yaml";
|
|
4975
5502
|
var MANIFEST_FILENAME = "constraints.yaml";
|
|
4976
5503
|
function createShareCommand() {
|
|
4977
|
-
return new
|
|
4978
|
-
const rootDir =
|
|
4979
|
-
const manifestPath =
|
|
4980
|
-
if (!
|
|
5504
|
+
return new Command54("share").description("Extract and publish a constraints bundle from constraints.yaml").argument("[path]", "Path to the project root", ".").option("-o, --output <dir>", "Output directory for the bundle", ".").action(async (projectPath, options) => {
|
|
5505
|
+
const rootDir = path46.resolve(projectPath);
|
|
5506
|
+
const manifestPath = path46.join(rootDir, MANIFEST_FILENAME);
|
|
5507
|
+
if (!fs27.existsSync(manifestPath)) {
|
|
4981
5508
|
logger.error(
|
|
4982
5509
|
`No ${MANIFEST_FILENAME} found at ${manifestPath}.
|
|
4983
5510
|
Create a constraints.yaml in your project root to define what to share.`
|
|
@@ -4986,7 +5513,7 @@ Create a constraints.yaml in your project root to define what to share.`
|
|
|
4986
5513
|
}
|
|
4987
5514
|
let parsed;
|
|
4988
5515
|
try {
|
|
4989
|
-
const raw =
|
|
5516
|
+
const raw = fs27.readFileSync(manifestPath, "utf-8");
|
|
4990
5517
|
parsed = parseYaml(raw);
|
|
4991
5518
|
} catch (err) {
|
|
4992
5519
|
logger.error(
|
|
@@ -5000,7 +5527,7 @@ Create a constraints.yaml in your project root to define what to share.`
|
|
|
5000
5527
|
process.exit(1);
|
|
5001
5528
|
}
|
|
5002
5529
|
const manifest = manifestResult.value;
|
|
5003
|
-
const configResult = resolveConfig(
|
|
5530
|
+
const configResult = resolveConfig(path46.join(rootDir, "harness.config.json"));
|
|
5004
5531
|
if (!configResult.ok) {
|
|
5005
5532
|
logger.error(configResult.error.message);
|
|
5006
5533
|
process.exit(1);
|
|
@@ -5018,8 +5545,8 @@ Create a constraints.yaml in your project root to define what to share.`
|
|
|
5018
5545
|
);
|
|
5019
5546
|
process.exit(1);
|
|
5020
5547
|
}
|
|
5021
|
-
const outputDir =
|
|
5022
|
-
const outputPath =
|
|
5548
|
+
const outputDir = path46.resolve(options.output);
|
|
5549
|
+
const outputPath = path46.join(outputDir, `${manifest.name}.harness-constraints.json`);
|
|
5023
5550
|
const writeResult = await writeConfig(outputPath, bundle);
|
|
5024
5551
|
if (!writeResult.ok) {
|
|
5025
5552
|
logger.error(`Failed to write bundle: ${writeResult.error.message}`);
|
|
@@ -5030,25 +5557,25 @@ Create a constraints.yaml in your project root to define what to share.`
|
|
|
5030
5557
|
}
|
|
5031
5558
|
|
|
5032
5559
|
// src/commands/install.ts
|
|
5033
|
-
import * as
|
|
5034
|
-
import * as
|
|
5035
|
-
import { Command as
|
|
5560
|
+
import * as fs29 from "fs";
|
|
5561
|
+
import * as path48 from "path";
|
|
5562
|
+
import { Command as Command55 } from "commander";
|
|
5036
5563
|
import { parse as yamlParse } from "yaml";
|
|
5037
5564
|
|
|
5038
5565
|
// src/registry/tarball.ts
|
|
5039
|
-
import * as
|
|
5040
|
-
import * as
|
|
5566
|
+
import * as fs28 from "fs";
|
|
5567
|
+
import * as path47 from "path";
|
|
5041
5568
|
import * as os6 from "os";
|
|
5042
5569
|
import { execFileSync as execFileSync5 } from "child_process";
|
|
5043
5570
|
function extractTarball(tarballBuffer) {
|
|
5044
|
-
const tmpDir =
|
|
5045
|
-
const tarballPath =
|
|
5571
|
+
const tmpDir = fs28.mkdtempSync(path47.join(os6.tmpdir(), "harness-skill-install-"));
|
|
5572
|
+
const tarballPath = path47.join(tmpDir, "package.tgz");
|
|
5046
5573
|
try {
|
|
5047
|
-
|
|
5574
|
+
fs28.writeFileSync(tarballPath, tarballBuffer);
|
|
5048
5575
|
execFileSync5("tar", ["-xzf", tarballPath, "-C", tmpDir], {
|
|
5049
5576
|
timeout: 3e4
|
|
5050
5577
|
});
|
|
5051
|
-
|
|
5578
|
+
fs28.unlinkSync(tarballPath);
|
|
5052
5579
|
} catch (err) {
|
|
5053
5580
|
cleanupTempDir(tmpDir);
|
|
5054
5581
|
throw new Error(
|
|
@@ -5059,37 +5586,37 @@ function extractTarball(tarballBuffer) {
|
|
|
5059
5586
|
return tmpDir;
|
|
5060
5587
|
}
|
|
5061
5588
|
function placeSkillContent(extractedPkgDir, communityBaseDir, skillName, platforms) {
|
|
5062
|
-
const files =
|
|
5589
|
+
const files = fs28.readdirSync(extractedPkgDir);
|
|
5063
5590
|
for (const platform of platforms) {
|
|
5064
|
-
const targetDir =
|
|
5065
|
-
if (
|
|
5066
|
-
|
|
5591
|
+
const targetDir = path47.join(communityBaseDir, platform, skillName);
|
|
5592
|
+
if (fs28.existsSync(targetDir)) {
|
|
5593
|
+
fs28.rmSync(targetDir, { recursive: true, force: true });
|
|
5067
5594
|
}
|
|
5068
|
-
|
|
5595
|
+
fs28.mkdirSync(targetDir, { recursive: true });
|
|
5069
5596
|
for (const file of files) {
|
|
5070
5597
|
if (file === "package.json" || file === "node_modules") continue;
|
|
5071
|
-
const srcPath =
|
|
5072
|
-
const destPath =
|
|
5073
|
-
const stat =
|
|
5598
|
+
const srcPath = path47.join(extractedPkgDir, file);
|
|
5599
|
+
const destPath = path47.join(targetDir, file);
|
|
5600
|
+
const stat = fs28.statSync(srcPath);
|
|
5074
5601
|
if (stat.isDirectory()) {
|
|
5075
|
-
|
|
5602
|
+
fs28.cpSync(srcPath, destPath, { recursive: true });
|
|
5076
5603
|
} else {
|
|
5077
|
-
|
|
5604
|
+
fs28.copyFileSync(srcPath, destPath);
|
|
5078
5605
|
}
|
|
5079
5606
|
}
|
|
5080
5607
|
}
|
|
5081
5608
|
}
|
|
5082
5609
|
function removeSkillContent(communityBaseDir, skillName, platforms) {
|
|
5083
5610
|
for (const platform of platforms) {
|
|
5084
|
-
const targetDir =
|
|
5085
|
-
if (
|
|
5086
|
-
|
|
5611
|
+
const targetDir = path47.join(communityBaseDir, platform, skillName);
|
|
5612
|
+
if (fs28.existsSync(targetDir)) {
|
|
5613
|
+
fs28.rmSync(targetDir, { recursive: true, force: true });
|
|
5087
5614
|
}
|
|
5088
5615
|
}
|
|
5089
5616
|
}
|
|
5090
5617
|
function cleanupTempDir(dirPath) {
|
|
5091
5618
|
try {
|
|
5092
|
-
|
|
5619
|
+
fs28.rmSync(dirPath, { recursive: true, force: true });
|
|
5093
5620
|
} catch {
|
|
5094
5621
|
}
|
|
5095
5622
|
}
|
|
@@ -5145,35 +5672,35 @@ function validateSkillYaml(parsed) {
|
|
|
5145
5672
|
};
|
|
5146
5673
|
}
|
|
5147
5674
|
async function runLocalInstall(fromPath, options) {
|
|
5148
|
-
const resolvedPath =
|
|
5149
|
-
if (!
|
|
5675
|
+
const resolvedPath = path48.resolve(fromPath);
|
|
5676
|
+
if (!fs29.existsSync(resolvedPath)) {
|
|
5150
5677
|
throw new Error(`--from path does not exist: ${resolvedPath}`);
|
|
5151
5678
|
}
|
|
5152
|
-
const stat =
|
|
5679
|
+
const stat = fs29.statSync(resolvedPath);
|
|
5153
5680
|
let extractDir = null;
|
|
5154
5681
|
let pkgDir;
|
|
5155
5682
|
if (stat.isDirectory()) {
|
|
5156
5683
|
pkgDir = resolvedPath;
|
|
5157
5684
|
} else if (resolvedPath.endsWith(".tgz") || resolvedPath.endsWith(".tar.gz")) {
|
|
5158
|
-
const tarballBuffer =
|
|
5685
|
+
const tarballBuffer = fs29.readFileSync(resolvedPath);
|
|
5159
5686
|
extractDir = extractTarball(tarballBuffer);
|
|
5160
|
-
pkgDir =
|
|
5687
|
+
pkgDir = path48.join(extractDir, "package");
|
|
5161
5688
|
} else {
|
|
5162
5689
|
throw new Error(`--from path must be a directory or .tgz file. Got: ${resolvedPath}`);
|
|
5163
5690
|
}
|
|
5164
5691
|
try {
|
|
5165
|
-
const skillYamlPath =
|
|
5166
|
-
if (!
|
|
5692
|
+
const skillYamlPath = path48.join(pkgDir, "skill.yaml");
|
|
5693
|
+
if (!fs29.existsSync(skillYamlPath)) {
|
|
5167
5694
|
throw new Error(`No skill.yaml found at ${skillYamlPath}`);
|
|
5168
5695
|
}
|
|
5169
|
-
const rawYaml =
|
|
5696
|
+
const rawYaml = fs29.readFileSync(skillYamlPath, "utf-8");
|
|
5170
5697
|
const parsed = yamlParse(rawYaml);
|
|
5171
5698
|
const skillYaml = validateSkillYaml(parsed);
|
|
5172
5699
|
const shortName = skillYaml.name;
|
|
5173
5700
|
const globalDir = resolveGlobalSkillsDir();
|
|
5174
|
-
const skillsDir =
|
|
5175
|
-
const communityBase =
|
|
5176
|
-
const lockfilePath =
|
|
5701
|
+
const skillsDir = path48.dirname(globalDir);
|
|
5702
|
+
const communityBase = path48.join(skillsDir, "community");
|
|
5703
|
+
const lockfilePath = path48.join(communityBase, "skills-lock.json");
|
|
5177
5704
|
const bundledNames = getBundledSkillNames(globalDir);
|
|
5178
5705
|
if (bundledNames.has(shortName)) {
|
|
5179
5706
|
throw new Error(
|
|
@@ -5214,9 +5741,9 @@ async function runInstall(skillName, options) {
|
|
|
5214
5741
|
const packageName = resolvePackageName(skillName);
|
|
5215
5742
|
const shortName = extractSkillName(packageName);
|
|
5216
5743
|
const globalDir = resolveGlobalSkillsDir();
|
|
5217
|
-
const skillsDir =
|
|
5218
|
-
const communityBase =
|
|
5219
|
-
const lockfilePath =
|
|
5744
|
+
const skillsDir = path48.dirname(globalDir);
|
|
5745
|
+
const communityBase = path48.join(skillsDir, "community");
|
|
5746
|
+
const lockfilePath = path48.join(communityBase, "skills-lock.json");
|
|
5220
5747
|
const bundledNames = getBundledSkillNames(globalDir);
|
|
5221
5748
|
if (bundledNames.has(shortName)) {
|
|
5222
5749
|
throw new Error(
|
|
@@ -5242,12 +5769,12 @@ async function runInstall(skillName, options) {
|
|
|
5242
5769
|
const extractDir = extractTarball(tarballBuffer);
|
|
5243
5770
|
let skillYaml;
|
|
5244
5771
|
try {
|
|
5245
|
-
const extractedPkgDir =
|
|
5246
|
-
const skillYamlPath =
|
|
5247
|
-
if (!
|
|
5772
|
+
const extractedPkgDir = path48.join(extractDir, "package");
|
|
5773
|
+
const skillYamlPath = path48.join(extractedPkgDir, "skill.yaml");
|
|
5774
|
+
if (!fs29.existsSync(skillYamlPath)) {
|
|
5248
5775
|
throw new Error(`contains invalid skill.yaml: file not found in package`);
|
|
5249
5776
|
}
|
|
5250
|
-
const rawYaml =
|
|
5777
|
+
const rawYaml = fs29.readFileSync(skillYamlPath, "utf-8");
|
|
5251
5778
|
const parsed = yamlParse(rawYaml);
|
|
5252
5779
|
skillYaml = validateSkillYaml(parsed);
|
|
5253
5780
|
placeSkillContent(extractedPkgDir, communityBase, shortName, skillYaml.platforms);
|
|
@@ -5286,7 +5813,7 @@ async function runInstall(skillName, options) {
|
|
|
5286
5813
|
return result;
|
|
5287
5814
|
}
|
|
5288
5815
|
function createInstallCommand() {
|
|
5289
|
-
const cmd = new
|
|
5816
|
+
const cmd = new Command55("install");
|
|
5290
5817
|
cmd.description("Install a community skill from the @harness-skills registry").argument("<skill>", "Skill name or @harness-skills/scoped package name").option("--version <range>", "Semver range or exact version to install").option("--force", "Force reinstall even if same version is already installed").option("--from <path>", "Install from a local directory or .tgz file").option("--registry <url>", "Use a custom npm registry URL").action(async (skill, opts) => {
|
|
5291
5818
|
try {
|
|
5292
5819
|
const result = await runInstall(skill, opts);
|
|
@@ -5310,15 +5837,15 @@ function createInstallCommand() {
|
|
|
5310
5837
|
}
|
|
5311
5838
|
|
|
5312
5839
|
// src/commands/install-constraints.ts
|
|
5313
|
-
import * as
|
|
5314
|
-
import * as
|
|
5315
|
-
import { Command as
|
|
5840
|
+
import * as fs30 from "fs/promises";
|
|
5841
|
+
import * as path49 from "path";
|
|
5842
|
+
import { Command as Command56 } from "commander";
|
|
5316
5843
|
import semver4 from "semver";
|
|
5317
5844
|
async function runInstallConstraints(options) {
|
|
5318
5845
|
const { source, configPath, lockfilePath } = options;
|
|
5319
5846
|
let rawBundle;
|
|
5320
5847
|
try {
|
|
5321
|
-
rawBundle = await
|
|
5848
|
+
rawBundle = await fs30.readFile(source, "utf-8");
|
|
5322
5849
|
} catch (err) {
|
|
5323
5850
|
if (isNodeError(err) && err.code === "ENOENT") {
|
|
5324
5851
|
return { ok: false, error: `Bundle file not found: ${source}` };
|
|
@@ -5361,7 +5888,7 @@ async function runInstallConstraints(options) {
|
|
|
5361
5888
|
}
|
|
5362
5889
|
let localConfig;
|
|
5363
5890
|
try {
|
|
5364
|
-
const raw = await
|
|
5891
|
+
const raw = await fs30.readFile(configPath, "utf-8");
|
|
5365
5892
|
localConfig = JSON.parse(raw);
|
|
5366
5893
|
} catch (err) {
|
|
5367
5894
|
return {
|
|
@@ -5509,7 +6036,7 @@ function isNodeError(err) {
|
|
|
5509
6036
|
return err instanceof Error && "code" in err;
|
|
5510
6037
|
}
|
|
5511
6038
|
function resolveConfigPath(opts) {
|
|
5512
|
-
if (opts.config) return
|
|
6039
|
+
if (opts.config) return path49.resolve(opts.config);
|
|
5513
6040
|
const found = findConfigFile();
|
|
5514
6041
|
if (!found.ok) {
|
|
5515
6042
|
logger.error(found.error.message);
|
|
@@ -5544,9 +6071,9 @@ function logInstallResult(val, opts) {
|
|
|
5544
6071
|
}
|
|
5545
6072
|
async function handleInstallConstraints(source, opts) {
|
|
5546
6073
|
const configPath = resolveConfigPath(opts);
|
|
5547
|
-
const projectRoot =
|
|
5548
|
-
const lockfilePath =
|
|
5549
|
-
const resolvedSource =
|
|
6074
|
+
const projectRoot = path49.dirname(configPath);
|
|
6075
|
+
const lockfilePath = path49.join(projectRoot, ".harness", "constraints.lock.json");
|
|
6076
|
+
const resolvedSource = path49.resolve(source);
|
|
5550
6077
|
if (opts.forceLocal && opts.forcePackage) {
|
|
5551
6078
|
logger.error("Cannot use both --force-local and --force-package.");
|
|
5552
6079
|
process.exit(1);
|
|
@@ -5566,15 +6093,15 @@ async function handleInstallConstraints(source, opts) {
|
|
|
5566
6093
|
logInstallResult(result.value, opts);
|
|
5567
6094
|
}
|
|
5568
6095
|
function createInstallConstraintsCommand() {
|
|
5569
|
-
const cmd = new
|
|
6096
|
+
const cmd = new Command56("install-constraints");
|
|
5570
6097
|
cmd.description("Install a constraints bundle into the local harness config").argument("<source>", "Path to a .harness-constraints.json bundle file").option("--force-local", "Resolve all conflicts by keeping local values").option("--force-package", "Resolve all conflicts by using package values").option("--dry-run", "Show what would change without writing files").option("-c, --config <path>", "Path to harness.config.json").action(handleInstallConstraints);
|
|
5571
6098
|
return cmd;
|
|
5572
6099
|
}
|
|
5573
6100
|
|
|
5574
6101
|
// src/commands/uninstall-constraints.ts
|
|
5575
|
-
import * as
|
|
5576
|
-
import * as
|
|
5577
|
-
import { Command as
|
|
6102
|
+
import * as fs31 from "fs/promises";
|
|
6103
|
+
import * as path50 from "path";
|
|
6104
|
+
import { Command as Command57 } from "commander";
|
|
5578
6105
|
async function runUninstallConstraints(options) {
|
|
5579
6106
|
const { packageName, configPath, lockfilePath } = options;
|
|
5580
6107
|
const lockfileResult = await readLockfile(lockfilePath);
|
|
@@ -5594,7 +6121,7 @@ async function runUninstallConstraints(options) {
|
|
|
5594
6121
|
}
|
|
5595
6122
|
let localConfig;
|
|
5596
6123
|
try {
|
|
5597
|
-
const raw = await
|
|
6124
|
+
const raw = await fs31.readFile(configPath, "utf-8");
|
|
5598
6125
|
localConfig = JSON.parse(raw);
|
|
5599
6126
|
} catch (err) {
|
|
5600
6127
|
return {
|
|
@@ -5631,11 +6158,11 @@ async function runUninstallConstraints(options) {
|
|
|
5631
6158
|
};
|
|
5632
6159
|
}
|
|
5633
6160
|
function createUninstallConstraintsCommand() {
|
|
5634
|
-
const cmd = new
|
|
6161
|
+
const cmd = new Command57("uninstall-constraints");
|
|
5635
6162
|
cmd.description("Remove a previously installed constraints package").argument("<name>", "Name of the constraint package to uninstall").option("-c, --config <path>", "Path to harness.config.json").action(async (name, opts) => {
|
|
5636
6163
|
let configPath;
|
|
5637
6164
|
if (opts.config) {
|
|
5638
|
-
configPath =
|
|
6165
|
+
configPath = path50.resolve(opts.config);
|
|
5639
6166
|
} else {
|
|
5640
6167
|
const found = findConfigFile();
|
|
5641
6168
|
if (!found.ok) {
|
|
@@ -5644,8 +6171,8 @@ function createUninstallConstraintsCommand() {
|
|
|
5644
6171
|
}
|
|
5645
6172
|
configPath = found.value;
|
|
5646
6173
|
}
|
|
5647
|
-
const projectRoot =
|
|
5648
|
-
const lockfilePath =
|
|
6174
|
+
const projectRoot = path50.dirname(configPath);
|
|
6175
|
+
const lockfilePath = path50.join(projectRoot, ".harness", "constraints.lock.json");
|
|
5649
6176
|
const result = await runUninstallConstraints({
|
|
5650
6177
|
packageName: name,
|
|
5651
6178
|
configPath,
|
|
@@ -5670,15 +6197,15 @@ function createUninstallConstraintsCommand() {
|
|
|
5670
6197
|
}
|
|
5671
6198
|
|
|
5672
6199
|
// src/commands/uninstall.ts
|
|
5673
|
-
import * as
|
|
5674
|
-
import { Command as
|
|
6200
|
+
import * as path51 from "path";
|
|
6201
|
+
import { Command as Command58 } from "commander";
|
|
5675
6202
|
async function runUninstall(skillName, options) {
|
|
5676
6203
|
const packageName = resolvePackageName(skillName);
|
|
5677
6204
|
const shortName = extractSkillName(packageName);
|
|
5678
6205
|
const globalDir = resolveGlobalSkillsDir();
|
|
5679
|
-
const skillsDir =
|
|
5680
|
-
const communityBase =
|
|
5681
|
-
const lockfilePath =
|
|
6206
|
+
const skillsDir = path51.dirname(globalDir);
|
|
6207
|
+
const communityBase = path51.join(skillsDir, "community");
|
|
6208
|
+
const lockfilePath = path51.join(communityBase, "skills-lock.json");
|
|
5682
6209
|
const lockfile = readLockfile2(lockfilePath);
|
|
5683
6210
|
const entry = lockfile.skills[packageName];
|
|
5684
6211
|
if (!entry) {
|
|
@@ -5708,7 +6235,7 @@ async function runUninstall(skillName, options) {
|
|
|
5708
6235
|
return result;
|
|
5709
6236
|
}
|
|
5710
6237
|
function createUninstallCommand() {
|
|
5711
|
-
const cmd = new
|
|
6238
|
+
const cmd = new Command58("uninstall");
|
|
5712
6239
|
cmd.description("Uninstall a community skill").argument("<skill>", "Skill name or @harness-skills/scoped package name").option("--force", "Remove even if other skills depend on this one").action(async (skill, opts) => {
|
|
5713
6240
|
try {
|
|
5714
6241
|
const result = await runUninstall(skill, opts);
|
|
@@ -5727,13 +6254,13 @@ function createUninstallCommand() {
|
|
|
5727
6254
|
}
|
|
5728
6255
|
|
|
5729
6256
|
// src/commands/orchestrator.ts
|
|
5730
|
-
import { Command as
|
|
5731
|
-
import * as
|
|
6257
|
+
import { Command as Command59 } from "commander";
|
|
6258
|
+
import * as path52 from "path";
|
|
5732
6259
|
import { Orchestrator, WorkflowLoader, launchTUI } from "@harness-engineering/orchestrator";
|
|
5733
6260
|
function createOrchestratorCommand() {
|
|
5734
|
-
const orchestrator = new
|
|
6261
|
+
const orchestrator = new Command59("orchestrator");
|
|
5735
6262
|
orchestrator.command("run").description("Run the orchestrator daemon").option("-w, --workflow <path>", "Path to WORKFLOW.md", "WORKFLOW.md").action(async (opts) => {
|
|
5736
|
-
const workflowPath =
|
|
6263
|
+
const workflowPath = path52.resolve(process.cwd(), opts.workflow);
|
|
5737
6264
|
const loader = new WorkflowLoader();
|
|
5738
6265
|
const result = await loader.loadWorkflow(workflowPath);
|
|
5739
6266
|
if (!result.ok) {
|
|
@@ -5757,13 +6284,13 @@ function createOrchestratorCommand() {
|
|
|
5757
6284
|
}
|
|
5758
6285
|
|
|
5759
6286
|
// src/commands/learnings/index.ts
|
|
5760
|
-
import { Command as
|
|
6287
|
+
import { Command as Command61 } from "commander";
|
|
5761
6288
|
|
|
5762
6289
|
// src/commands/learnings/prune.ts
|
|
5763
|
-
import { Command as
|
|
5764
|
-
import * as
|
|
6290
|
+
import { Command as Command60 } from "commander";
|
|
6291
|
+
import * as path53 from "path";
|
|
5765
6292
|
async function handlePrune(opts) {
|
|
5766
|
-
const projectPath =
|
|
6293
|
+
const projectPath = path53.resolve(opts.path);
|
|
5767
6294
|
const result = await pruneLearnings(projectPath, opts.stream);
|
|
5768
6295
|
if (!result.ok) {
|
|
5769
6296
|
logger.error(result.error.message);
|
|
@@ -5802,21 +6329,666 @@ function printPatternProposals(patterns) {
|
|
|
5802
6329
|
);
|
|
5803
6330
|
}
|
|
5804
6331
|
function createPruneCommand() {
|
|
5805
|
-
return new
|
|
6332
|
+
return new Command60("prune").description(
|
|
5806
6333
|
"Analyze global learnings for patterns, present improvement proposals, and archive old entries"
|
|
5807
6334
|
).option("--path <path>", "Project root path", ".").option("--stream <name>", "Target a specific stream").action(handlePrune);
|
|
5808
6335
|
}
|
|
5809
6336
|
|
|
5810
6337
|
// src/commands/learnings/index.ts
|
|
5811
6338
|
function createLearningsCommand() {
|
|
5812
|
-
const command = new
|
|
6339
|
+
const command = new Command61("learnings").description("Learnings management commands");
|
|
5813
6340
|
command.addCommand(createPruneCommand());
|
|
5814
6341
|
return command;
|
|
5815
6342
|
}
|
|
5816
6343
|
|
|
6344
|
+
// src/commands/integrations/index.ts
|
|
6345
|
+
import { Command as Command66 } from "commander";
|
|
6346
|
+
|
|
6347
|
+
// src/commands/integrations/add.ts
|
|
6348
|
+
import { Command as Command62 } from "commander";
|
|
6349
|
+
import * as fs32 from "fs";
|
|
6350
|
+
import * as path54 from "path";
|
|
6351
|
+
import chalk6 from "chalk";
|
|
6352
|
+
function addIntegration(cwd, name) {
|
|
6353
|
+
const def = INTEGRATION_REGISTRY.find((i) => i.name === name);
|
|
6354
|
+
if (!def) {
|
|
6355
|
+
return Err(
|
|
6356
|
+
new CLIError(
|
|
6357
|
+
`Integration '${name}' not found in registry. Run 'harness integrations list' to see available integrations.`,
|
|
6358
|
+
ExitCode.ERROR
|
|
6359
|
+
)
|
|
6360
|
+
);
|
|
6361
|
+
}
|
|
6362
|
+
if (def.tier === 0) {
|
|
6363
|
+
return Err(
|
|
6364
|
+
new CLIError(
|
|
6365
|
+
`${def.displayName} is a Tier 0 integration, already configured by 'harness setup'. Run 'harness setup' if missing.`,
|
|
6366
|
+
ExitCode.ERROR
|
|
6367
|
+
)
|
|
6368
|
+
);
|
|
6369
|
+
}
|
|
6370
|
+
const mcpPath = path54.join(cwd, ".mcp.json");
|
|
6371
|
+
const mcpEntry = {
|
|
6372
|
+
command: def.mcpConfig.command
|
|
6373
|
+
};
|
|
6374
|
+
if (def.mcpConfig.args.length > 0) mcpEntry.args = def.mcpConfig.args;
|
|
6375
|
+
if (def.mcpConfig.env) mcpEntry.env = def.mcpConfig.env;
|
|
6376
|
+
writeMcpEntry(mcpPath, def.name, mcpEntry);
|
|
6377
|
+
const geminiDir = path54.join(cwd, ".gemini");
|
|
6378
|
+
if (fs32.existsSync(geminiDir)) {
|
|
6379
|
+
const geminiPath = path54.join(geminiDir, "settings.json");
|
|
6380
|
+
writeMcpEntry(geminiPath, def.name, mcpEntry);
|
|
6381
|
+
}
|
|
6382
|
+
const configPath = path54.join(cwd, "harness.config.json");
|
|
6383
|
+
const integConfig = readIntegrationsConfig(configPath);
|
|
6384
|
+
if (!integConfig.enabled.includes(def.name)) {
|
|
6385
|
+
integConfig.enabled.push(def.name);
|
|
6386
|
+
}
|
|
6387
|
+
integConfig.dismissed = integConfig.dismissed.filter((d) => d !== def.name);
|
|
6388
|
+
writeIntegrationsConfig(configPath, integConfig);
|
|
6389
|
+
const envVarMissing = !!def.envVar && !process.env[def.envVar];
|
|
6390
|
+
return Ok({
|
|
6391
|
+
name: def.name,
|
|
6392
|
+
displayName: def.displayName,
|
|
6393
|
+
envVarMissing,
|
|
6394
|
+
...def.envVar !== void 0 && { envVar: def.envVar },
|
|
6395
|
+
...def.installHint !== void 0 && { installHint: def.installHint }
|
|
6396
|
+
});
|
|
6397
|
+
}
|
|
6398
|
+
function createAddIntegrationCommand() {
|
|
6399
|
+
return new Command62("add").description("Enable an MCP integration").argument("<name>", "Integration name (e.g. perplexity, augment-code)").action(async (name, _opts, cmd) => {
|
|
6400
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
6401
|
+
const cwd = process.cwd();
|
|
6402
|
+
const result = addIntegration(cwd, name);
|
|
6403
|
+
if (!result.ok) {
|
|
6404
|
+
if (!globalOpts.quiet) {
|
|
6405
|
+
logger.error(result.error.message);
|
|
6406
|
+
}
|
|
6407
|
+
process.exit(result.error.exitCode);
|
|
6408
|
+
return;
|
|
6409
|
+
}
|
|
6410
|
+
const { displayName, envVarMissing, envVar, installHint } = result.value;
|
|
6411
|
+
if (!globalOpts.quiet) {
|
|
6412
|
+
console.log("");
|
|
6413
|
+
logger.success(`${displayName} integration enabled.`);
|
|
6414
|
+
console.log("");
|
|
6415
|
+
if (envVarMissing && envVar) {
|
|
6416
|
+
logger.warn(`Set ${chalk6.bold(envVar)} in your environment to activate.`);
|
|
6417
|
+
if (installHint) {
|
|
6418
|
+
console.log(` ${chalk6.dim(installHint)}`);
|
|
6419
|
+
}
|
|
6420
|
+
console.log("");
|
|
6421
|
+
}
|
|
6422
|
+
}
|
|
6423
|
+
process.exit(ExitCode.SUCCESS);
|
|
6424
|
+
});
|
|
6425
|
+
}
|
|
6426
|
+
|
|
6427
|
+
// src/commands/integrations/list.ts
|
|
6428
|
+
import { Command as Command63 } from "commander";
|
|
6429
|
+
import * as path55 from "path";
|
|
6430
|
+
import chalk7 from "chalk";
|
|
6431
|
+
function createListIntegrationsCommand() {
|
|
6432
|
+
return new Command63("list").description("Show all MCP integrations with status").action(async (_opts, cmd) => {
|
|
6433
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
6434
|
+
const cwd = process.cwd();
|
|
6435
|
+
const mcpPath = path55.join(cwd, ".mcp.json");
|
|
6436
|
+
const configPath = path55.join(cwd, "harness.config.json");
|
|
6437
|
+
const mcpConfig = readMcpConfig(mcpPath);
|
|
6438
|
+
const integConfig = readIntegrationsConfig(configPath);
|
|
6439
|
+
const mcpServers = mcpConfig.mcpServers ?? {};
|
|
6440
|
+
const tier0 = INTEGRATION_REGISTRY.filter((i) => i.tier === 0);
|
|
6441
|
+
const tier1 = INTEGRATION_REGISTRY.filter((i) => i.tier === 1);
|
|
6442
|
+
if (globalOpts.json) {
|
|
6443
|
+
const entries = INTEGRATION_REGISTRY.map((i) => ({
|
|
6444
|
+
name: i.name,
|
|
6445
|
+
tier: i.tier,
|
|
6446
|
+
configured: i.name in mcpServers,
|
|
6447
|
+
enabled: integConfig.enabled.includes(i.name),
|
|
6448
|
+
dismissed: integConfig.dismissed.includes(i.name),
|
|
6449
|
+
envVar: i.envVar ?? null,
|
|
6450
|
+
envVarSet: i.envVar ? !!process.env[i.envVar] : null
|
|
6451
|
+
}));
|
|
6452
|
+
console.log(JSON.stringify(entries, null, 2));
|
|
6453
|
+
process.exit(ExitCode.SUCCESS);
|
|
6454
|
+
return;
|
|
6455
|
+
}
|
|
6456
|
+
console.log("");
|
|
6457
|
+
console.log("MCP Integrations:");
|
|
6458
|
+
console.log("");
|
|
6459
|
+
console.log(" Tier 0 (zero-config):");
|
|
6460
|
+
for (const i of tier0) {
|
|
6461
|
+
const configured = i.name in mcpServers;
|
|
6462
|
+
const icon = configured ? chalk7.green("\u2713") : chalk7.dim("\u25CB");
|
|
6463
|
+
console.log(` ${icon} ${i.name.padEnd(22)} ${i.description}`);
|
|
6464
|
+
}
|
|
6465
|
+
console.log("");
|
|
6466
|
+
console.log(" Tier 1 (API key required):");
|
|
6467
|
+
for (const i of tier1) {
|
|
6468
|
+
const configured = i.name in mcpServers;
|
|
6469
|
+
const dismissed = integConfig.dismissed.includes(i.name);
|
|
6470
|
+
const icon = configured ? chalk7.green("\u2713") : chalk7.dim("\u25CB");
|
|
6471
|
+
let suffix = "";
|
|
6472
|
+
if (dismissed) {
|
|
6473
|
+
suffix = chalk7.dim("[dismissed]");
|
|
6474
|
+
} else if (i.envVar) {
|
|
6475
|
+
const envSet = !!process.env[i.envVar];
|
|
6476
|
+
suffix = `${i.envVar} ${envSet ? chalk7.green("\u2713") : chalk7.yellow("not set")}`;
|
|
6477
|
+
}
|
|
6478
|
+
console.log(` ${icon} ${i.name.padEnd(22)} ${i.description.padEnd(35)} ${suffix}`);
|
|
6479
|
+
}
|
|
6480
|
+
console.log("");
|
|
6481
|
+
console.log(
|
|
6482
|
+
` Run '${chalk7.cyan("harness integrations add <name>")}' to enable a Tier 1 integration.`
|
|
6483
|
+
);
|
|
6484
|
+
console.log("");
|
|
6485
|
+
process.exit(ExitCode.SUCCESS);
|
|
6486
|
+
});
|
|
6487
|
+
}
|
|
6488
|
+
|
|
6489
|
+
// src/commands/integrations/remove.ts
|
|
6490
|
+
import { Command as Command64 } from "commander";
|
|
6491
|
+
import * as fs33 from "fs";
|
|
6492
|
+
import * as path56 from "path";
|
|
6493
|
+
function removeIntegration(cwd, name) {
|
|
6494
|
+
const def = INTEGRATION_REGISTRY.find((i) => i.name === name);
|
|
6495
|
+
if (!def) {
|
|
6496
|
+
return Err(
|
|
6497
|
+
new CLIError(
|
|
6498
|
+
`Integration '${name}' not found in registry. Run 'harness integrations list' to see available integrations.`,
|
|
6499
|
+
ExitCode.ERROR
|
|
6500
|
+
)
|
|
6501
|
+
);
|
|
6502
|
+
}
|
|
6503
|
+
const mcpPath = path56.join(cwd, ".mcp.json");
|
|
6504
|
+
removeMcpEntry(mcpPath, def.name);
|
|
6505
|
+
const geminiDir = path56.join(cwd, ".gemini");
|
|
6506
|
+
if (fs33.existsSync(geminiDir)) {
|
|
6507
|
+
const geminiPath = path56.join(geminiDir, "settings.json");
|
|
6508
|
+
removeMcpEntry(geminiPath, def.name);
|
|
6509
|
+
}
|
|
6510
|
+
const configPath = path56.join(cwd, "harness.config.json");
|
|
6511
|
+
const integConfig = readIntegrationsConfig(configPath);
|
|
6512
|
+
integConfig.enabled = integConfig.enabled.filter((e) => e !== def.name);
|
|
6513
|
+
writeIntegrationsConfig(configPath, integConfig);
|
|
6514
|
+
return Ok(def.displayName);
|
|
6515
|
+
}
|
|
6516
|
+
function createRemoveIntegrationCommand() {
|
|
6517
|
+
return new Command64("remove").description("Remove an MCP integration").argument("<name>", "Integration name (e.g. perplexity, augment-code)").action(async (name, _opts, cmd) => {
|
|
6518
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
6519
|
+
const cwd = process.cwd();
|
|
6520
|
+
const result = removeIntegration(cwd, name);
|
|
6521
|
+
if (!result.ok) {
|
|
6522
|
+
if (!globalOpts.quiet) {
|
|
6523
|
+
logger.error(result.error.message);
|
|
6524
|
+
}
|
|
6525
|
+
process.exit(result.error.exitCode);
|
|
6526
|
+
return;
|
|
6527
|
+
}
|
|
6528
|
+
if (!globalOpts.quiet) {
|
|
6529
|
+
console.log("");
|
|
6530
|
+
logger.success(`${result.value} integration removed.`);
|
|
6531
|
+
console.log("");
|
|
6532
|
+
}
|
|
6533
|
+
process.exit(ExitCode.SUCCESS);
|
|
6534
|
+
});
|
|
6535
|
+
}
|
|
6536
|
+
|
|
6537
|
+
// src/commands/integrations/dismiss.ts
|
|
6538
|
+
import { Command as Command65 } from "commander";
|
|
6539
|
+
import * as path57 from "path";
|
|
6540
|
+
function dismissIntegration(cwd, name) {
|
|
6541
|
+
const def = INTEGRATION_REGISTRY.find((i) => i.name === name);
|
|
6542
|
+
if (!def) {
|
|
6543
|
+
return Err(
|
|
6544
|
+
new CLIError(
|
|
6545
|
+
`Integration '${name}' not found in registry. Run 'harness integrations list' to see available integrations.`,
|
|
6546
|
+
ExitCode.ERROR
|
|
6547
|
+
)
|
|
6548
|
+
);
|
|
6549
|
+
}
|
|
6550
|
+
const configPath = path57.join(cwd, "harness.config.json");
|
|
6551
|
+
const integConfig = readIntegrationsConfig(configPath);
|
|
6552
|
+
if (!integConfig.dismissed.includes(def.name)) {
|
|
6553
|
+
integConfig.dismissed.push(def.name);
|
|
6554
|
+
}
|
|
6555
|
+
integConfig.enabled = integConfig.enabled.filter((e) => e !== def.name);
|
|
6556
|
+
writeIntegrationsConfig(configPath, integConfig);
|
|
6557
|
+
return Ok(def.displayName);
|
|
6558
|
+
}
|
|
6559
|
+
function createDismissIntegrationCommand() {
|
|
6560
|
+
return new Command65("dismiss").description("Suppress doctor recommendations for an integration").argument("<name>", "Integration name (e.g. perplexity, augment-code)").action(async (name, _opts, cmd) => {
|
|
6561
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
6562
|
+
const cwd = process.cwd();
|
|
6563
|
+
const result = dismissIntegration(cwd, name);
|
|
6564
|
+
if (!result.ok) {
|
|
6565
|
+
if (!globalOpts.quiet) {
|
|
6566
|
+
logger.error(result.error.message);
|
|
6567
|
+
}
|
|
6568
|
+
process.exit(result.error.exitCode);
|
|
6569
|
+
return;
|
|
6570
|
+
}
|
|
6571
|
+
if (!globalOpts.quiet) {
|
|
6572
|
+
console.log("");
|
|
6573
|
+
logger.info(
|
|
6574
|
+
`${result.value} dismissed. It will no longer appear in 'harness doctor' suggestions.`
|
|
6575
|
+
);
|
|
6576
|
+
console.log("");
|
|
6577
|
+
}
|
|
6578
|
+
process.exit(ExitCode.SUCCESS);
|
|
6579
|
+
});
|
|
6580
|
+
}
|
|
6581
|
+
|
|
6582
|
+
// src/commands/integrations/index.ts
|
|
6583
|
+
function createIntegrationsCommand() {
|
|
6584
|
+
const command = new Command66("integrations").description(
|
|
6585
|
+
"Manage MCP peer integrations (add, list, remove, dismiss)"
|
|
6586
|
+
);
|
|
6587
|
+
command.addCommand(createListIntegrationsCommand());
|
|
6588
|
+
command.addCommand(createAddIntegrationCommand());
|
|
6589
|
+
command.addCommand(createRemoveIntegrationCommand());
|
|
6590
|
+
command.addCommand(createDismissIntegrationCommand());
|
|
6591
|
+
return command;
|
|
6592
|
+
}
|
|
6593
|
+
|
|
6594
|
+
// src/commands/usage.ts
|
|
6595
|
+
import { Command as Command67 } from "commander";
|
|
6596
|
+
async function loadAndPriceRecords(cwd, includeClaudeSessions = false) {
|
|
6597
|
+
const { readCostRecords, loadPricingData, calculateCost, parseCCRecords } = await import("./dist-MF5BK5AD.js");
|
|
6598
|
+
const records = readCostRecords(cwd);
|
|
6599
|
+
if (includeClaudeSessions) {
|
|
6600
|
+
const ccRecords = parseCCRecords();
|
|
6601
|
+
records.push(...ccRecords);
|
|
6602
|
+
}
|
|
6603
|
+
if (records.length === 0) return records;
|
|
6604
|
+
const pricingData = await loadPricingData(cwd);
|
|
6605
|
+
for (const record of records) {
|
|
6606
|
+
if (record.model && record.costMicroUSD == null) {
|
|
6607
|
+
const cost = calculateCost(record, pricingData);
|
|
6608
|
+
if (cost != null) record.costMicroUSD = cost;
|
|
6609
|
+
}
|
|
6610
|
+
}
|
|
6611
|
+
return records;
|
|
6612
|
+
}
|
|
6613
|
+
function formatMicroUSD(microUSD) {
|
|
6614
|
+
if (microUSD == null) return "N/A";
|
|
6615
|
+
return "$" + (microUSD / 1e6).toFixed(4);
|
|
6616
|
+
}
|
|
6617
|
+
function formatTokenCount(count) {
|
|
6618
|
+
if (count >= 1e6) return (count / 1e6).toFixed(1) + "M";
|
|
6619
|
+
if (count >= 1e3) return (count / 1e3).toFixed(1) + "K";
|
|
6620
|
+
return String(count);
|
|
6621
|
+
}
|
|
6622
|
+
function formatModels(models) {
|
|
6623
|
+
if (models.length === 0) return "unknown";
|
|
6624
|
+
if (models.length === 1) return models[0] ?? "unknown";
|
|
6625
|
+
return `${models[0] ?? "unknown"} and ${models.length - 1} other${models.length - 1 > 1 ? "s" : ""}`;
|
|
6626
|
+
}
|
|
6627
|
+
function registerDailyCommand(usage) {
|
|
6628
|
+
usage.command("daily").description("Show per-day token usage and cost").option("--days <n>", "Number of days to show (default: 7, max: 90)", "7").action(async (opts, cmd) => {
|
|
6629
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
6630
|
+
const days = Math.min(Math.max(parseInt(opts.days, 10) || 7, 1), 90);
|
|
6631
|
+
const cwd = process.cwd();
|
|
6632
|
+
const records = await loadAndPriceRecords(cwd, globalOpts.includeClaudeSessions);
|
|
6633
|
+
if (records.length === 0) {
|
|
6634
|
+
if (globalOpts.json) {
|
|
6635
|
+
console.log(JSON.stringify([]));
|
|
6636
|
+
} else {
|
|
6637
|
+
logger.info("No usage data found. Run some harness sessions first.");
|
|
6638
|
+
}
|
|
6639
|
+
return;
|
|
6640
|
+
}
|
|
6641
|
+
const { aggregateByDay } = await import("./dist-MF5BK5AD.js");
|
|
6642
|
+
const dailyData = aggregateByDay(records);
|
|
6643
|
+
const limited = dailyData.slice(0, days);
|
|
6644
|
+
if (globalOpts.json) {
|
|
6645
|
+
console.log(JSON.stringify(limited, null, 2));
|
|
6646
|
+
return;
|
|
6647
|
+
}
|
|
6648
|
+
const header = "Date | Sessions | Input | Output | Model(s) | Cost";
|
|
6649
|
+
const divider = "-------------|----------|-----------|-----------|------------------------------|--------";
|
|
6650
|
+
logger.info(header);
|
|
6651
|
+
logger.info(divider);
|
|
6652
|
+
for (const day of limited) {
|
|
6653
|
+
const date = day.date.padEnd(12);
|
|
6654
|
+
const sessions = String(day.sessionCount).padStart(8);
|
|
6655
|
+
const input = formatTokenCount(day.tokens.inputTokens).padStart(9);
|
|
6656
|
+
const output = formatTokenCount(day.tokens.outputTokens).padStart(9);
|
|
6657
|
+
const models = formatModels(day.models).padEnd(28);
|
|
6658
|
+
const cost = formatMicroUSD(day.costMicroUSD);
|
|
6659
|
+
logger.info(`${date} | ${sessions} | ${input} | ${output} | ${models} | ${cost}`);
|
|
6660
|
+
}
|
|
6661
|
+
});
|
|
6662
|
+
}
|
|
6663
|
+
function registerSessionsCommand(usage) {
|
|
6664
|
+
usage.command("sessions").description("List recent sessions with token usage and cost").option("--limit <n>", "Number of sessions to show (default: 10, max: 100)", "10").action(async (opts, cmd) => {
|
|
6665
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
6666
|
+
const limit = Math.min(Math.max(parseInt(opts.limit, 10) || 10, 1), 100);
|
|
6667
|
+
const cwd = process.cwd();
|
|
6668
|
+
const records = await loadAndPriceRecords(cwd, globalOpts.includeClaudeSessions);
|
|
6669
|
+
if (records.length === 0) {
|
|
6670
|
+
if (globalOpts.json) {
|
|
6671
|
+
console.log(JSON.stringify([]));
|
|
6672
|
+
} else {
|
|
6673
|
+
logger.info("No usage data found. Run some harness sessions first.");
|
|
6674
|
+
}
|
|
6675
|
+
return;
|
|
6676
|
+
}
|
|
6677
|
+
const { aggregateBySession } = await import("./dist-MF5BK5AD.js");
|
|
6678
|
+
const sessionData = aggregateBySession(records);
|
|
6679
|
+
const limited = sessionData.slice(0, limit);
|
|
6680
|
+
if (globalOpts.json) {
|
|
6681
|
+
console.log(JSON.stringify(limited, null, 2));
|
|
6682
|
+
return;
|
|
6683
|
+
}
|
|
6684
|
+
const header = "Session ID | Started | Duration | Tokens | Model | Cost";
|
|
6685
|
+
const divider = "---------------------|----------------------|-----------|-----------|----------------------|--------";
|
|
6686
|
+
logger.info(header);
|
|
6687
|
+
logger.info(divider);
|
|
6688
|
+
for (const s of limited) {
|
|
6689
|
+
const id = s.sessionId.slice(0, 20).padEnd(20);
|
|
6690
|
+
const started = s.firstTimestamp.slice(0, 19).padEnd(20);
|
|
6691
|
+
const durationMs = new Date(s.lastTimestamp).getTime() - new Date(s.firstTimestamp).getTime();
|
|
6692
|
+
const durationMin = Math.max(1, Math.round(durationMs / 6e4));
|
|
6693
|
+
const duration = `${durationMin}m`.padStart(9);
|
|
6694
|
+
const tokens = formatTokenCount(s.tokens.totalTokens).padStart(9);
|
|
6695
|
+
const model = (s.model ?? "unknown").slice(0, 20).padEnd(20);
|
|
6696
|
+
const cost = formatMicroUSD(s.costMicroUSD);
|
|
6697
|
+
logger.info(`${id} | ${started} | ${duration} | ${tokens} | ${model} | ${cost}`);
|
|
6698
|
+
}
|
|
6699
|
+
});
|
|
6700
|
+
}
|
|
6701
|
+
function registerSessionCommand(usage) {
|
|
6702
|
+
usage.command("session <id>").description("Show detailed token breakdown for a specific session").action(async (id, _opts, cmd) => {
|
|
6703
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
6704
|
+
const cwd = process.cwd();
|
|
6705
|
+
const records = await loadAndPriceRecords(cwd, globalOpts.includeClaudeSessions);
|
|
6706
|
+
const { aggregateBySession } = await import("./dist-MF5BK5AD.js");
|
|
6707
|
+
const sessionData = aggregateBySession(records);
|
|
6708
|
+
const match = sessionData.find((s) => s.sessionId === id);
|
|
6709
|
+
if (!match) {
|
|
6710
|
+
const fuzzy = sessionData.filter(
|
|
6711
|
+
(s) => s.sessionId.includes(id) || id.includes(s.sessionId.slice(0, 8))
|
|
6712
|
+
);
|
|
6713
|
+
const errMsg = `Session "${id}" not found.`;
|
|
6714
|
+
if (fuzzy.length > 0) {
|
|
6715
|
+
const suggestions = fuzzy.slice(0, 3).map((s) => s.sessionId);
|
|
6716
|
+
if (globalOpts.json) {
|
|
6717
|
+
console.log(JSON.stringify({ error: errMsg, suggestions }));
|
|
6718
|
+
} else {
|
|
6719
|
+
logger.error(errMsg);
|
|
6720
|
+
logger.info("Did you mean:");
|
|
6721
|
+
for (const s of suggestions) {
|
|
6722
|
+
logger.info(` ${s}`);
|
|
6723
|
+
}
|
|
6724
|
+
}
|
|
6725
|
+
} else {
|
|
6726
|
+
if (globalOpts.json) {
|
|
6727
|
+
console.log(JSON.stringify({ error: errMsg, suggestions: [] }));
|
|
6728
|
+
} else {
|
|
6729
|
+
logger.error(errMsg);
|
|
6730
|
+
}
|
|
6731
|
+
}
|
|
6732
|
+
process.exitCode = 1;
|
|
6733
|
+
return;
|
|
6734
|
+
}
|
|
6735
|
+
if (globalOpts.json) {
|
|
6736
|
+
console.log(JSON.stringify(match, null, 2));
|
|
6737
|
+
return;
|
|
6738
|
+
}
|
|
6739
|
+
logger.info(`Session: ${match.sessionId}`);
|
|
6740
|
+
logger.info(`Started: ${match.firstTimestamp}`);
|
|
6741
|
+
logger.info(`Ended: ${match.lastTimestamp}`);
|
|
6742
|
+
logger.info(`Model: ${match.model ?? "unknown"}`);
|
|
6743
|
+
logger.info(`Source: ${match.source}`);
|
|
6744
|
+
logger.info("");
|
|
6745
|
+
logger.info("Token Breakdown:");
|
|
6746
|
+
logger.info(` Input tokens: ${formatTokenCount(match.tokens.inputTokens)}`);
|
|
6747
|
+
logger.info(` Output tokens: ${formatTokenCount(match.tokens.outputTokens)}`);
|
|
6748
|
+
logger.info(` Total tokens: ${formatTokenCount(match.tokens.totalTokens)}`);
|
|
6749
|
+
if (match.cacheReadTokens != null) {
|
|
6750
|
+
logger.info(` Cache read tokens: ${formatTokenCount(match.cacheReadTokens)}`);
|
|
6751
|
+
}
|
|
6752
|
+
if (match.cacheCreationTokens != null) {
|
|
6753
|
+
logger.info(` Cache creation tokens: ${formatTokenCount(match.cacheCreationTokens)}`);
|
|
6754
|
+
}
|
|
6755
|
+
logger.info("");
|
|
6756
|
+
logger.info(`Cost: ${formatMicroUSD(match.costMicroUSD)}`);
|
|
6757
|
+
});
|
|
6758
|
+
}
|
|
6759
|
+
function registerLatestCommand(usage) {
|
|
6760
|
+
usage.command("latest").description("Show the most recently completed session cost summary").action(async (_opts, cmd) => {
|
|
6761
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
6762
|
+
const cwd = process.cwd();
|
|
6763
|
+
const records = await loadAndPriceRecords(cwd, globalOpts.includeClaudeSessions);
|
|
6764
|
+
if (records.length === 0) {
|
|
6765
|
+
if (globalOpts.json) {
|
|
6766
|
+
console.log(JSON.stringify({ error: "No usage data found" }));
|
|
6767
|
+
} else {
|
|
6768
|
+
logger.info("No usage data found. Run some harness sessions first.");
|
|
6769
|
+
}
|
|
6770
|
+
return;
|
|
6771
|
+
}
|
|
6772
|
+
const { aggregateBySession } = await import("./dist-MF5BK5AD.js");
|
|
6773
|
+
const sessionData = aggregateBySession(records);
|
|
6774
|
+
const latest = sessionData[0];
|
|
6775
|
+
if (!latest) {
|
|
6776
|
+
if (globalOpts.json) {
|
|
6777
|
+
console.log(JSON.stringify({ error: "No session data found" }));
|
|
6778
|
+
} else {
|
|
6779
|
+
logger.info("No session data found.");
|
|
6780
|
+
}
|
|
6781
|
+
return;
|
|
6782
|
+
}
|
|
6783
|
+
if (globalOpts.json) {
|
|
6784
|
+
console.log(JSON.stringify(latest, null, 2));
|
|
6785
|
+
return;
|
|
6786
|
+
}
|
|
6787
|
+
logger.info(`Session: ${latest.sessionId}`);
|
|
6788
|
+
logger.info(`Started: ${latest.firstTimestamp}`);
|
|
6789
|
+
logger.info(`Ended: ${latest.lastTimestamp}`);
|
|
6790
|
+
logger.info(`Model: ${latest.model ?? "unknown"}`);
|
|
6791
|
+
logger.info(
|
|
6792
|
+
`Tokens: ${formatTokenCount(latest.tokens.totalTokens)} (${formatTokenCount(latest.tokens.inputTokens)} in / ${formatTokenCount(latest.tokens.outputTokens)} out)`
|
|
6793
|
+
);
|
|
6794
|
+
logger.info(`Cost: ${formatMicroUSD(latest.costMicroUSD)}`);
|
|
6795
|
+
});
|
|
6796
|
+
}
|
|
6797
|
+
function createUsageCommand() {
|
|
6798
|
+
const usage = new Command67("usage").description("Token usage and cost tracking");
|
|
6799
|
+
usage.option(
|
|
6800
|
+
"--include-claude-sessions",
|
|
6801
|
+
"Include Claude Code session data from ~/.claude/projects/"
|
|
6802
|
+
);
|
|
6803
|
+
registerDailyCommand(usage);
|
|
6804
|
+
registerSessionsCommand(usage);
|
|
6805
|
+
registerSessionCommand(usage);
|
|
6806
|
+
registerLatestCommand(usage);
|
|
6807
|
+
return usage;
|
|
6808
|
+
}
|
|
6809
|
+
|
|
6810
|
+
// src/commands/taint.ts
|
|
6811
|
+
import { Command as Command68 } from "commander";
|
|
6812
|
+
function getProjectRoot() {
|
|
6813
|
+
return process.cwd();
|
|
6814
|
+
}
|
|
6815
|
+
function registerClearCommand(taint) {
|
|
6816
|
+
taint.command("clear [sessionId]").description(
|
|
6817
|
+
"Clear session taint \u2014 removes taint file(s) and re-enables destructive operations"
|
|
6818
|
+
).action((sessionId) => {
|
|
6819
|
+
const projectRoot = getProjectRoot();
|
|
6820
|
+
const count = clearTaint(projectRoot, sessionId);
|
|
6821
|
+
if (count === 0) {
|
|
6822
|
+
if (sessionId) {
|
|
6823
|
+
logger.info(`No taint found for session "${sessionId}".`);
|
|
6824
|
+
} else {
|
|
6825
|
+
logger.info("No active taint files found.");
|
|
6826
|
+
}
|
|
6827
|
+
} else if (sessionId) {
|
|
6828
|
+
logger.info(
|
|
6829
|
+
`Sentinel: taint cleared for session "${sessionId}". Destructive operations re-enabled.`
|
|
6830
|
+
);
|
|
6831
|
+
} else {
|
|
6832
|
+
logger.info(
|
|
6833
|
+
`Sentinel: cleared ${count} taint file${count === 1 ? "" : "s"}. Destructive operations re-enabled.`
|
|
6834
|
+
);
|
|
6835
|
+
}
|
|
6836
|
+
});
|
|
6837
|
+
}
|
|
6838
|
+
function registerStatusCommand(taint) {
|
|
6839
|
+
taint.command("status [sessionId]").description("Show current taint status for a session or all sessions").action((sessionId) => {
|
|
6840
|
+
const projectRoot = getProjectRoot();
|
|
6841
|
+
if (sessionId) {
|
|
6842
|
+
const result = checkTaint(projectRoot, sessionId);
|
|
6843
|
+
if (result.expired) {
|
|
6844
|
+
logger.info(`Session "${sessionId}": taint expired (cleared).`);
|
|
6845
|
+
} else if (result.tainted && result.state) {
|
|
6846
|
+
const state = result.state;
|
|
6847
|
+
const expiresAt = new Date(state.expiresAt);
|
|
6848
|
+
const remaining = Math.max(0, Math.round((expiresAt.getTime() - Date.now()) / 1e3 / 60));
|
|
6849
|
+
logger.info(`Session "${sessionId}": TAINTED (${state.severity})`);
|
|
6850
|
+
logger.info(` Reason: ${state.reason}`);
|
|
6851
|
+
logger.info(` Expires in: ~${remaining} minute${remaining === 1 ? "" : "s"}`);
|
|
6852
|
+
logger.info(` Findings: ${state.findings.length}`);
|
|
6853
|
+
} else {
|
|
6854
|
+
logger.info(`Session "${sessionId}": clean (no taint).`);
|
|
6855
|
+
}
|
|
6856
|
+
} else {
|
|
6857
|
+
const sessions = listTaintedSessions(projectRoot);
|
|
6858
|
+
if (sessions.length === 0) {
|
|
6859
|
+
logger.info("No active taint sessions.");
|
|
6860
|
+
} else {
|
|
6861
|
+
logger.info(`Active taint sessions (${sessions.length}):`);
|
|
6862
|
+
for (const id of sessions) {
|
|
6863
|
+
const result = checkTaint(projectRoot, id);
|
|
6864
|
+
if (result.tainted && result.state) {
|
|
6865
|
+
const remaining = Math.max(
|
|
6866
|
+
0,
|
|
6867
|
+
Math.round((new Date(result.state.expiresAt).getTime() - Date.now()) / 1e3 / 60)
|
|
6868
|
+
);
|
|
6869
|
+
logger.info(` ${id}: ${result.state.severity} \u2014 expires in ~${remaining}m`);
|
|
6870
|
+
}
|
|
6871
|
+
}
|
|
6872
|
+
}
|
|
6873
|
+
}
|
|
6874
|
+
});
|
|
6875
|
+
}
|
|
6876
|
+
function createTaintCommand() {
|
|
6877
|
+
const taint = new Command68("taint").description("Manage sentinel session taint state");
|
|
6878
|
+
registerClearCommand(taint);
|
|
6879
|
+
registerStatusCommand(taint);
|
|
6880
|
+
return taint;
|
|
6881
|
+
}
|
|
6882
|
+
|
|
6883
|
+
// src/commands/scan-config.ts
|
|
6884
|
+
import { Command as Command69 } from "commander";
|
|
6885
|
+
import { existsSync as existsSync30, readFileSync as readFileSync18, writeFileSync as writeFileSync15 } from "fs";
|
|
6886
|
+
import { join as join44, relative as relative2 } from "path";
|
|
6887
|
+
var CONFIG_FILES = ["CLAUDE.md", "AGENTS.md", ".gemini/settings.json", "skill.yaml"];
|
|
6888
|
+
function stripHighSeverityPatterns(content, injectionFindings) {
|
|
6889
|
+
const highLines = /* @__PURE__ */ new Set();
|
|
6890
|
+
for (const f of injectionFindings) {
|
|
6891
|
+
if (f.severity === "high" && f.line !== void 0) {
|
|
6892
|
+
highLines.add(f.line);
|
|
6893
|
+
}
|
|
6894
|
+
}
|
|
6895
|
+
if (highLines.size === 0) return { cleaned: content, linesStripped: 0 };
|
|
6896
|
+
const lines = content.split("\n");
|
|
6897
|
+
let linesStripped = 0;
|
|
6898
|
+
for (const lineNum of highLines) {
|
|
6899
|
+
const idx = lineNum - 1;
|
|
6900
|
+
if (idx >= 0 && idx < lines.length) {
|
|
6901
|
+
lines[idx] = "";
|
|
6902
|
+
linesStripped++;
|
|
6903
|
+
}
|
|
6904
|
+
}
|
|
6905
|
+
return { cleaned: lines.join("\n"), linesStripped };
|
|
6906
|
+
}
|
|
6907
|
+
function applyFix(filePath, targetDir, content, injectionFindings) {
|
|
6908
|
+
const hasHighSeverity = injectionFindings.some((f) => f.severity === "high");
|
|
6909
|
+
if (!hasHighSeverity) return;
|
|
6910
|
+
const { cleaned, linesStripped } = stripHighSeverityPatterns(content, injectionFindings);
|
|
6911
|
+
if (linesStripped > 0) {
|
|
6912
|
+
writeFileSync15(filePath, cleaned);
|
|
6913
|
+
logger.info(
|
|
6914
|
+
`scan-config --fix: stripped ${linesStripped} high-severity line(s) from ${relative2(targetDir, filePath).replaceAll("\\", "/")}`
|
|
6915
|
+
);
|
|
6916
|
+
}
|
|
6917
|
+
}
|
|
6918
|
+
function scanSingleFile(filePath, targetDir, scanner, options) {
|
|
6919
|
+
if (!existsSync30(filePath)) return null;
|
|
6920
|
+
let content;
|
|
6921
|
+
try {
|
|
6922
|
+
content = readFileSync18(filePath, "utf8");
|
|
6923
|
+
} catch {
|
|
6924
|
+
return null;
|
|
6925
|
+
}
|
|
6926
|
+
const injectionFindings = scanForInjection(content);
|
|
6927
|
+
const findings = mapInjectionFindings(injectionFindings);
|
|
6928
|
+
const secFindings = scanner.scanContent(content, filePath);
|
|
6929
|
+
findings.push(...mapSecurityFindings(secFindings, findings));
|
|
6930
|
+
if (options.fix) {
|
|
6931
|
+
applyFix(filePath, targetDir, content, injectionFindings);
|
|
6932
|
+
}
|
|
6933
|
+
return {
|
|
6934
|
+
file: relative2(targetDir, filePath).replaceAll("\\", "/"),
|
|
6935
|
+
findings,
|
|
6936
|
+
overallSeverity: computeOverallSeverity(findings)
|
|
6937
|
+
};
|
|
6938
|
+
}
|
|
6939
|
+
async function runScanConfig(targetDir, options) {
|
|
6940
|
+
const scanner = new SecurityScanner(parseSecurityConfig({}));
|
|
6941
|
+
const results = [];
|
|
6942
|
+
for (const configFile of CONFIG_FILES) {
|
|
6943
|
+
const result = scanSingleFile(join44(targetDir, configFile), targetDir, scanner, options);
|
|
6944
|
+
if (result) results.push(result);
|
|
6945
|
+
}
|
|
6946
|
+
return { exitCode: computeScanExitCode(results), results };
|
|
6947
|
+
}
|
|
6948
|
+
function formatTextOutput(result) {
|
|
6949
|
+
if (result.results.length === 0) {
|
|
6950
|
+
logger.info("scan-config: no config files found to scan.");
|
|
6951
|
+
return;
|
|
6952
|
+
}
|
|
6953
|
+
for (const fileResult of result.results) {
|
|
6954
|
+
if (fileResult.findings.length === 0) {
|
|
6955
|
+
logger.info(`${fileResult.file}: clean`);
|
|
6956
|
+
continue;
|
|
6957
|
+
}
|
|
6958
|
+
logger.info(
|
|
6959
|
+
`${fileResult.file}: ${fileResult.overallSeverity} (${fileResult.findings.length} finding(s))`
|
|
6960
|
+
);
|
|
6961
|
+
for (const f of fileResult.findings) {
|
|
6962
|
+
const lineInfo = f.line ? ` (line ${f.line})` : "";
|
|
6963
|
+
logger.info(` [${f.ruleId}] ${f.severity.toUpperCase()}: ${f.message}${lineInfo}`);
|
|
6964
|
+
}
|
|
6965
|
+
}
|
|
6966
|
+
if (result.exitCode === 2) {
|
|
6967
|
+
logger.error("scan-config: HIGH severity findings detected. Execution should be blocked.");
|
|
6968
|
+
} else if (result.exitCode === 1) {
|
|
6969
|
+
logger.warn("scan-config: MEDIUM severity findings detected. Session should be tainted.");
|
|
6970
|
+
}
|
|
6971
|
+
}
|
|
6972
|
+
function createScanConfigCommand() {
|
|
6973
|
+
const command = new Command69("scan-config").description(
|
|
6974
|
+
"Scan CLAUDE.md, AGENTS.md, .gemini/settings.json, and skill.yaml for prompt injection patterns"
|
|
6975
|
+
).option("--path <dir>", "Target directory to scan (default: cwd)").option("--fix", "Strip high-severity patterns from files in-place").action(async (opts, cmd) => {
|
|
6976
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
6977
|
+
const mode = globalOpts.json ? OutputMode.JSON : globalOpts.quiet ? OutputMode.QUIET : OutputMode.TEXT;
|
|
6978
|
+
const result = await runScanConfig(opts.path ?? process.cwd(), { fix: opts.fix });
|
|
6979
|
+
if (mode === OutputMode.JSON) {
|
|
6980
|
+
console.log(JSON.stringify(result, null, 2));
|
|
6981
|
+
} else if (mode !== OutputMode.QUIET) {
|
|
6982
|
+
formatTextOutput(result);
|
|
6983
|
+
}
|
|
6984
|
+
process.exit(result.exitCode);
|
|
6985
|
+
});
|
|
6986
|
+
return command;
|
|
6987
|
+
}
|
|
6988
|
+
|
|
5817
6989
|
// src/index.ts
|
|
5818
6990
|
function createProgram() {
|
|
5819
|
-
const program = new
|
|
6991
|
+
const program = new Command70();
|
|
5820
6992
|
program.name("harness").description("CLI for Harness Engineering toolkit").version(CLI_VERSION).option("-c, --config <path>", "Path to config file").option("--json", "Output as JSON").option("--verbose", "Verbose output").option("--quiet", "Minimal output");
|
|
5821
6993
|
program.addCommand(createValidateCommand());
|
|
5822
6994
|
program.addCommand(createCheckDepsCommand());
|
|
@@ -5859,6 +7031,10 @@ function createProgram() {
|
|
|
5859
7031
|
program.addCommand(createUninstallConstraintsCommand());
|
|
5860
7032
|
program.addCommand(createUninstallCommand());
|
|
5861
7033
|
program.addCommand(createOrchestratorCommand());
|
|
7034
|
+
program.addCommand(createIntegrationsCommand());
|
|
7035
|
+
program.addCommand(createUsageCommand());
|
|
7036
|
+
program.addCommand(createTaintCommand());
|
|
7037
|
+
program.addCommand(createScanConfigCommand());
|
|
5862
7038
|
return program;
|
|
5863
7039
|
}
|
|
5864
7040
|
|