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,191 @@
|
|
|
1
|
+
// Language lint for model-facing prose (rules, skills, agents, commands, hook messages):
|
|
2
|
+
// flags gerund preambles and Portuguese text so `doctor --context-budget` can report the
|
|
3
|
+
// first violation per file. Advisory only: findings are lines, never an exit status.
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
/** File extensions scanned as prose. */
|
|
7
|
+
const PROSE_EXT = new Set([".md", ".txt", ".markdown"]);
|
|
8
|
+
/** First-person progressive preambles and progressive openers at the start of a line. */
|
|
9
|
+
const GERUND_PATTERNS = [
|
|
10
|
+
/\b(?:I|We)(?:'ll| will| would) be \w+ing\b/i,
|
|
11
|
+
/\b(?:I|We)(?:'m| am|'re| are) going to\b/i,
|
|
12
|
+
/\b(?:I|We)(?:'ll| will) (?:now |just )?\w+ing\b/i,
|
|
13
|
+
/\bLet me\b/,
|
|
14
|
+
/\bLet's (?:start|begin) by\b/i,
|
|
15
|
+
/^(?:Now|Currently|Still|First,?|Next,?) \w+ing\b/,
|
|
16
|
+
/^\w+ing (?:the|your|this|that|these|those|it|now|to)\b.*(?:\.\.\.|…)\s*$/,
|
|
17
|
+
];
|
|
18
|
+
/**
|
|
19
|
+
* Portuguese function words that rarely occur in English prose. Ambiguous tokens shared
|
|
20
|
+
* with English (`a`, `as`, `no`, `do`, `me`) are left out on purpose.
|
|
21
|
+
*/
|
|
22
|
+
const PT_STOPWORDS = new Set([
|
|
23
|
+
"que", "não", "nao", "para", "com", "uma", "um", "você", "voce", "está", "esta", "também", "tambem",
|
|
24
|
+
"mas", "por", "como", "mais", "dos", "das", "nos", "nas", "ou", "se", "ao", "à", "são", "sao", "foi",
|
|
25
|
+
"ser", "isso", "esse", "essa", "este", "pelo", "pela", "sobre", "quando", "onde", "então", "entao",
|
|
26
|
+
"porque", "já", "muito", "sem", "entre", "até", "ate", "seu", "sua", "seus", "suas", "ele", "ela",
|
|
27
|
+
"eles", "elas", "nós", "da", "de", "em", "na", "os", "o", "e", "é", "há", "deve", "devem", "pode",
|
|
28
|
+
"podem", "cada", "todo", "toda", "todos", "todas", "aqui", "depois", "antes", "ainda", "apenas",
|
|
29
|
+
]);
|
|
30
|
+
/** Minimum words on a line before the stopword ratio is measured. */
|
|
31
|
+
const PT_MIN_WORDS = 6;
|
|
32
|
+
/** Stopword share at which a line counts as Portuguese. */
|
|
33
|
+
const PT_RATIO = 0.25;
|
|
34
|
+
/**
|
|
35
|
+
* Removes inline code spans and double-quoted examples so rule text that quotes a banned
|
|
36
|
+
* phrase ("I will be doing") is not itself flagged.
|
|
37
|
+
* @param line Raw line.
|
|
38
|
+
* @returns Line with quoted material blanked.
|
|
39
|
+
*/
|
|
40
|
+
function stripQuoted(line) {
|
|
41
|
+
return line.replace(/`[^`]*`/g, " ").replace(/"[^"]*"/g, " ").replace(/“[^”]*”/g, " ");
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Strips markdown list, heading and emphasis markers from the start of a line.
|
|
45
|
+
* @param line Raw line.
|
|
46
|
+
* @returns Text after the leading markup.
|
|
47
|
+
*/
|
|
48
|
+
function stripMarkup(line) {
|
|
49
|
+
return line.replace(/^\s*(?:[-*+]|\d+[.)]|#{1,6}|\|)\s*/, "").replace(/^\*\*?|\*\*?$/g, "").trim();
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Whether the line reads as Portuguese by stopword share.
|
|
53
|
+
* @param text Line without quoted material.
|
|
54
|
+
* @returns True above the threshold on lines with enough words.
|
|
55
|
+
*/
|
|
56
|
+
function isPortuguese(text) {
|
|
57
|
+
const words = text.toLowerCase().match(/(?<![\p{L}\p{N}_])\p{L}+(?![\p{L}\p{N}_])/gu) ?? [];
|
|
58
|
+
if (words.length < PT_MIN_WORDS)
|
|
59
|
+
return false;
|
|
60
|
+
const hits = words.filter((w) => PT_STOPWORDS.has(w)).length;
|
|
61
|
+
return hits / words.length >= PT_RATIO;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Reads the `lang:` key of a YAML front-matter block at the top of the text.
|
|
65
|
+
* @param lines File lines.
|
|
66
|
+
* @returns The declared language, or null.
|
|
67
|
+
*/
|
|
68
|
+
function frontMatterLang(lines) {
|
|
69
|
+
if (lines[0]?.trim() !== "---")
|
|
70
|
+
return null;
|
|
71
|
+
for (let i = 1; i < lines.length; i += 1) {
|
|
72
|
+
if (lines[i].trim() === "---")
|
|
73
|
+
return null;
|
|
74
|
+
const m = /^lang:\s*["']?([\w-]+)/.exec(lines[i]);
|
|
75
|
+
if (m)
|
|
76
|
+
return m[1];
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Lints one prose file.
|
|
82
|
+
*
|
|
83
|
+
* Skipped: YAML front matter, fenced code, blockquotes, and lines under a
|
|
84
|
+
* `<!-- lang: pt-BR -->` marker (until `<!-- lang: en -->`). A file whose front matter
|
|
85
|
+
* declares `lang: pt-BR` is skipped entirely.
|
|
86
|
+
* @param file Absolute path.
|
|
87
|
+
* @param text File contents (read from `file` when omitted).
|
|
88
|
+
* @returns Every finding in line order (the caller keeps the first).
|
|
89
|
+
*/
|
|
90
|
+
export function lintText(file, text) {
|
|
91
|
+
const body = text ?? fs.readFileSync(file, "utf8");
|
|
92
|
+
const lines = body.split("\n");
|
|
93
|
+
const declared = frontMatterLang(lines);
|
|
94
|
+
if (declared && /^pt/i.test(declared))
|
|
95
|
+
return [];
|
|
96
|
+
const out = [];
|
|
97
|
+
let inFrontMatter = lines[0]?.trim() === "---";
|
|
98
|
+
let inFence = false;
|
|
99
|
+
let inPt = false;
|
|
100
|
+
for (let i = inFrontMatter ? 1 : 0; i < lines.length; i += 1) {
|
|
101
|
+
const raw = lines[i];
|
|
102
|
+
const trimmed = raw.trim();
|
|
103
|
+
if (inFrontMatter) {
|
|
104
|
+
if (trimmed === "---")
|
|
105
|
+
inFrontMatter = false;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
if (/^(?:```|~~~)/.test(trimmed)) {
|
|
109
|
+
inFence = !inFence;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (inFence)
|
|
113
|
+
continue;
|
|
114
|
+
const marker = /<!--\s*lang:\s*([\w-]+)\s*-->/.exec(trimmed);
|
|
115
|
+
if (marker) {
|
|
116
|
+
inPt = /^pt/i.test(marker[1]);
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (inPt || trimmed.startsWith(">") || trimmed === "")
|
|
120
|
+
continue;
|
|
121
|
+
const text = stripMarkup(stripQuoted(raw));
|
|
122
|
+
if (text === "")
|
|
123
|
+
continue;
|
|
124
|
+
const gerund = GERUND_PATTERNS.find((p) => p.test(text));
|
|
125
|
+
if (gerund) {
|
|
126
|
+
out.push({ file, line: i + 1, pattern: `gerund-preamble (${gerund.exec(text)?.[0] ?? ""})`, text: trimmed });
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (isPortuguese(text))
|
|
130
|
+
out.push({ file, line: i + 1, pattern: "portuguese", text: trimmed });
|
|
131
|
+
}
|
|
132
|
+
return out;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Walks a directory for prose files.
|
|
136
|
+
* @param dir Root.
|
|
137
|
+
* @returns Absolute file paths, sorted.
|
|
138
|
+
*/
|
|
139
|
+
export function proseFiles(dir) {
|
|
140
|
+
const out = [];
|
|
141
|
+
if (!fs.existsSync(dir))
|
|
142
|
+
return out;
|
|
143
|
+
const walk = (d) => {
|
|
144
|
+
for (const e of fs.readdirSync(d, { withFileTypes: true })) {
|
|
145
|
+
if (e.name === "node_modules" || e.name.startsWith(".git"))
|
|
146
|
+
continue;
|
|
147
|
+
const p = path.join(d, e.name);
|
|
148
|
+
if (e.isDirectory())
|
|
149
|
+
walk(p);
|
|
150
|
+
else if (PROSE_EXT.has(path.extname(e.name)))
|
|
151
|
+
out.push(p);
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
walk(dir);
|
|
155
|
+
return out.sort();
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Lints every prose file under the given roots (files or directories) and keeps the first
|
|
159
|
+
* finding per file.
|
|
160
|
+
* @param roots Files or directories to scan; missing paths are ignored.
|
|
161
|
+
* @returns Findings and the scanned-file count.
|
|
162
|
+
*/
|
|
163
|
+
export function lintPaths(roots) {
|
|
164
|
+
const files = new Set();
|
|
165
|
+
for (const r of roots) {
|
|
166
|
+
if (!fs.existsSync(r))
|
|
167
|
+
continue;
|
|
168
|
+
if (fs.statSync(r).isDirectory())
|
|
169
|
+
for (const f of proseFiles(r))
|
|
170
|
+
files.add(f);
|
|
171
|
+
else if (PROSE_EXT.has(path.extname(r)))
|
|
172
|
+
files.add(r);
|
|
173
|
+
}
|
|
174
|
+
const findings = [];
|
|
175
|
+
for (const f of [...files].sort()) {
|
|
176
|
+
const first = lintText(f)[0];
|
|
177
|
+
if (first)
|
|
178
|
+
findings.push(first);
|
|
179
|
+
}
|
|
180
|
+
return { findings, scanned: files.size };
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Renders the language section of `doctor --context-budget`.
|
|
184
|
+
* @param report Result of {@link lintPaths}.
|
|
185
|
+
* @returns Printable lines: one OK line, or one FAIL line per flagged file.
|
|
186
|
+
*/
|
|
187
|
+
export function languageLines(report) {
|
|
188
|
+
if (report.findings.length === 0)
|
|
189
|
+
return [`OK language: ${report.scanned} prose files, no gerund preamble or Portuguese outside declared pt-BR sections`];
|
|
190
|
+
return report.findings.map((f) => `FAIL language: ${f.file}:${f.line} ${f.pattern}: ${f.text.length > 100 ? `${f.text.slice(0, 97)}...` : f.text}`);
|
|
191
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { Capability } from "@freeharness/core";
|
|
2
|
+
import type { AdapterResult, InstallContext } from "@freeharness/core";
|
|
3
|
+
import type { HarnessPort } from "@freeharness/core";
|
|
4
|
+
export { ROUTING_FILE } from "../../infra/routing.js";
|
|
5
|
+
/**
|
|
6
|
+
* Capabilities that live in the machine-global install (~/.claude). At project scope the
|
|
7
|
+
* adapter refuses them unless `allowProjectCopies` is set: Claude Code loads user and
|
|
8
|
+
* project dirs together, so a project copy shows every agent/skill/hook twice.
|
|
9
|
+
*/
|
|
10
|
+
export declare const GLOBAL_ONLY_CAPABILITIES: readonly string[];
|
|
11
|
+
/** Project slash command written by `bootstrap` (rules, project scope): fills the CLAUDE.md placeholders. */
|
|
12
|
+
export declare const FH_CONTEXT_COMMAND: string;
|
|
13
|
+
/** SessionStart matcher: compact/resume/fork get the re-orientation block, startup/clear the pointer. */
|
|
14
|
+
export declare const SESSION_START_MATCHER = "startup|clear|compact|resume|fork";
|
|
15
|
+
/** Flags baked into the hook commands (the fragment defaults; edit settings.json to tune). */
|
|
16
|
+
export declare const OUTPUT_PRUNER_FLAGS = "--max-bytes 8192 --max-lines 200";
|
|
17
|
+
export declare const CONTEXT_MONITOR_FLAGS = "--budget 200000 --thresholds 60,85";
|
|
18
|
+
export declare const CONFIG_GUARD_FLAGS = "--ttl 30";
|
|
19
|
+
export declare const REPEAT_TOOL_FLAGS = "--thresholds 3,5,8 --preview-chars 500";
|
|
20
|
+
/**
|
|
21
|
+
* Per-hook timeout (seconds) on SessionEnd entries. Claude Code gives SessionEnd hooks a
|
|
22
|
+
* shared 1.5 s budget by default and raises it to the highest `timeout` set in a settings
|
|
23
|
+
* file (up to 60 s); the cost tracker reads the whole transcript, which a long session
|
|
24
|
+
* pushes past 1.5 s.
|
|
25
|
+
*/
|
|
26
|
+
export declare const SESSION_END_TIMEOUT_S = 10;
|
|
27
|
+
/** Every hook script FreeHarness emits (rules + safety + cost capabilities), for doctor and uninstall. */
|
|
28
|
+
export declare const HOOK_SCRIPTS: readonly string[];
|
|
29
|
+
export declare class ClaudeCodeAdapter implements HarnessPort {
|
|
30
|
+
readonly target: "claude-code";
|
|
31
|
+
apply(cap: Capability, ctx: InstallContext): Promise<AdapterResult>;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Reports and, only once confirmed, strips a trailing `[1m]` suffix from the scope's own
|
|
35
|
+
* `model` value. The emitted defaults never force or replace a model family: a value with no
|
|
36
|
+
* suffix, or a bracketed suffix other than `1m` (case-insensitive), is left untouched. A `[1m]`
|
|
37
|
+
* value is named in pass 1 (`dry`) as what a confirmed pass 2 would write (the same id, suffix
|
|
38
|
+
* removed); the rewrite itself happens only in pass 2.
|
|
39
|
+
* @param settingsPath The scope's settings.json.
|
|
40
|
+
* @param dry Dry run (pass 1: report only).
|
|
41
|
+
* @param res Adapter result to report into.
|
|
42
|
+
*/
|
|
43
|
+
export declare function reportAndStripLongContextModel(settingsPath: string, dry: boolean, res: AdapterResult): void;
|
|
44
|
+
/**
|
|
45
|
+
* Creates the `.freeharness/sdd/` workspace and seeds the project `.gitignore` at project
|
|
46
|
+
* scope: the transient SDD workspace line and the two agent-memory tree lines
|
|
47
|
+
* (`.claude/agent-memory/`, `.claude/agent-memory-local/`). `.claude/` is tracked in a
|
|
48
|
+
* FreeHarness-activated project, so an agent's `MEMORY.md` (written by Claude Code under
|
|
49
|
+
* `memory: project` / `memory: local`) would otherwise become a committed, attacker-supplied
|
|
50
|
+
* instruction channel injected into that agent's next prompt. `.gitignore` is never ours to
|
|
51
|
+
* remove. The durable evidence is `.freeharness/telemetry.jsonl`, appended by the cost-tracker
|
|
52
|
+
* hook at session end; nothing else is seeded or read here.
|
|
53
|
+
* @param projectRoot Project root.
|
|
54
|
+
* @param dry Dry run.
|
|
55
|
+
* @param res Adapter result to report into.
|
|
56
|
+
*/
|
|
57
|
+
export declare function seedProjectEvidence(projectRoot: string, dry: boolean, res: AdapterResult): void;
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { copyDir, copyExecutable, ensureDir, mergeClaudeHook, mergeSettingsKeys, readText, writeText, writeTextIfAbsent } from "../../infra/fsutil.js";
|
|
5
|
+
import { AGENT_MEMORY_GITIGNORE_LINE, AGENT_MEMORY_LOCAL_GITIGNORE_LINE, SDD_DIR, SDD_GITIGNORE_LINE, ensureGitignoreLine, ensureSddDir, } from "../../infra/dogfood.js";
|
|
6
|
+
import { rewriteAgentModel } from "@freeharness/core";
|
|
7
|
+
import { initProject } from "@freeharness/sdd/init";
|
|
8
|
+
import { VendoredMemory, vendoredMemoryEngineDir } from "../memory/vendored.js";
|
|
9
|
+
import { vendoredSddBin } from "../../infra/paths.js";
|
|
10
|
+
import { ROUTING_FILE, routingSourcePath } from "../../infra/routing.js";
|
|
11
|
+
// Re-exported: callers keep importing the routing file name from the claude-code adapter.
|
|
12
|
+
export { ROUTING_FILE } from "../../infra/routing.js";
|
|
13
|
+
/**
|
|
14
|
+
* Capabilities that live in the machine-global install (~/.claude). At project scope the
|
|
15
|
+
* adapter refuses them unless `allowProjectCopies` is set: Claude Code loads user and
|
|
16
|
+
* project dirs together, so a project copy shows every agent/skill/hook twice.
|
|
17
|
+
*/
|
|
18
|
+
export const GLOBAL_ONLY_CAPABILITIES = ["skills", "agents", "safety", "cost", "memory", "mcp"];
|
|
19
|
+
/** Project slash command written by `bootstrap` (rules, project scope): fills the CLAUDE.md placeholders. */
|
|
20
|
+
export const FH_CONTEXT_COMMAND = path.join(".claude", "commands", "fh-context.md");
|
|
21
|
+
/** SessionStart matcher: compact/resume/fork get the re-orientation block, startup/clear the pointer. */
|
|
22
|
+
export const SESSION_START_MATCHER = "startup|clear|compact|resume|fork";
|
|
23
|
+
/** Flags baked into the hook commands (the fragment defaults; edit settings.json to tune). */
|
|
24
|
+
export const OUTPUT_PRUNER_FLAGS = "--max-bytes 8192 --max-lines 200";
|
|
25
|
+
export const CONTEXT_MONITOR_FLAGS = "--budget 200000 --thresholds 60,85";
|
|
26
|
+
export const CONFIG_GUARD_FLAGS = "--ttl 30";
|
|
27
|
+
export const REPEAT_TOOL_FLAGS = "--thresholds 3,5,8 --preview-chars 500";
|
|
28
|
+
/**
|
|
29
|
+
* Per-hook timeout (seconds) on SessionEnd entries. Claude Code gives SessionEnd hooks a
|
|
30
|
+
* shared 1.5 s budget by default and raises it to the highest `timeout` set in a settings
|
|
31
|
+
* file (up to 60 s); the cost tracker reads the whole transcript, which a long session
|
|
32
|
+
* pushes past 1.5 s.
|
|
33
|
+
*/
|
|
34
|
+
export const SESSION_END_TIMEOUT_S = 10;
|
|
35
|
+
/** Every hook script FreeHarness emits (rules + safety + cost capabilities), for doctor and uninstall. */
|
|
36
|
+
export const HOOK_SCRIPTS = [
|
|
37
|
+
"freeharness-session-start",
|
|
38
|
+
"freeharness-memory-guard.mjs",
|
|
39
|
+
"freeharness-config-guard.mjs",
|
|
40
|
+
"freeharness-repeat-tool.mjs",
|
|
41
|
+
"freeharness-security-guidance.mjs",
|
|
42
|
+
"freeharness-cost-tracker.mjs",
|
|
43
|
+
"freeharness-output-pruner.mjs",
|
|
44
|
+
"freeharness-context-monitor.mjs",
|
|
45
|
+
"freeharness-precompact.mjs",
|
|
46
|
+
"freeharness-auto-memory.mjs",
|
|
47
|
+
"freeharness-converge.mjs",
|
|
48
|
+
];
|
|
49
|
+
export class ClaudeCodeAdapter {
|
|
50
|
+
target = "claude-code";
|
|
51
|
+
async apply(cap, ctx) {
|
|
52
|
+
const res = { capabilityId: cap.id, target: this.target, written: [], actions: [], notes: [] };
|
|
53
|
+
const C = ctx.canonicalDir;
|
|
54
|
+
const dry = ctx.dryRun;
|
|
55
|
+
const user = ctx.scope === "user";
|
|
56
|
+
const home = os.homedir();
|
|
57
|
+
// Hooks (and every global-only capability) are emitted at user scope, or into a project
|
|
58
|
+
// only on explicit request. A plain project install writes context + SDD, nothing else.
|
|
59
|
+
const projectCopies = user || ctx.allowProjectCopies === true;
|
|
60
|
+
if (!projectCopies && GLOBAL_ONLY_CAPABILITIES.includes(cap.id)) {
|
|
61
|
+
res.notes.push(`'${cap.id}' is a global capability; installed by install.sh / \`freeharness init --scope user\` (a project copy would be loaded twice). Pass --allow-project-copies to init to force a project copy.`);
|
|
62
|
+
return res;
|
|
63
|
+
}
|
|
64
|
+
const claudeDir = user ? path.join(home, ".claude") : path.join(ctx.projectRoot, ".claude");
|
|
65
|
+
const rulesFile = user ? path.join(home, ".claude", "CLAUDE.md") : path.join(ctx.projectRoot, "CLAUDE.md");
|
|
66
|
+
const skillsDir = path.join(claudeDir, "skills");
|
|
67
|
+
const agentsDir = path.join(claudeDir, "agents");
|
|
68
|
+
const settingsPath = path.join(claudeDir, "settings.json");
|
|
69
|
+
const hooksDir = user
|
|
70
|
+
? path.join(home, ".claude", "freeharness", "hooks")
|
|
71
|
+
: path.join(ctx.projectRoot, ".freeharness", "hooks");
|
|
72
|
+
const safetyFile = user
|
|
73
|
+
? path.join(home, ".claude", "freeharness", "safety.md")
|
|
74
|
+
: path.join(ctx.projectRoot, ".freeharness", "safety.md");
|
|
75
|
+
const ssScript = path.join(hooksDir, "freeharness-session-start");
|
|
76
|
+
const mgScript = path.join(hooksDir, "freeharness-memory-guard.mjs");
|
|
77
|
+
// user scope bakes an absolute path; project scope uses ${CLAUDE_PROJECT_DIR}.
|
|
78
|
+
const ssCmd = user ? `"${ssScript}"` : '"${CLAUDE_PROJECT_DIR}/.freeharness/hooks/freeharness-session-start"';
|
|
79
|
+
const mgCmd = user
|
|
80
|
+
? `node "${mgScript}"`
|
|
81
|
+
: 'node "${CLAUDE_PROJECT_DIR}/.freeharness/hooks/freeharness-memory-guard.mjs"';
|
|
82
|
+
const sgScript = path.join(hooksDir, "freeharness-security-guidance.mjs");
|
|
83
|
+
const sgCmd = user
|
|
84
|
+
? `node "${sgScript}"`
|
|
85
|
+
: 'node "${CLAUDE_PROJECT_DIR}/.freeharness/hooks/freeharness-security-guidance.mjs"';
|
|
86
|
+
const ctScript = path.join(hooksDir, "freeharness-cost-tracker.mjs");
|
|
87
|
+
const ctPrices = path.join(hooksDir, "cost-prices.json");
|
|
88
|
+
const ctCmd = user
|
|
89
|
+
? `node "${ctScript}"`
|
|
90
|
+
: 'node "${CLAUDE_PROJECT_DIR}/.freeharness/hooks/freeharness-cost-tracker.mjs"';
|
|
91
|
+
const hookCmd = (script, flags) => `${user ? `node "${path.join(hooksDir, script)}"` : `node "\${CLAUDE_PROJECT_DIR}/.freeharness/hooks/${script}"`}${flags ? ` ${flags}` : ""}`;
|
|
92
|
+
const emitHook = (script, flags, events, note) => {
|
|
93
|
+
res.written.push(writeText(path.join(hooksDir, script), readText(path.join(C, "hooks", script)), dry));
|
|
94
|
+
const cmd = hookCmd(script, flags);
|
|
95
|
+
let added = false;
|
|
96
|
+
for (const e of events) {
|
|
97
|
+
const entry = { ...(e.matcher ? { matcher: e.matcher } : {}), hooks: [{ type: "command", command: cmd, ...(e.event === "SessionEnd" ? { timeout: SESSION_END_TIMEOUT_S } : {}) }] };
|
|
98
|
+
added = mergeClaudeHook(settingsPath, e.event, entry, dry).added || added;
|
|
99
|
+
}
|
|
100
|
+
res.notes.push(added ? `Added ${note}.` : `${script} hook already present.`);
|
|
101
|
+
};
|
|
102
|
+
switch (cap.id) {
|
|
103
|
+
case "rules": {
|
|
104
|
+
if (user) {
|
|
105
|
+
res.written.push(writeText(rulesFile, readText(path.join(C, "rules", "CLAUDE.md")), dry));
|
|
106
|
+
const tpl = path.join(home, ".claude", "freeharness", "project-CLAUDE.template.md");
|
|
107
|
+
res.written.push(writeText(tpl, readText(path.join(C, "rules", "project-CLAUDE.template.md")), dry));
|
|
108
|
+
res.notes.push("Project CLAUDE.md template shipped to ~/.claude/freeharness/ for /init and per-project scaffolding.");
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
// Project scope seeds CONTEXT (the template), never the global rules, and
|
|
112
|
+
// never clobbers an existing file. Behavior comes from --scope user.
|
|
113
|
+
const seeded = writeTextIfAbsent(rulesFile, readText(path.join(C, "rules", "project-CLAUDE.template.md")), dry);
|
|
114
|
+
res.written.push(seeded.path);
|
|
115
|
+
if (seeded.skipped)
|
|
116
|
+
(res.skipped ??= []).push(seeded.path);
|
|
117
|
+
res.notes.push(seeded.skipped
|
|
118
|
+
? "Project CLAUDE.md already exists; left untouched. Behavior rules come from --scope user."
|
|
119
|
+
: "Seeded project CLAUDE.md from the FreeHarness template; fill in project context. Behavior rules come from --scope user.");
|
|
120
|
+
res.written.push(writeText(path.join(ctx.projectRoot, FH_CONTEXT_COMMAND), readText(path.join(C, "commands", "fh-context.md")), dry));
|
|
121
|
+
res.notes.push("Wrote .claude/commands/fh-context.md: run /fh-context in Claude Code to fill the CLAUDE.md placeholders (never /init).");
|
|
122
|
+
seedProjectEvidence(ctx.projectRoot, dry, res);
|
|
123
|
+
if (!projectCopies) {
|
|
124
|
+
res.notes.push("Hooks are global (~/.claude/settings.json); none merged into the project settings.json.");
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
res.written.push(copyExecutable(path.join(C, "hooks", "freeharness-session-start"), ssScript, dry));
|
|
129
|
+
const ssEntry = { matcher: SESSION_START_MATCHER, hooks: [{ type: "command", command: ssCmd, async: false }] };
|
|
130
|
+
const ss = mergeClaudeHook(settingsPath, "SessionStart", ssEntry, dry);
|
|
131
|
+
res.notes.push(ss.added ? "Added SessionStart pointer hook (additive; claude-mem untouched; compact/resume re-inject the checkpoint)." : "SessionStart hook already present.");
|
|
132
|
+
// Routing table beside the script the hook reads it from.
|
|
133
|
+
res.written.push(writeText(path.join(hooksDir, ROUTING_FILE), readText(routingSourcePath(C)), dry));
|
|
134
|
+
res.written.push(writeText(mgScript, readText(path.join(C, "hooks", "freeharness-memory-guard.mjs")), dry));
|
|
135
|
+
const mgEntry = { matcher: "Write|Edit|MultiEdit", hooks: [{ type: "command", command: mgCmd }] };
|
|
136
|
+
const mg = mergeClaudeHook(settingsPath, "PreToolUse", mgEntry, dry);
|
|
137
|
+
res.notes.push(mg.added ? "Added memory-guard hook (PreToolUse denies memory .md writes; saves tokens)." : "Memory-guard hook already present.");
|
|
138
|
+
emitHook("freeharness-config-guard.mjs", CONFIG_GUARD_FLAGS, [{ event: "PreToolUse", matcher: "Edit|Write|MultiEdit|Bash" }], "config-guard hook (PreToolUse denies settings/.mcp.json/hook/CLAUDE.md writes without .freeharness/allow-config-edit)");
|
|
139
|
+
emitHook("freeharness-repeat-tool.mjs", REPEAT_TOOL_FLAGS, [{ event: "PreToolUse" }, { event: "UserPromptSubmit" }], "repeat-tool hook (PreToolUse reminds at 3/5/8 identical calls; UserPromptSubmit resets; advisory)");
|
|
140
|
+
// Memory-independent: the skeleton is built locally; the worker summary is best effort.
|
|
141
|
+
emitHook("freeharness-precompact.mjs", "", [{ event: "PreCompact" }], "precompact hook (PreCompact writes the checkpoint skeleton + file-op list for SessionStart and asks a running claude-mem worker for a summary; worker down = skipped)");
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
case "skills": {
|
|
145
|
+
res.written.push(...copyDir(path.join(C, "skills"), skillsDir, dry, {
|
|
146
|
+
transform: (c) => rewriteAgentModel(c, this.target),
|
|
147
|
+
transformOnly: (n) => n === "SKILL.md",
|
|
148
|
+
}));
|
|
149
|
+
const rfScripts = path.join(skillsDir, "report-formatting", "scripts");
|
|
150
|
+
res.actions.push({
|
|
151
|
+
description: "Install report-formatting .docx deps (docx, image-size)",
|
|
152
|
+
command: "npm install --no-audit --no-fund",
|
|
153
|
+
cwd: rfScripts,
|
|
154
|
+
optional: true,
|
|
155
|
+
});
|
|
156
|
+
res.actions.push({
|
|
157
|
+
description: "Install report-formatting .pptx deps (python-pptx, lxml, Pillow, defusedxml)",
|
|
158
|
+
command: "python3 -m pip install -r pptx/requirements.txt --break-system-packages",
|
|
159
|
+
cwd: rfScripts,
|
|
160
|
+
optional: true,
|
|
161
|
+
});
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
case "agents":
|
|
165
|
+
res.written.push(...copyDir(path.join(C, "agents"), agentsDir, dry, {
|
|
166
|
+
skip: (n) => n === "README.md",
|
|
167
|
+
transform: (c) => rewriteAgentModel(c, this.target),
|
|
168
|
+
}));
|
|
169
|
+
break;
|
|
170
|
+
case "safety": {
|
|
171
|
+
res.written.push(writeText(safetyFile, readText(path.join(C, "rules", "safety.md")), dry));
|
|
172
|
+
// Advisory PreToolUse hook: warns on vulnerability sinks in Edit/Write content,
|
|
173
|
+
// never blocks. Same additive merge as the memory guard.
|
|
174
|
+
res.written.push(writeText(sgScript, readText(path.join(C, "hooks", "freeharness-security-guidance.mjs")), dry));
|
|
175
|
+
const sgEntry = { matcher: "Write|Edit|MultiEdit", hooks: [{ type: "command", command: sgCmd }] };
|
|
176
|
+
const sg = mergeClaudeHook(settingsPath, "PreToolUse", sgEntry, dry);
|
|
177
|
+
res.notes.push(sg.added ? "Added security-guidance hook (PreToolUse warns on vulnerability sinks; advisory only)." : "Security-guidance hook already present.");
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
case "sdd": {
|
|
181
|
+
const src = path.join(C, "openspec");
|
|
182
|
+
const dest = path.join(home, ".local", "share", "openspec", "schemas", "spec-driven");
|
|
183
|
+
res.actions.push({
|
|
184
|
+
description: "Install spec-driven schema override (user-global)",
|
|
185
|
+
command: `mkdir -p "${dest}/templates" && cp "${src}/schema.spec-driven.yaml" "${dest}/schema.yaml" && cp -R "${src}/templates/." "${dest}/templates/"`,
|
|
186
|
+
});
|
|
187
|
+
// packages/cli/canonical -> ../../sdd = packages/sdd; the workspace root is two
|
|
188
|
+
// levels above it. Build through the root workspaces (npm -w) so hoisted deps resolve.
|
|
189
|
+
const sddDir = path.join(C, "..", "..", "sdd");
|
|
190
|
+
const workspaceRoot = path.join(sddDir, "..", "..");
|
|
191
|
+
if (fs.existsSync(path.join(sddDir, "package.json"))) {
|
|
192
|
+
const sddBin = path.join(sddDir, "dist", "src", "bin", "openspec.js");
|
|
193
|
+
res.actions.push({
|
|
194
|
+
description: "Expose the vendored `openspec` on PATH via ~/.local/bin (shadows the system openspec; ensure ~/.local/bin is on PATH)",
|
|
195
|
+
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"`,
|
|
196
|
+
cwd: workspaceRoot,
|
|
197
|
+
optional: true,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
else if (vendoredSddBin()) {
|
|
201
|
+
// npm/npx install: the sdd package is bundled and already built; link its bin.
|
|
202
|
+
const sddBin = vendoredSddBin();
|
|
203
|
+
res.actions.push({
|
|
204
|
+
description: "Expose the bundled `openspec` on PATH via ~/.local/bin (shadows the system openspec; ensure ~/.local/bin is on PATH)",
|
|
205
|
+
command: `chmod +x "${sddBin}" && mkdir -p "$HOME/.local/bin" && ln -sf "${sddBin}" "$HOME/.local/bin/openspec"`,
|
|
206
|
+
optional: true,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
res.notes.push("Vendored @freeharness/sdd not resolvable; expose the vendored openspec bin manually.");
|
|
211
|
+
}
|
|
212
|
+
if (user) {
|
|
213
|
+
// Converge loop: the Stop hook is global; a project arms it by writing
|
|
214
|
+
// .freeharness/converge.json. Off everywhere else (no file = no-op).
|
|
215
|
+
emitHook("freeharness-converge.mjs", "", [{ event: "Stop" }], "converge hook (Stop blocks with the next unchecked task + Verify line while <project>/.freeharness/converge.json exists and rounds < max_rounds; off without the file)");
|
|
216
|
+
res.notes.push("SDD is project-scoped: run `freeharness bootstrap` inside each project.");
|
|
217
|
+
}
|
|
218
|
+
else if (dry) {
|
|
219
|
+
res.notes.push("Would initialize OpenSpec (/opsx commands + skills + openspec/) via the vendored engine.");
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
const r = await initProject({ target: "claude", projectDir: ctx.projectRoot });
|
|
223
|
+
res.written.push(...r.written);
|
|
224
|
+
res.notes.push(...r.notes);
|
|
225
|
+
}
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
case "memory": {
|
|
229
|
+
// Optional tier-0 spike, off by default (FREEHARNESS_AUTO_MEMORY=on); registered even
|
|
230
|
+
// when the vendored engine is absent because it only reads the dogfood log.
|
|
231
|
+
emitHook("freeharness-auto-memory.mjs", "", [{ event: "SessionEnd" }], "auto-memory hook (SessionEnd curates one line per project into ~/.claude/freeharness/MEMORY.md; OFF until FREEHARNESS_AUTO_MEMORY=on)");
|
|
232
|
+
const engineDir = vendoredMemoryEngineDir(C);
|
|
233
|
+
if (!engineDir) {
|
|
234
|
+
// --bridge fallback: vendored engine absent; upstream installer does the rest.
|
|
235
|
+
res.actions.push({ description: "Install claude-mem plugin + wire hooks/MCP (bridge: vendored engine absent)", command: "npx claude-mem install", optional: true });
|
|
236
|
+
res.notes.push("Vendored engine not found; bridging to upstream `npx claude-mem install`.");
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
// Real plugin install from OUR build (MemoryPort). Dry-run previews every step
|
|
240
|
+
// and writes nothing; the apply pass builds, stages, and registers the plugin.
|
|
241
|
+
const mem = new VendoredMemory(engineDir, this.target);
|
|
242
|
+
const merge = (r) => {
|
|
243
|
+
res.written.push(...r.written);
|
|
244
|
+
res.actions.push(...r.actions);
|
|
245
|
+
res.notes.push(...r.notes);
|
|
246
|
+
};
|
|
247
|
+
const pre = await mem.ensurePrerequisites(ctx);
|
|
248
|
+
merge(pre);
|
|
249
|
+
if (pre.notes.some((n) => n.startsWith("MISSING: Bun"))) {
|
|
250
|
+
res.notes.push("memory: install blocked until Bun is present.");
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
merge(await mem.migrateExisting(ctx));
|
|
254
|
+
merge(await mem.install(ctx.scope, ctx));
|
|
255
|
+
merge(await mem.registerHooks(ctx.scope, ctx));
|
|
256
|
+
res.notes.push("Worker start + recall are verified on the machine (needs the Claude Agent SDK key / network); not exercised by the installer.");
|
|
257
|
+
break;
|
|
258
|
+
}
|
|
259
|
+
case "mcp": {
|
|
260
|
+
const cfg = JSON.parse(readText(path.join(C, "mcp", "servers.json")));
|
|
261
|
+
const sc = user ? "user" : "local";
|
|
262
|
+
for (const s of cfg.servers) {
|
|
263
|
+
if (!s.targets.includes("claude-code"))
|
|
264
|
+
continue;
|
|
265
|
+
res.actions.push({
|
|
266
|
+
description: `Register MCP server ${s.name}${s.optional ? " (optional)" : ""}`,
|
|
267
|
+
command: `claude mcp add ${s.name} -s ${sc} -- ${s.command} ${s.args.join(" ")}`,
|
|
268
|
+
optional: true, // a failing/missing `claude` must not abort the whole install
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
case "cost": {
|
|
274
|
+
// Stop + SessionEnd append usage snapshots to cost/usage.jsonl; the price table is
|
|
275
|
+
// seeded once and never overwritten (the user edits it to the account's contract).
|
|
276
|
+
res.written.push(writeText(ctScript, readText(path.join(C, "hooks", "freeharness-cost-tracker.mjs")), dry));
|
|
277
|
+
const prices = writeTextIfAbsent(ctPrices, readText(path.join(C, "hooks", "cost-prices.json")), dry);
|
|
278
|
+
res.written.push(prices.path);
|
|
279
|
+
if (prices.skipped)
|
|
280
|
+
(res.skipped ??= []).push(prices.path);
|
|
281
|
+
const stop = mergeClaudeHook(settingsPath, "Stop", { hooks: [{ type: "command", command: ctCmd }] }, dry);
|
|
282
|
+
const end = mergeClaudeHook(settingsPath, "SessionEnd", { hooks: [{ type: "command", command: ctCmd, timeout: SESSION_END_TIMEOUT_S }] }, dry);
|
|
283
|
+
// SubagentStop mirrors SessionEnd: a long dispatch also pushes the subagent's own
|
|
284
|
+
// transcript past the default budget.
|
|
285
|
+
const subagentStop = mergeClaudeHook(settingsPath, "SubagentStop", { hooks: [{ type: "command", command: ctCmd, timeout: SESSION_END_TIMEOUT_S }] }, dry);
|
|
286
|
+
res.notes.push(stop.added || end.added || subagentStop.added ? "Added cost-tracker hooks (Stop + SessionEnd + SubagentStop append usage/ledger entries; never block)." : "Cost-tracker hooks already present.");
|
|
287
|
+
res.notes.push(prices.skipped ? `Price table kept as is: ${ctPrices} (edit to your contract).` : `Price table seeded at ${ctPrices} — placeholders; edit to your contract. Report: freeharness cost report.`);
|
|
288
|
+
emitHook("freeharness-output-pruner.mjs", OUTPUT_PRUNER_FLAGS, [{ event: "PostToolUse", matcher: "Bash" }], "output-pruner hook (PostToolUse trims Bash output over 8 KB / 200 lines; full copy under ~/.claude/freeharness/tool-output/)");
|
|
289
|
+
emitHook("freeharness-context-monitor.mjs", CONTEXT_MONITOR_FLAGS, [{ event: "Stop" }], "context-monitor hook (Stop warns the user once at 60% and 85% of the context budget)");
|
|
290
|
+
// Cache-hygiene settings: additive, existing keys win.
|
|
291
|
+
const tpl = JSON.parse(readText(path.join(C, "settings", "settings.cache-hygiene.claude-code.json")));
|
|
292
|
+
const merged = mergeSettingsKeys(settingsPath, tpl, dry);
|
|
293
|
+
res.notes.push(merged.added.length > 0
|
|
294
|
+
? `Added cache-hygiene settings ${merged.added.join(", ")} = 1h${merged.kept.length > 0 ? ` (kept existing ${merged.kept.join(", ")})` : ""}; CLAUDE_AUTOCOMPACT_PCT_OVERRIDE stays optional (see the template comment).`
|
|
295
|
+
: `Cache-hygiene settings already present (${merged.kept.join(", ")}); left as they are.`);
|
|
296
|
+
reportAndStripLongContextModel(settingsPath, dry, res);
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
299
|
+
default:
|
|
300
|
+
res.notes.push(`No claude-code action for capability '${cap.id}'.`);
|
|
301
|
+
}
|
|
302
|
+
return res;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Splits a model id on a trailing `[suffix]` tag (e.g. `claude-fable-5-1[1m]`). Mirrors
|
|
307
|
+
* `splitModelSuffix` in `../../cli/doctor.js`, duplicated here rather than shared: adapters
|
|
308
|
+
* never import the cli layer (`scripts/check-arch.mjs`).
|
|
309
|
+
* @param model Model id as recorded in settings.json.
|
|
310
|
+
* @returns Base id and suffix (null when the id carries none).
|
|
311
|
+
*/
|
|
312
|
+
function splitModelSuffix(model) {
|
|
313
|
+
const m = /^(.*)\[([^[\]]+)\]$/.exec(model);
|
|
314
|
+
return m ? { base: m[1], suffix: m[2] } : { base: model, suffix: null };
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Reports and, only once confirmed, strips a trailing `[1m]` suffix from the scope's own
|
|
318
|
+
* `model` value. The emitted defaults never force or replace a model family: a value with no
|
|
319
|
+
* suffix, or a bracketed suffix other than `1m` (case-insensitive), is left untouched. A `[1m]`
|
|
320
|
+
* value is named in pass 1 (`dry`) as what a confirmed pass 2 would write (the same id, suffix
|
|
321
|
+
* removed); the rewrite itself happens only in pass 2.
|
|
322
|
+
* @param settingsPath The scope's settings.json.
|
|
323
|
+
* @param dry Dry run (pass 1: report only).
|
|
324
|
+
* @param res Adapter result to report into.
|
|
325
|
+
*/
|
|
326
|
+
export function reportAndStripLongContextModel(settingsPath, dry, res) {
|
|
327
|
+
let current = {};
|
|
328
|
+
if (fs.existsSync(settingsPath)) {
|
|
329
|
+
try {
|
|
330
|
+
current = JSON.parse(fs.readFileSync(settingsPath, "utf8"));
|
|
331
|
+
}
|
|
332
|
+
catch (e) {
|
|
333
|
+
res.notes.push(`settings.json not parsed (${settingsPath}): ${e instanceof Error ? e.message : String(e)}; model-suffix check skipped.`);
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
const currentModel = typeof current.model === "string" ? current.model : undefined;
|
|
338
|
+
if (currentModel === undefined)
|
|
339
|
+
return;
|
|
340
|
+
const { base, suffix } = splitModelSuffix(currentModel);
|
|
341
|
+
if (suffix?.toLowerCase() !== "1m")
|
|
342
|
+
return;
|
|
343
|
+
res.notes.push(`model: ${currentModel} (would become ${base})`);
|
|
344
|
+
if (dry)
|
|
345
|
+
return;
|
|
346
|
+
current.model = base;
|
|
347
|
+
ensureDir(path.dirname(settingsPath), false);
|
|
348
|
+
fs.writeFileSync(settingsPath, JSON.stringify(current, null, 2), "utf8");
|
|
349
|
+
res.notes.push(`Rewrote model = ${base} in ${settingsPath} (stripped the ${suffix} suffix).`);
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Creates the `.freeharness/sdd/` workspace and seeds the project `.gitignore` at project
|
|
353
|
+
* scope: the transient SDD workspace line and the two agent-memory tree lines
|
|
354
|
+
* (`.claude/agent-memory/`, `.claude/agent-memory-local/`). `.claude/` is tracked in a
|
|
355
|
+
* FreeHarness-activated project, so an agent's `MEMORY.md` (written by Claude Code under
|
|
356
|
+
* `memory: project` / `memory: local`) would otherwise become a committed, attacker-supplied
|
|
357
|
+
* instruction channel injected into that agent's next prompt. `.gitignore` is never ours to
|
|
358
|
+
* remove. The durable evidence is `.freeharness/telemetry.jsonl`, appended by the cost-tracker
|
|
359
|
+
* hook at session end; nothing else is seeded or read here.
|
|
360
|
+
* @param projectRoot Project root.
|
|
361
|
+
* @param dry Dry run.
|
|
362
|
+
* @param res Adapter result to report into.
|
|
363
|
+
*/
|
|
364
|
+
export function seedProjectEvidence(projectRoot, dry, res) {
|
|
365
|
+
const sdd = ensureSddDir(projectRoot, dry);
|
|
366
|
+
if (sdd.created)
|
|
367
|
+
res.notes.push(`Created ${SDD_DIR}/ (subagent-driven-development briefs and review packages).`);
|
|
368
|
+
const gitignoreLines = [
|
|
369
|
+
{ line: SDD_GITIGNORE_LINE, reason: "briefs and review packages are transient" },
|
|
370
|
+
{ line: AGENT_MEMORY_GITIGNORE_LINE, reason: "agent memory is never a committed, attacker-supplied input" },
|
|
371
|
+
{ line: AGENT_MEMORY_LOCAL_GITIGNORE_LINE, reason: "agent memory is never a committed, attacker-supplied input" },
|
|
372
|
+
];
|
|
373
|
+
for (const { line, reason } of gitignoreLines) {
|
|
374
|
+
const gi = ensureGitignoreLine(projectRoot, line, dry);
|
|
375
|
+
if (gi.added) {
|
|
376
|
+
res.written.push(gi.path);
|
|
377
|
+
(res.skipped ??= []).push(gi.path);
|
|
378
|
+
res.notes.push(`Added \`${line}\` to .gitignore (${reason}).`);
|
|
379
|
+
}
|
|
380
|
+
else {
|
|
381
|
+
res.notes.push(`.gitignore already lists \`${line}\`.`);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
res.notes.push("telemetry lands in .freeharness/telemetry.jsonl (written by the cost tracker at session end; commit it when you want it as evidence).");
|
|
385
|
+
}
|