freeharness 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/NOTICE +108 -0
- package/README.md +134 -0
- package/canonical/HARVEST.yaml +858 -0
- package/canonical/PROVENANCE.yaml +740 -0
- package/canonical/agents/README.md +95 -0
- package/canonical/agents/agentic-architect.md +18 -0
- package/canonical/agents/database-reviewer.md +41 -0
- package/canonical/agents/explorer-ai-engineer-analyst.md +65 -0
- package/canonical/agents/pr-reviewer.md +96 -0
- package/canonical/agents/python-reviewer.md +55 -0
- package/canonical/agents/report-formatter.md +19 -0
- package/canonical/agents/security-reviewer.md +53 -0
- package/canonical/agents/test-writer.md +34 -0
- package/canonical/commands/fh-context.md +37 -0
- package/canonical/evals/promptfoo.redteam.yaml +108 -0
- package/canonical/hooks/cost-prices.json +14 -0
- package/canonical/hooks/freeharness-auto-memory.mjs +158 -0
- package/canonical/hooks/freeharness-config-guard.mjs +162 -0
- package/canonical/hooks/freeharness-context-monitor.mjs +198 -0
- package/canonical/hooks/freeharness-converge.mjs +227 -0
- package/canonical/hooks/freeharness-cost-tracker.mjs +1027 -0
- package/canonical/hooks/freeharness-memory-guard.mjs +57 -0
- package/canonical/hooks/freeharness-output-pruner.mjs +333 -0
- package/canonical/hooks/freeharness-precompact.mjs +323 -0
- package/canonical/hooks/freeharness-repeat-tool.mjs +208 -0
- package/canonical/hooks/freeharness-security-guidance.mjs +164 -0
- package/canonical/hooks/freeharness-session-start +583 -0
- package/canonical/hooks/hooks.auto-memory.claude-code.json +16 -0
- package/canonical/hooks/hooks.claude-code.json +17 -0
- package/canonical/hooks/hooks.config-guard.claude-code.json +16 -0
- package/canonical/hooks/hooks.context-monitor.claude-code.json +15 -0
- package/canonical/hooks/hooks.converge.claude-code.json +15 -0
- package/canonical/hooks/hooks.cost-tracker.claude-code.json +37 -0
- package/canonical/hooks/hooks.memory-guard.claude-code.json +16 -0
- package/canonical/hooks/hooks.output-pruner.claude-code.json +16 -0
- package/canonical/hooks/hooks.precompact.claude-code.json +15 -0
- package/canonical/hooks/hooks.repeat-tool.claude-code.json +25 -0
- package/canonical/hooks/hooks.security-guidance.claude-code.json +16 -0
- package/canonical/mcp/servers.json +20 -0
- package/canonical/openspec/config.base.yaml +89 -0
- package/canonical/openspec/schema.spec-driven.yaml +277 -0
- package/canonical/openspec/templates/data-contract.md +36 -0
- package/canonical/openspec/templates/design.md +84 -0
- package/canonical/openspec/templates/eval-plan.md +33 -0
- package/canonical/openspec/templates/follow-ups.md +2 -0
- package/canonical/openspec/templates/proposal.md +45 -0
- package/canonical/openspec/templates/spec.md +42 -0
- package/canonical/openspec/templates/tasks.md +38 -0
- package/canonical/profiles/local-llamacpp.LOCAL-MODELS.md +47 -0
- package/canonical/profiles/local-llamacpp.json +27 -0
- package/canonical/profiles/local-ollama.json +20 -0
- package/canonical/profiles/mcp.json +91 -0
- package/canonical/rules/CLAUDE.md +78 -0
- package/canonical/rules/project-CLAUDE.template.md +62 -0
- package/canonical/rules/routing.md +20 -0
- package/canonical/rules/safety.md +41 -0
- package/canonical/settings/settings.cache-hygiene.claude-code.json +5 -0
- package/canonical/skills/agent-evaluation/SKILL.md +102 -0
- package/canonical/skills/agent-evaluation/assets/evaluation_report_template.md +206 -0
- package/canonical/skills/agent-evaluation/evals/cases.yaml +106 -0
- package/canonical/skills/agent-evaluation/references/dataset-preparation.md +351 -0
- package/canonical/skills/agent-evaluation/references/scorers-constraints.md +165 -0
- package/canonical/skills/agent-evaluation/references/scorers.md +431 -0
- package/canonical/skills/agent-evaluation/references/setup-guide.md +252 -0
- package/canonical/skills/agent-evaluation/references/tracing-integration.md +164 -0
- package/canonical/skills/agent-evaluation/references/troubleshooting.md +514 -0
- package/canonical/skills/agent-evaluation/scripts/analyze_results.py +462 -0
- package/canonical/skills/agent-evaluation/scripts/create_dataset_template.py +382 -0
- package/canonical/skills/agent-evaluation/scripts/list_datasets.py +273 -0
- package/canonical/skills/agent-evaluation/scripts/run_evaluation_template.py +309 -0
- package/canonical/skills/agent-evaluation/scripts/setup_mlflow.py +353 -0
- package/canonical/skills/agent-evaluation/scripts/utils/__init__.py +27 -0
- package/canonical/skills/agent-evaluation/scripts/utils/env_validation.py +123 -0
- package/canonical/skills/agent-evaluation/scripts/utils/tracing_utils.py +108 -0
- package/canonical/skills/agent-evaluation/scripts/validate_agent_tracing.py +148 -0
- package/canonical/skills/agent-evaluation/scripts/validate_auth.py +240 -0
- package/canonical/skills/agent-evaluation/scripts/validate_environment.py +185 -0
- package/canonical/skills/agent-evaluation/scripts/validate_tracing_runtime.py +318 -0
- package/canonical/skills/agentic-architecture/SKILL.md +41 -0
- package/canonical/skills/agentic-architecture/evals/cases.yaml +94 -0
- package/canonical/skills/agentic-architecture/reference/context-engineering.md +75 -0
- package/canonical/skills/agentic-architecture/reference/patterns.md +76 -0
- package/canonical/skills/agentic-architecture/reference/reliability-observability.md +94 -0
- package/canonical/skills/agentic-architecture/reference/review-framework.md +59 -0
- package/canonical/skills/code-review/SKILL.md +52 -0
- package/canonical/skills/code-review/evals/cases.yaml +197 -0
- package/canonical/skills/git-workflow/SKILL.md +41 -0
- package/canonical/skills/git-workflow/evals/cases.yaml +92 -0
- package/canonical/skills/local-code/SKILL.md +24 -0
- package/canonical/skills/local-code/evals/cases.yaml +85 -0
- package/canonical/skills/local-code/scripts/query_llama.py +68 -0
- package/canonical/skills/report-formatting/SKILL.md +62 -0
- package/canonical/skills/report-formatting/evals/cases.yaml +83 -0
- package/canonical/skills/report-formatting/reference/ARCHITECTURE.md +135 -0
- package/canonical/skills/report-formatting/reference/HELPERS.md +120 -0
- package/canonical/skills/report-formatting/reference/PPTX.md +136 -0
- package/canonical/skills/report-formatting/reference/RELEASE.md +99 -0
- package/canonical/skills/report-formatting/reference/TEMPLATES.md +48 -0
- package/canonical/skills/report-formatting/reference/authoring.md +66 -0
- package/canonical/skills/report-formatting/scripts/architecture-template.md +271 -0
- package/canonical/skills/report-formatting/scripts/assets/claro-logo.png +0 -0
- package/canonical/skills/report-formatting/scripts/assets/claro-wordmark.svg +1 -0
- package/canonical/skills/report-formatting/scripts/build-architecture.js +57 -0
- package/canonical/skills/report-formatting/scripts/build-md-claro.js +224 -0
- package/canonical/skills/report-formatting/scripts/build-release.js +51 -0
- package/canonical/skills/report-formatting/scripts/check-env.js +58 -0
- package/canonical/skills/report-formatting/scripts/docmodel.js +283 -0
- package/canonical/skills/report-formatting/scripts/example-build.js +119 -0
- package/canonical/skills/report-formatting/scripts/example_build_ArchitectureReview_docx.js +195 -0
- package/canonical/skills/report-formatting/scripts/example_build_ExecutiveReport_docx.js +114 -0
- package/canonical/skills/report-formatting/scripts/package.json +17 -0
- package/canonical/skills/report-formatting/scripts/pptx/example_build_StatusReport_pptx.py +223 -0
- package/canonical/skills/report-formatting/scripts/pptx/example_build_WeeklyStatus_pptx.py +245 -0
- package/canonical/skills/report-formatting/scripts/pptx/pptx_office.py +144 -0
- package/canonical/skills/report-formatting/scripts/pptx/requirements.txt +7 -0
- package/canonical/skills/report-formatting/scripts/pptx/status_report_blocks.py +193 -0
- package/canonical/skills/report-formatting/scripts/pptx/theme_claro_pptx.py +473 -0
- package/canonical/skills/report-formatting/scripts/release-template.md +80 -0
- package/canonical/skills/report-formatting/scripts/render-mermaid.js +105 -0
- package/canonical/skills/report-formatting/scripts/theme-claro.js +403 -0
- package/canonical/skills/subagent-driven-development/SKILL.md +60 -0
- package/canonical/skills/subagent-driven-development/evals/cases.yaml +208 -0
- package/canonical/skills/subagent-driven-development/reference/brief.md +91 -0
- package/canonical/skills/subagent-driven-development/reference/handoff.md +118 -0
- package/canonical/skills/subagent-driven-development/scripts/validate-handoff.mjs +850 -0
- package/canonical/skills/systematic-debugging/SKILL.md +44 -0
- package/canonical/skills/systematic-debugging/evals/cases.yaml +90 -0
- package/canonical/skills/test-driven-development/SKILL.md +42 -0
- package/canonical/skills/test-driven-development/evals/cases.yaml +86 -0
- package/canonical/skills/test-driven-development/reference/writing-good-tests.md +52 -0
- package/canonical/skills/verification-before-completion/SKILL.md +53 -0
- package/canonical/skills/verification-before-completion/evals/cases.yaml +107 -0
- package/dist/adapters/content/language-lint.d.ts +46 -0
- package/dist/adapters/content/language-lint.js +191 -0
- package/dist/adapters/harness/claude-code.d.ts +57 -0
- package/dist/adapters/harness/claude-code.js +385 -0
- package/dist/adapters/harness/index.d.ts +3 -0
- package/dist/adapters/harness/index.js +17 -0
- package/dist/adapters/harness/opencode.d.ts +7 -0
- package/dist/adapters/harness/opencode.js +166 -0
- package/dist/adapters/harness/plugin.d.ts +22 -0
- package/dist/adapters/harness/plugin.js +257 -0
- package/dist/adapters/memory/doctor.d.ts +480 -0
- package/dist/adapters/memory/doctor.js +1217 -0
- package/dist/adapters/memory/vendored.d.ts +150 -0
- package/dist/adapters/memory/vendored.js +487 -0
- package/dist/adapters/package/node.d.ts +9 -0
- package/dist/adapters/package/node.js +20 -0
- package/dist/adapters/prompt/readline.d.ts +19 -0
- package/dist/adapters/prompt/readline.js +53 -0
- package/dist/adapters/sync/diff.d.ts +43 -0
- package/dist/adapters/sync/diff.js +380 -0
- package/dist/cli/cleanup.d.ts +86 -0
- package/dist/cli/cleanup.js +382 -0
- package/dist/cli/cost.d.ts +304 -0
- package/dist/cli/cost.js +501 -0
- package/dist/cli/doctor.d.ts +205 -0
- package/dist/cli/doctor.js +654 -0
- package/dist/cli/executor.d.ts +5 -0
- package/dist/cli/executor.js +48 -0
- package/dist/cli/plugin-validate.d.ts +37 -0
- package/dist/cli/plugin-validate.js +256 -0
- package/dist/cli/prefix.d.ts +68 -0
- package/dist/cli/prefix.js +218 -0
- package/dist/cli/profile.d.ts +170 -0
- package/dist/cli/profile.js +285 -0
- package/dist/cli/runner.d.ts +10 -0
- package/dist/cli/runner.js +27 -0
- package/dist/cli/uninstall.d.ts +65 -0
- package/dist/cli/uninstall.js +147 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +989 -0
- package/dist/infra/dogfood.d.ts +31 -0
- package/dist/infra/dogfood.js +51 -0
- package/dist/infra/fsutil.d.ts +40 -0
- package/dist/infra/fsutil.js +143 -0
- package/dist/infra/manifest.d.ts +76 -0
- package/dist/infra/manifest.js +92 -0
- package/dist/infra/paths.d.ts +19 -0
- package/dist/infra/paths.js +74 -0
- package/dist/infra/routing.d.ts +4 -0
- package/dist/infra/routing.js +7 -0
- package/node_modules/@freeharness/core/dist/context.d.ts +30 -0
- package/node_modules/@freeharness/core/dist/context.js +1 -0
- package/node_modules/@freeharness/core/dist/index.d.ts +5 -0
- package/node_modules/@freeharness/core/dist/index.js +6 -0
- package/node_modules/@freeharness/core/dist/planner.d.ts +13 -0
- package/node_modules/@freeharness/core/dist/planner.js +18 -0
- package/node_modules/@freeharness/core/dist/ports.d.ts +90 -0
- package/node_modules/@freeharness/core/dist/ports.js +1 -0
- package/node_modules/@freeharness/core/dist/registry.d.ts +41 -0
- package/node_modules/@freeharness/core/dist/registry.js +124 -0
- package/node_modules/@freeharness/core/dist/tiers.d.ts +30 -0
- package/node_modules/@freeharness/core/dist/tiers.js +99 -0
- package/node_modules/@freeharness/core/package.json +34 -0
- package/node_modules/@freeharness/sdd/PROVENANCE.yaml +175 -0
- package/node_modules/@freeharness/sdd/dist/src/bin/openspec.d.ts +2 -0
- package/node_modules/@freeharness/sdd/dist/src/bin/openspec.js +166 -0
- package/node_modules/@freeharness/sdd/dist/src/index.d.ts +14 -0
- package/node_modules/@freeharness/sdd/dist/src/index.js +11 -0
- package/node_modules/@freeharness/sdd/dist/src/init.d.ts +39 -0
- package/node_modules/@freeharness/sdd/dist/src/init.js +190 -0
- package/node_modules/@freeharness/sdd/dist/src/root-resolver.d.ts +8 -0
- package/node_modules/@freeharness/sdd/dist/src/root-resolver.js +32 -0
- package/node_modules/@freeharness/sdd/dist/src/shim/archive.d.ts +81 -0
- package/node_modules/@freeharness/sdd/dist/src/shim/archive.js +483 -0
- package/node_modules/@freeharness/sdd/dist/src/shim/context.d.ts +4 -0
- package/node_modules/@freeharness/sdd/dist/src/shim/context.js +48 -0
- package/node_modules/@freeharness/sdd/dist/src/shim/follow-up.d.ts +57 -0
- package/node_modules/@freeharness/sdd/dist/src/shim/follow-up.js +279 -0
- package/node_modules/@freeharness/sdd/dist/src/shim/instructions.d.ts +67 -0
- package/node_modules/@freeharness/sdd/dist/src/shim/instructions.js +460 -0
- package/node_modules/@freeharness/sdd/dist/src/shim/list.d.ts +5 -0
- package/node_modules/@freeharness/sdd/dist/src/shim/list.js +95 -0
- package/node_modules/@freeharness/sdd/dist/src/shim/new-change.d.ts +6 -0
- package/node_modules/@freeharness/sdd/dist/src/shim/new-change.js +150 -0
- package/node_modules/@freeharness/sdd/dist/src/shim/schemas.d.ts +4 -0
- package/node_modules/@freeharness/sdd/dist/src/shim/schemas.js +23 -0
- package/node_modules/@freeharness/sdd/dist/src/shim/shared.d.ts +56 -0
- package/node_modules/@freeharness/sdd/dist/src/shim/shared.js +140 -0
- package/node_modules/@freeharness/sdd/dist/src/shim/status.d.ts +8 -0
- package/node_modules/@freeharness/sdd/dist/src/shim/status.js +85 -0
- package/node_modules/@freeharness/sdd/dist/src/shim/validate.d.ts +6 -0
- package/node_modules/@freeharness/sdd/dist/src/shim/validate.js +229 -0
- package/node_modules/@freeharness/sdd/dist/src/validate-change.d.ts +40 -0
- package/node_modules/@freeharness/sdd/dist/src/validate-change.js +114 -0
- package/node_modules/@freeharness/sdd/dist/src/validate.d.ts +24 -0
- package/node_modules/@freeharness/sdd/dist/src/validate.js +72 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/apply/specs-apply.d.ts +130 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/apply/specs-apply.js +981 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/change-metadata.d.ts +46 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/change-metadata.js +128 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/change-status-policy.d.ts +26 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/change-status-policy.js +32 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/file-system.d.ts +50 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/file-system.js +394 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/global-config.d.ts +15 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/global-config.js +49 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/graph.d.ts +71 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/graph.js +164 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/index.d.ts +11 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/index.js +21 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/instruction-loader.d.ts +102 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/instruction-loader.js +230 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/outputs.d.ts +18 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/outputs.js +138 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/resolver.d.ts +82 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/resolver.js +333 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/schema.d.ts +12 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/schema.js +110 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/state.d.ts +11 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/state.js +32 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/task-progress.d.ts +33 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/task-progress.js +149 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/types.d.ts +39 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/types.js +49 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/command-generation/adapters/claude.d.ts +12 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/command-generation/adapters/claude.js +31 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/command-generation/adapters/index.d.ts +10 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/command-generation/adapters/index.js +10 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/command-generation/adapters/opencode.d.ts +12 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/command-generation/adapters/opencode.js +26 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/command-generation/generator.d.ts +28 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/command-generation/generator.js +41 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/command-generation/index.d.ts +23 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/command-generation/index.js +25 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/command-generation/invocation.d.ts +72 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/command-generation/invocation.js +76 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/command-generation/registry.d.ts +39 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/command-generation/registry.js +53 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/command-generation/types.d.ts +62 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/command-generation/types.js +7 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/command-generation/yaml.d.ts +35 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/command-generation/yaml.js +47 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/command-references.d.ts +33 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/command-references.js +55 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/shared/allowed-tools.d.ts +11 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/shared/allowed-tools.js +11 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/shared/skill-generation.d.ts +48 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/shared/skill-generation.js +99 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/skill-templates.d.ts +19 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/skill-templates.js +18 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/types.d.ts +18 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/types.js +4 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/apply-change.d.ts +19 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/apply-change.js +207 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/archive-change.d.ts +12 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/archive-change.js +409 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/bulk-archive-change.d.ts +9 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/bulk-archive-change.js +673 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/continue-change.d.ts +9 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/continue-change.js +230 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/explore.d.ts +12 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/explore.js +550 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/feedback.d.ts +8 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/feedback.js +107 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/ff-change.d.ts +12 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/ff-change.js +228 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/new-change.d.ts +9 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/new-change.js +147 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/onboard.d.ts +9 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/onboard.js +571 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/propose.d.ts +12 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/propose.js +322 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/store-selection.d.ts +7 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/store-selection.js +7 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/sync-specs.d.ts +9 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/sync-specs.js +518 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/update-change.d.ts +9 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/update-change.js +176 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/verify-change.d.ts +9 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/verify-change.js +344 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/constants.d.ts +39 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/constants.js +54 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/delta-resolution.d.ts +35 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/delta-resolution.js +171 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/parsers/change-parser.d.ts +27 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/parsers/change-parser.js +210 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/parsers/code-fence.d.ts +14 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/parsers/code-fence.js +48 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/parsers/markdown-parser.d.ts +22 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/parsers/markdown-parser.js +186 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/parsers/requirement-blocks.d.ts +65 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/parsers/requirement-blocks.js +409 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/parsers/requirement-text.d.ts +55 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/parsers/requirement-text.js +98 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/parsers/spec-structure.d.ts +8 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/parsers/spec-structure.js +77 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/purpose-placeholder.d.ts +44 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/purpose-placeholder.js +116 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/schemas/base.schema.d.ts +12 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/schemas/base.schema.js +17 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/schemas/change.schema.d.ts +72 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/schemas/change.schema.js +30 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/schemas/index.d.ts +3 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/schemas/index.js +3 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/schemas/spec.schema.d.ts +17 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/schemas/spec.schema.js +14 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/spec-discovery.d.ts +39 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/spec-discovery.js +114 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/task-lines.d.ts +45 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/task-lines.js +105 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/task-numbering.d.ts +15 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/task-numbering.js +56 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/types.d.ts +17 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/types.js +1 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/validator.d.ts +94 -0
- package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/validator.js +756 -0
- package/node_modules/@freeharness/sdd/package.json +57 -0
- package/node_modules/@freeharness/sdd/vendor/openspec/LICENSE.upstream +22 -0
- package/node_modules/@freeharness/sdd/vendor/openspec/assets/schema.upstream.yaml +232 -0
- package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/README.md +19 -0
- package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-apply-change/SKILL.md +187 -0
- package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-archive-change/SKILL.md +181 -0
- package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-bulk-archive-change/SKILL.md +338 -0
- package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-continue-change/SKILL.md +117 -0
- package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-explore/SKILL.md +342 -0
- package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-ff-change/SKILL.md +116 -0
- package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-new-change/SKILL.md +76 -0
- package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-onboard/SKILL.md +560 -0
- package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-propose/SKILL.md +162 -0
- package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-sync-specs/SKILL.md +261 -0
- package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-update-change/SKILL.md +90 -0
- package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-verify-change/SKILL.md +174 -0
- package/package.json +70 -0
- package/provenance/canonical.PROVENANCE.yaml +740 -0
- package/provenance/claude-mem.LICENSE.upstream +202 -0
- package/provenance/memory.PROVENANCE.yaml +399 -0
- package/provenance/sdd.PROVENANCE.yaml +175 -0
- package/vendor-licenses/caveman.LICENSE +27 -0
- package/vendor-licenses/deepseek-harness.LICENSE +21 -0
- package/vendor-licenses/ecc.LICENSE +21 -0
- package/vendor-licenses/pi.LICENSE +21 -0
- package/vendor-licenses/superpowers.LICENSE +21 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ClaudeCodeAdapter } from "./claude-code.js";
|
|
2
|
+
import { OpenCodeAdapter } from "./opencode.js";
|
|
3
|
+
import { PluginAdapter } from "./plugin.js";
|
|
4
|
+
export function getAdapter(target) {
|
|
5
|
+
switch (target) {
|
|
6
|
+
case "claude-code":
|
|
7
|
+
return new ClaudeCodeAdapter();
|
|
8
|
+
case "opencode":
|
|
9
|
+
return new OpenCodeAdapter();
|
|
10
|
+
case "plugin":
|
|
11
|
+
return new PluginAdapter();
|
|
12
|
+
default: {
|
|
13
|
+
const exhaustive = target;
|
|
14
|
+
throw new Error(`No adapter for target: ${String(exhaustive)}`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Capability } from "@freeharness/core";
|
|
2
|
+
import type { AdapterResult, InstallContext } from "@freeharness/core";
|
|
3
|
+
import type { HarnessPort } from "@freeharness/core";
|
|
4
|
+
export declare class OpenCodeAdapter implements HarnessPort {
|
|
5
|
+
readonly target: "opencode";
|
|
6
|
+
apply(cap: Capability, ctx: InstallContext): Promise<AdapterResult>;
|
|
7
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { copyDir, isJunkEntry, readText, writeText } from "../../infra/fsutil.js";
|
|
5
|
+
import { routingSourcePath } from "../../infra/routing.js";
|
|
6
|
+
import { frontmatterHasKey, rewriteAgentModel, stripAgentKeys } from "@freeharness/core";
|
|
7
|
+
import { initProject } from "@freeharness/sdd/init";
|
|
8
|
+
import { vendoredSddBin } from "../../infra/paths.js";
|
|
9
|
+
import { seedProjectEvidence } from "./claude-code.js";
|
|
10
|
+
/**
|
|
11
|
+
* Whether any canonical agent (excluding README.md) carries a `memory:` front matter key.
|
|
12
|
+
* Reads the source tree directly, independent of `dryRun`, so a preview names the same
|
|
13
|
+
* drop a real run would perform (invariant 2: pass 1 previews pass 2 exactly). Skips the
|
|
14
|
+
* same entries `copyDir` skips (`isJunkEntry`), and asks `frontmatterHasKey`, the same
|
|
15
|
+
* reader `stripAgentKeys` strips by, so the note can never name a drop that never happened.
|
|
16
|
+
* @param agentsSrc The canonical `agents` directory.
|
|
17
|
+
* @returns True when at least one agent file's front matter sets `memory:`.
|
|
18
|
+
*/
|
|
19
|
+
function anyAgentCarriesMemoryKey(agentsSrc) {
|
|
20
|
+
if (!fs.existsSync(agentsSrc))
|
|
21
|
+
return false;
|
|
22
|
+
const walk = (dir) => {
|
|
23
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
24
|
+
if (isJunkEntry(entry.name))
|
|
25
|
+
continue;
|
|
26
|
+
const abs = path.join(dir, entry.name);
|
|
27
|
+
if (entry.isDirectory()) {
|
|
28
|
+
if (walk(abs))
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
else if (entry.name !== "README.md" && frontmatterHasKey(readText(abs), "memory")) {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return false;
|
|
36
|
+
};
|
|
37
|
+
return walk(agentsSrc);
|
|
38
|
+
}
|
|
39
|
+
// OpenCode adapter: emits AGENTS.md + best-effort skill/agent layouts; no session-start hook (OpenCode has no hook contract we emit to).
|
|
40
|
+
export class OpenCodeAdapter {
|
|
41
|
+
target = "opencode";
|
|
42
|
+
async apply(cap, ctx) {
|
|
43
|
+
const res = { capabilityId: cap.id, target: this.target, written: [], actions: [], notes: [] };
|
|
44
|
+
const C = ctx.canonicalDir;
|
|
45
|
+
const P = ctx.projectRoot;
|
|
46
|
+
const dry = ctx.dryRun;
|
|
47
|
+
// OpenCode global (user) scope is not supported; emitting capabilities are project-only.
|
|
48
|
+
if (ctx.scope === "user" && ["rules", "skills", "agents", "safety"].includes(cap.id)) {
|
|
49
|
+
res.notes.push("OpenCode global (user) scope not supported; use --scope project for OpenCode.");
|
|
50
|
+
return res;
|
|
51
|
+
}
|
|
52
|
+
switch (cap.id) {
|
|
53
|
+
case "rules": {
|
|
54
|
+
// No SessionStart hook to read the routing file from disk, so AGENTS.md carries the
|
|
55
|
+
// table itself, appended after the rules with one blank line between them.
|
|
56
|
+
const rules = readText(path.join(C, "rules", "CLAUDE.md")).replace(/\n+$/, "\n");
|
|
57
|
+
const routing = readText(routingSourcePath(C));
|
|
58
|
+
res.written.push(writeText(path.join(P, "AGENTS.md"), `${rules}\n${routing}`, dry));
|
|
59
|
+
res.notes.push("OpenCode uses AGENTS.md; no session-start pointer hook emitted, so the routing table is appended to AGENTS.md instead of read from a file beside a hook.");
|
|
60
|
+
seedProjectEvidence(P, dry, res);
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
case "skills": {
|
|
64
|
+
res.written.push(...copyDir(path.join(C, "skills"), path.join(P, ".opencode", "skills"), dry, {
|
|
65
|
+
transform: (c) => stripAgentKeys(c, ["disable-model-invocation", "context", "agent", "model"]),
|
|
66
|
+
transformOnly: (n) => n === "SKILL.md",
|
|
67
|
+
}));
|
|
68
|
+
res.notes.push("Skills emitted to .opencode/skills (verify against your OpenCode version).");
|
|
69
|
+
const rfScripts = path.join(P, ".opencode", "skills", "report-formatting", "scripts");
|
|
70
|
+
res.actions.push({
|
|
71
|
+
description: "Install report-formatting .docx deps (docx, image-size)",
|
|
72
|
+
command: "npm install --no-audit --no-fund",
|
|
73
|
+
cwd: rfScripts,
|
|
74
|
+
optional: true,
|
|
75
|
+
});
|
|
76
|
+
res.actions.push({
|
|
77
|
+
description: "Install report-formatting .pptx deps (python-pptx, lxml, Pillow, defusedxml)",
|
|
78
|
+
command: "python3 -m pip install -r pptx/requirements.txt --break-system-packages",
|
|
79
|
+
cwd: rfScripts,
|
|
80
|
+
optional: true,
|
|
81
|
+
});
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
case "agents": {
|
|
85
|
+
const agentsSrc = path.join(C, "agents");
|
|
86
|
+
const droppedMemoryKey = anyAgentCarriesMemoryKey(agentsSrc);
|
|
87
|
+
res.written.push(...copyDir(agentsSrc, path.join(P, ".opencode", "agent"), dry, {
|
|
88
|
+
skip: (name) => name === "README.md",
|
|
89
|
+
transform: (content) => stripAgentKeys(rewriteAgentModel(content, this.target), ["memory"]),
|
|
90
|
+
}));
|
|
91
|
+
res.notes.push("Agent model tiers mapped to ${OPENCODE_*_MODEL} placeholders; fill them in.");
|
|
92
|
+
if (droppedMemoryKey)
|
|
93
|
+
res.notes.push("memory: dropped from the agent copy (OpenCode has no such front matter key).");
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
case "safety":
|
|
97
|
+
res.written.push(writeText(path.join(P, ".freeharness", "safety.md"), readText(path.join(C, "rules", "safety.md")), dry));
|
|
98
|
+
break;
|
|
99
|
+
case "sdd": {
|
|
100
|
+
const src = path.join(C, "openspec");
|
|
101
|
+
const dest = path.join(os.homedir(), ".local", "share", "openspec", "schemas", "spec-driven");
|
|
102
|
+
res.actions.push({
|
|
103
|
+
description: "Install spec-driven schema override (user-global)",
|
|
104
|
+
command: `mkdir -p "${dest}/templates" && cp "${src}/schema.spec-driven.yaml" "${dest}/schema.yaml" && cp -R "${src}/templates/." "${dest}/templates/"`,
|
|
105
|
+
});
|
|
106
|
+
// packages/cli/canonical -> ../../sdd = packages/sdd; the workspace root is two
|
|
107
|
+
// levels above it. Build through the root workspaces (npm -w) so hoisted deps resolve.
|
|
108
|
+
const sddDir = path.join(C, "..", "..", "sdd");
|
|
109
|
+
const workspaceRoot = path.join(sddDir, "..", "..");
|
|
110
|
+
if (fs.existsSync(path.join(sddDir, "package.json"))) {
|
|
111
|
+
const sddBin = path.join(sddDir, "dist", "src", "bin", "openspec.js");
|
|
112
|
+
res.actions.push({
|
|
113
|
+
description: "Expose the vendored `openspec` on PATH via ~/.local/bin (shadows the system openspec; ensure ~/.local/bin is on PATH)",
|
|
114
|
+
command: `npm install --no-audit --no-fund -w @freeharness/sdd && npm run build -w @freeharness/sdd && chmod +x "${sddBin}" && mkdir -p "$HOME/.local/bin" && ln -sf "${sddBin}" "$HOME/.local/bin/openspec"`,
|
|
115
|
+
cwd: workspaceRoot,
|
|
116
|
+
optional: true,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
else if (vendoredSddBin()) {
|
|
120
|
+
// npm/npx install: the sdd package is bundled and already built; link its bin.
|
|
121
|
+
const sddBin = vendoredSddBin();
|
|
122
|
+
res.actions.push({
|
|
123
|
+
description: "Expose the bundled `openspec` on PATH via ~/.local/bin (shadows the system openspec; ensure ~/.local/bin is on PATH)",
|
|
124
|
+
command: `chmod +x "${sddBin}" && mkdir -p "$HOME/.local/bin" && ln -sf "${sddBin}" "$HOME/.local/bin/openspec"`,
|
|
125
|
+
optional: true,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
res.notes.push("Vendored @freeharness/sdd not resolvable; expose the vendored openspec bin manually.");
|
|
130
|
+
}
|
|
131
|
+
if (ctx.scope !== "project") {
|
|
132
|
+
res.notes.push("SDD is project-scoped: run `freeharness bootstrap --target opencode` inside each project.");
|
|
133
|
+
}
|
|
134
|
+
else if (dry) {
|
|
135
|
+
res.notes.push("Would initialize OpenSpec for OpenCode (/opsx commands + skills + openspec/) via the vendored engine.");
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
const r = await initProject({ target: "opencode", projectDir: P });
|
|
139
|
+
res.written.push(...r.written);
|
|
140
|
+
res.notes.push(...r.notes);
|
|
141
|
+
}
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
case "memory": {
|
|
145
|
+
// Seam only. The vendored engine is trimmed to the Claude Code hook platform
|
|
146
|
+
// (memory-slim), and the upstream `npx claude-mem install --ide opencode` bridge
|
|
147
|
+
// would install a SECOND upstream daemon beside ours (same worker port). Nothing
|
|
148
|
+
// is installed for OpenCode until FreeHarness ships its own OpenCode plugin wiring.
|
|
149
|
+
res.notes.push("memory: OpenCode wiring not shipped (seam). The vendored free-mem plugin targets Claude Code; recall from OpenCode is a v4+ item.");
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
case "mcp": {
|
|
153
|
+
const cfg = JSON.parse(readText(path.join(C, "mcp", "servers.json")));
|
|
154
|
+
const names = cfg.servers.filter((s) => s.targets.includes("opencode")).map((s) => s.name);
|
|
155
|
+
res.notes.push(`Configure MCP servers in your OpenCode config: ${names.join(", ") || "(none)"}.`);
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
case "cost":
|
|
159
|
+
res.notes.push("cost: OpenCode has no Stop/SessionEnd hook contract with a transcript path; usage tracking is Claude Code only for now.");
|
|
160
|
+
break;
|
|
161
|
+
default:
|
|
162
|
+
res.notes.push(`No opencode action for capability '${cap.id}'.`);
|
|
163
|
+
}
|
|
164
|
+
return res;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { AdapterResult, Capability, HarnessPort, InstallContext } from "@freeharness/core";
|
|
2
|
+
/** Plugin directory name inside the project root. */
|
|
3
|
+
export declare const PLUGIN_DIR_NAME = "freeharness-plugin";
|
|
4
|
+
/** Marketplace name (the `@marketplace` suffix in `claude plugin install freeharness@...`). */
|
|
5
|
+
export declare const MARKETPLACE_NAME = "freeharness-local";
|
|
6
|
+
/**
|
|
7
|
+
* Emits the FreeHarness content as a Claude Code plugin directory under
|
|
8
|
+
* `<projectRoot>/freeharness-plugin/` with a sibling local marketplace manifest.
|
|
9
|
+
*/
|
|
10
|
+
export declare class PluginAdapter implements HarnessPort {
|
|
11
|
+
readonly target: "plugin";
|
|
12
|
+
private scaffolded;
|
|
13
|
+
apply(cap: Capability, ctx: InstallContext): Promise<AdapterResult>;
|
|
14
|
+
/**
|
|
15
|
+
* Writes the plugin manifest, README, and the local marketplace manifest.
|
|
16
|
+
* @param projectRoot Directory that receives `.claude-plugin/marketplace.json`.
|
|
17
|
+
* @param root The plugin directory.
|
|
18
|
+
* @param dry When true nothing is written.
|
|
19
|
+
* @returns Paths written (or planned).
|
|
20
|
+
*/
|
|
21
|
+
private scaffold;
|
|
22
|
+
}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
// Plugin adapter: arranges the canonical content into the official Claude Code plugin
|
|
2
|
+
// layout (format taken from the claude-code fork's plugins/: .claude-plugin/plugin.json,
|
|
3
|
+
// agents/, skills/, hooks/hooks.json, .mcp.json, README.md) plus a local marketplace
|
|
4
|
+
// manifest so the emitted directory installs with `claude plugin marketplace add <dir>`.
|
|
5
|
+
// Project scope only: a plugin is a directory, not an install into ~/.claude.
|
|
6
|
+
import fs from "node:fs";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import { rewriteAgentModel } from "@freeharness/core";
|
|
9
|
+
import { copyDir, copyExecutable, readText, writeText } from "../../infra/fsutil.js";
|
|
10
|
+
import { cliVersion } from "../../infra/paths.js";
|
|
11
|
+
import { ROUTING_FILE, routingSourcePath } from "../../infra/routing.js";
|
|
12
|
+
import { CONFIG_GUARD_FLAGS, CONTEXT_MONITOR_FLAGS, OUTPUT_PRUNER_FLAGS, REPEAT_TOOL_FLAGS, SESSION_END_TIMEOUT_S, SESSION_START_MATCHER } from "./claude-code.js";
|
|
13
|
+
/** Plugin directory name inside the project root. */
|
|
14
|
+
export const PLUGIN_DIR_NAME = "freeharness-plugin";
|
|
15
|
+
/** Marketplace name (the `@marketplace` suffix in `claude plugin install freeharness@...`). */
|
|
16
|
+
export const MARKETPLACE_NAME = "freeharness-local";
|
|
17
|
+
/**
|
|
18
|
+
* Additively merges a hook entry into the plugin's hooks.json (dedupe by command; the file is
|
|
19
|
+
* created with the official `description` + `hooks` shape).
|
|
20
|
+
* @param file Absolute path of hooks/hooks.json.
|
|
21
|
+
* @param event Hook event name (SessionStart, PreToolUse, ...).
|
|
22
|
+
* @param entry Entry to add.
|
|
23
|
+
* @param dryRun When true nothing is written.
|
|
24
|
+
* @returns Whether the entry was added (false when already present).
|
|
25
|
+
*/
|
|
26
|
+
function mergePluginHook(file, event, entry, dryRun) {
|
|
27
|
+
let doc = {
|
|
28
|
+
description: "FreeHarness hooks: SessionStart pointer (+ post-compaction re-orientation), PreCompact checkpoint, memory guard, config guard, repeat-tool reminder, security guidance (advisory), cost tracker, output pruner, context monitor, auto-memory (flag-gated). Additive; only the memory guard and the config guard deny.",
|
|
29
|
+
hooks: {},
|
|
30
|
+
};
|
|
31
|
+
if (fs.existsSync(file))
|
|
32
|
+
doc = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
33
|
+
doc.hooks ??= {};
|
|
34
|
+
doc.hooks[event] ??= [];
|
|
35
|
+
const cmd = entry.hooks[0].command;
|
|
36
|
+
const exists = doc.hooks[event].some((e) => e.hooks.some((h) => h.command === cmd));
|
|
37
|
+
if (!exists)
|
|
38
|
+
doc.hooks[event].push(entry);
|
|
39
|
+
if (!dryRun && !exists)
|
|
40
|
+
writeText(file, JSON.stringify(doc, null, 2) + "\n", false);
|
|
41
|
+
return !exists;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Emits the FreeHarness content as a Claude Code plugin directory under
|
|
45
|
+
* `<projectRoot>/freeharness-plugin/` with a sibling local marketplace manifest.
|
|
46
|
+
*/
|
|
47
|
+
export class PluginAdapter {
|
|
48
|
+
target = "plugin";
|
|
49
|
+
scaffolded = false;
|
|
50
|
+
async apply(cap, ctx) {
|
|
51
|
+
const res = { capabilityId: cap.id, target: this.target, written: [], actions: [], notes: [] };
|
|
52
|
+
if (ctx.scope !== "project") {
|
|
53
|
+
res.notes.push("plugin target is project-scoped: it writes a plugin directory, not ~/.claude. Use --scope project --project <dir>.");
|
|
54
|
+
return res;
|
|
55
|
+
}
|
|
56
|
+
const C = ctx.canonicalDir;
|
|
57
|
+
const dry = ctx.dryRun;
|
|
58
|
+
const root = path.join(ctx.projectRoot, PLUGIN_DIR_NAME);
|
|
59
|
+
const hooksDir = path.join(root, "hooks");
|
|
60
|
+
const hooksFile = path.join(hooksDir, "hooks.json");
|
|
61
|
+
const hookCmd = (script, runner = "node", flags = "") => `${runner} "\${CLAUDE_PLUGIN_ROOT}/hooks/${script}"${flags ? ` ${flags}` : ""}`;
|
|
62
|
+
/** Copies a canonical hook script and registers it under each event (dedupe by command). */
|
|
63
|
+
const emitHook = (script, flags, events) => {
|
|
64
|
+
res.written.push(writeText(path.join(hooksDir, script), readText(path.join(C, "hooks", script)), dry));
|
|
65
|
+
for (const e of events) {
|
|
66
|
+
const entry = { ...(e.matcher ? { matcher: e.matcher } : {}), hooks: [{ type: "command", command: hookCmd(script, "node", flags), ...(e.event === "SessionEnd" ? { timeout: SESSION_END_TIMEOUT_S } : {}) }] };
|
|
67
|
+
mergePluginHook(hooksFile, e.event, entry, dry);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
if (!this.scaffolded) {
|
|
71
|
+
this.scaffolded = true;
|
|
72
|
+
res.written.push(...this.scaffold(ctx.projectRoot, root, dry));
|
|
73
|
+
}
|
|
74
|
+
switch (cap.id) {
|
|
75
|
+
case "rules": {
|
|
76
|
+
// CLAUDE.md is not a plugin component: the behavior rules stay a per-user install.
|
|
77
|
+
res.notes.push("rules: CLAUDE.md cannot ship inside a plugin; install the behavior rules with `freeharness init --scope user --only rules` (the plugin carries the SessionStart pointer + memory guard only).");
|
|
78
|
+
res.written.push(copyExecutable(path.join(C, "hooks", "freeharness-session-start"), path.join(hooksDir, "freeharness-session-start"), dry));
|
|
79
|
+
const ss = { matcher: SESSION_START_MATCHER, hooks: [{ type: "command", command: hookCmd("freeharness-session-start", "bash"), async: false }] };
|
|
80
|
+
mergePluginHook(hooksFile, "SessionStart", ss, dry);
|
|
81
|
+
// Same file name and same directory as the claude-code target: the hook script reads
|
|
82
|
+
// it from its own directory regardless of which target installed it.
|
|
83
|
+
res.written.push(writeText(path.join(hooksDir, ROUTING_FILE), readText(routingSourcePath(C)), dry));
|
|
84
|
+
res.written.push(writeText(path.join(hooksDir, "freeharness-memory-guard.mjs"), readText(path.join(C, "hooks", "freeharness-memory-guard.mjs")), dry));
|
|
85
|
+
const mg = { matcher: "Write|Edit|MultiEdit", hooks: [{ type: "command", command: hookCmd("freeharness-memory-guard.mjs") }] };
|
|
86
|
+
mergePluginHook(hooksFile, "PreToolUse", mg, dry);
|
|
87
|
+
emitHook("freeharness-config-guard.mjs", CONFIG_GUARD_FLAGS, [{ event: "PreToolUse", matcher: "Edit|Write|MultiEdit|Bash" }]);
|
|
88
|
+
emitHook("freeharness-repeat-tool.mjs", REPEAT_TOOL_FLAGS, [{ event: "PreToolUse" }, { event: "UserPromptSubmit" }]);
|
|
89
|
+
emitHook("freeharness-precompact.mjs", "", [{ event: "PreCompact" }]);
|
|
90
|
+
res.written.push(hooksFile);
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
case "skills":
|
|
94
|
+
res.written.push(...copyDir(path.join(C, "skills"), path.join(root, "skills"), dry, {
|
|
95
|
+
transform: (c) => rewriteAgentModel(c, this.target),
|
|
96
|
+
transformOnly: (n) => n === "SKILL.md",
|
|
97
|
+
}));
|
|
98
|
+
res.notes.push("skills: report-formatting runtime deps (docx/python-pptx) are installed by the user after the plugin loads (see the plugin README).");
|
|
99
|
+
break;
|
|
100
|
+
case "agents":
|
|
101
|
+
res.written.push(...copyDir(path.join(C, "agents"), path.join(root, "agents"), dry, {
|
|
102
|
+
skip: (n) => n === "README.md",
|
|
103
|
+
transform: (c) => rewriteAgentModel(c, this.target),
|
|
104
|
+
}));
|
|
105
|
+
break;
|
|
106
|
+
case "safety": {
|
|
107
|
+
res.notes.push("safety: rules/safety.md is a rules file (not a plugin component); only the advisory security-guidance hook ships in the plugin.");
|
|
108
|
+
res.written.push(writeText(path.join(hooksDir, "freeharness-security-guidance.mjs"), readText(path.join(C, "hooks", "freeharness-security-guidance.mjs")), dry));
|
|
109
|
+
const sg = { matcher: "Write|Edit|MultiEdit", hooks: [{ type: "command", command: hookCmd("freeharness-security-guidance.mjs") }] };
|
|
110
|
+
mergePluginHook(hooksFile, "PreToolUse", sg, dry);
|
|
111
|
+
res.written.push(hooksFile);
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
case "mcp": {
|
|
115
|
+
const cfg = JSON.parse(readText(path.join(C, "mcp", "servers.json")));
|
|
116
|
+
const mcpServers = {};
|
|
117
|
+
for (const s of cfg.servers) {
|
|
118
|
+
if (!s.targets.includes("claude-code"))
|
|
119
|
+
continue;
|
|
120
|
+
mcpServers[s.name] = { command: s.command, args: s.args };
|
|
121
|
+
}
|
|
122
|
+
res.written.push(writeText(path.join(root, ".mcp.json"), JSON.stringify({ mcpServers }, null, 2) + "\n", dry));
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
case "cost": {
|
|
126
|
+
res.written.push(writeText(path.join(hooksDir, "freeharness-cost-tracker.mjs"), readText(path.join(C, "hooks", "freeharness-cost-tracker.mjs")), dry));
|
|
127
|
+
res.written.push(writeText(path.join(hooksDir, "cost-prices.json"), readText(path.join(C, "hooks", "cost-prices.json")), dry));
|
|
128
|
+
mergePluginHook(hooksFile, "Stop", { hooks: [{ type: "command", command: hookCmd("freeharness-cost-tracker.mjs") }] }, dry);
|
|
129
|
+
// A plugin hook's timeout never raises Claude Code's shared 1.5 s SessionEnd budget
|
|
130
|
+
// (only settings-file timeouts do); the user sets CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS
|
|
131
|
+
// for long transcripts. The timeout still bounds this hook inside the budget.
|
|
132
|
+
mergePluginHook(hooksFile, "SessionEnd", { hooks: [{ type: "command", command: hookCmd("freeharness-cost-tracker.mjs"), timeout: SESSION_END_TIMEOUT_S }] }, dry);
|
|
133
|
+
// SubagentStop mirrors SessionEnd (same command, same timeout): the cost tracker reads
|
|
134
|
+
// the subagent's own transcript, which a long dispatch pushes past the default budget.
|
|
135
|
+
mergePluginHook(hooksFile, "SubagentStop", { hooks: [{ type: "command", command: hookCmd("freeharness-cost-tracker.mjs"), timeout: SESSION_END_TIMEOUT_S }] }, dry);
|
|
136
|
+
res.notes.push("cost: SessionEnd hooks run under Claude Code's shared 1.5 s budget, which a plugin timeout does not raise; set CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS=10000 for long transcripts.");
|
|
137
|
+
emitHook("freeharness-output-pruner.mjs", OUTPUT_PRUNER_FLAGS, [{ event: "PostToolUse", matcher: "Bash" }]);
|
|
138
|
+
emitHook("freeharness-context-monitor.mjs", CONTEXT_MONITOR_FLAGS, [{ event: "Stop" }]);
|
|
139
|
+
res.written.push(hooksFile);
|
|
140
|
+
res.notes.push("cost: the hook prices with hooks/cost-prices.json next to it (placeholders; edit, or point FREEHARNESS_COST_PRICES at your table).");
|
|
141
|
+
res.notes.push("cost: a plugin cannot set settings keys; add promptCacheTtl / subagentPromptCacheTtl = 1h to ~/.claude/settings.json by hand or with `freeharness init --scope user --only cost`.");
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
case "sdd":
|
|
145
|
+
res.notes.push("sdd: not a plugin component (OpenSpec is a per-project init through the vendored engine): run `freeharness bootstrap` inside each project.");
|
|
146
|
+
emitHook("freeharness-converge.mjs", "", [{ event: "Stop" }]);
|
|
147
|
+
res.notes.push("sdd: the converge Stop hook ships (a no-op until <project>/.freeharness/converge.json exists).");
|
|
148
|
+
res.written.push(hooksFile);
|
|
149
|
+
break;
|
|
150
|
+
case "memory":
|
|
151
|
+
res.notes.push("memory: not a plugin component (free-mem is its own plugin, built and registered by `freeharness init --scope user --only memory`).");
|
|
152
|
+
emitHook("freeharness-auto-memory.mjs", "", [{ event: "SessionEnd" }]);
|
|
153
|
+
res.notes.push("memory: the auto-memory tier-0 hook ships (SessionEnd; a no-op until FREEHARNESS_AUTO_MEMORY=on).");
|
|
154
|
+
res.written.push(hooksFile);
|
|
155
|
+
break;
|
|
156
|
+
default:
|
|
157
|
+
res.notes.push(`No plugin action for capability '${cap.id}'.`);
|
|
158
|
+
}
|
|
159
|
+
return res;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Writes the plugin manifest, README, and the local marketplace manifest.
|
|
163
|
+
* @param projectRoot Directory that receives `.claude-plugin/marketplace.json`.
|
|
164
|
+
* @param root The plugin directory.
|
|
165
|
+
* @param dry When true nothing is written.
|
|
166
|
+
* @returns Paths written (or planned).
|
|
167
|
+
*/
|
|
168
|
+
scaffold(projectRoot, root, dry) {
|
|
169
|
+
const version = cliVersion();
|
|
170
|
+
const manifest = {
|
|
171
|
+
name: "freeharness",
|
|
172
|
+
version,
|
|
173
|
+
description: "FreeHarness for Claude Code: execution/verification skills (TDD, subagent-driven development, debugging, code/PR review, git workflow, report-formatting), reviewer/design/exploration agents, and the SessionStart, memory-guard, config-guard, repeat-tool, security-guidance, cost-tracker, output-pruner and context-monitor hooks.",
|
|
174
|
+
author: { name: "André Negrelli" },
|
|
175
|
+
homepage: "https://github.com/anegrelli/FreeHarness",
|
|
176
|
+
repository: "https://github.com/anegrelli/FreeHarness",
|
|
177
|
+
license: "MIT",
|
|
178
|
+
keywords: ["claude-code", "agentic", "spec-driven", "openspec", "skills", "agents", "hooks", "harness"],
|
|
179
|
+
};
|
|
180
|
+
const marketplace = {
|
|
181
|
+
$schema: "https://json.schemastore.org/claude-code-marketplace.json",
|
|
182
|
+
name: MARKETPLACE_NAME,
|
|
183
|
+
version,
|
|
184
|
+
description: "Local marketplace for the FreeHarness plugin emitted by `freeharness init --target plugin`.",
|
|
185
|
+
owner: { name: "André Negrelli" },
|
|
186
|
+
plugins: [
|
|
187
|
+
{
|
|
188
|
+
name: "freeharness",
|
|
189
|
+
description: manifest.description,
|
|
190
|
+
version,
|
|
191
|
+
author: manifest.author,
|
|
192
|
+
source: `./${PLUGIN_DIR_NAME}`,
|
|
193
|
+
category: "productivity",
|
|
194
|
+
},
|
|
195
|
+
],
|
|
196
|
+
};
|
|
197
|
+
return [
|
|
198
|
+
writeText(path.join(root, ".claude-plugin", "plugin.json"), JSON.stringify(manifest, null, 2) + "\n", dry),
|
|
199
|
+
writeText(path.join(root, "README.md"), pluginReadme(version, projectRoot), dry),
|
|
200
|
+
writeText(path.join(projectRoot, ".claude-plugin", "marketplace.json"), JSON.stringify(marketplace, null, 2) + "\n", dry),
|
|
201
|
+
];
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Builds the plugin README (install mechanism per the official plugins README: marketplace
|
|
206
|
+
* add + install, or `--plugin-dir` for local development).
|
|
207
|
+
* @param version Plugin version.
|
|
208
|
+
* @param marketplaceDir Directory holding `.claude-plugin/marketplace.json`.
|
|
209
|
+
* @returns Markdown text.
|
|
210
|
+
*/
|
|
211
|
+
function pluginReadme(version, marketplaceDir) {
|
|
212
|
+
return `# FreeHarness plugin (v${version})
|
|
213
|
+
|
|
214
|
+
Generated by \`freeharness init --target plugin\`. Official Claude Code plugin layout:
|
|
215
|
+
|
|
216
|
+
\`\`\`
|
|
217
|
+
${PLUGIN_DIR_NAME}/
|
|
218
|
+
├── .claude-plugin/plugin.json # manifest
|
|
219
|
+
├── agents/ # reviewer, design, and planning agents (model tiers resolved)
|
|
220
|
+
├── skills/ # execution/verification skills
|
|
221
|
+
├── hooks/hooks.json # SessionStart pointer, memory/config guards, repeat-tool, security guidance, cost tracker, output pruner, context monitor
|
|
222
|
+
├── hooks/*.mjs, freeharness-session-start
|
|
223
|
+
├── .mcp.json # MCP servers (context7)
|
|
224
|
+
└── README.md
|
|
225
|
+
\`\`\`
|
|
226
|
+
|
|
227
|
+
## Install
|
|
228
|
+
|
|
229
|
+
Claude Code installs plugins from a marketplace. This directory's parent is a local marketplace
|
|
230
|
+
(\`.claude-plugin/marketplace.json\`, name \`${MARKETPLACE_NAME}\`):
|
|
231
|
+
|
|
232
|
+
\`\`\`
|
|
233
|
+
claude plugin marketplace add ${marketplaceDir}
|
|
234
|
+
claude plugin install freeharness@${MARKETPLACE_NAME}
|
|
235
|
+
\`\`\`
|
|
236
|
+
|
|
237
|
+
Inside a session the same steps are \`/plugin marketplace add <path>\` and
|
|
238
|
+
\`/plugin install freeharness@${MARKETPLACE_NAME}\`. For local development, load the directory
|
|
239
|
+
directly without a marketplace: \`claude --plugin-dir ${path.join(marketplaceDir, PLUGIN_DIR_NAME)}\`.
|
|
240
|
+
Publishing: push the marketplace directory to a git repo and add it with
|
|
241
|
+
\`claude plugin marketplace add <owner>/<repo>\`.
|
|
242
|
+
|
|
243
|
+
Verify with \`claude plugin list\` (or \`/plugin\`): the \`freeharness\` plugin should be listed, with
|
|
244
|
+
its agents, skills, and hooks.
|
|
245
|
+
|
|
246
|
+
## What is not in the plugin
|
|
247
|
+
|
|
248
|
+
- Behavior rules (\`CLAUDE.md\`) and \`safety.md\`: not plugin components. Install them per user with
|
|
249
|
+
\`freeharness init --scope user --only rules,safety\`.
|
|
250
|
+
- SDD (OpenSpec \`/opsx\`): per-project init, \`freeharness bootstrap\` inside the project.
|
|
251
|
+
- Memory (free-mem): its own plugin, \`freeharness init --scope user --only memory\`.
|
|
252
|
+
- Report-formatting runtime deps: after install run \`npm install\` in
|
|
253
|
+
\`skills/report-formatting/scripts\` and \`python3 -m pip install -r skills/report-formatting/scripts/pptx/requirements.txt --break-system-packages\`.
|
|
254
|
+
|
|
255
|
+
Hook commands use \`\${CLAUDE_PLUGIN_ROOT}\` so the plugin works from any install location.
|
|
256
|
+
`;
|
|
257
|
+
}
|