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,57 @@
|
|
|
1
|
+
// FreeHarness PreToolUse memory guard.
|
|
2
|
+
// Denies hand-written memory .md files: the free-mem plugin owns persistence, and writing
|
|
3
|
+
// duplicate memory files wastes output tokens. Allows free-mem data, openspec,
|
|
4
|
+
// docs, and the .claude/agent-memory/ and .claude/agent-memory-local/ subtrees (Claude
|
|
5
|
+
// Code's own per-agent memory files); a memory-named basename (MEMORY.md, memory.md,
|
|
6
|
+
// memories.md) is denied everywhere else, a path under .claude/ is never treated as
|
|
7
|
+
// source, and otherwise a .md file under packages/memory/ or vendor/ is allowed as a
|
|
8
|
+
// repository source tree.
|
|
9
|
+
// Reads the hook payload from stdin; emits a deny decision or nothing.
|
|
10
|
+
// The path is normalized (`.`/`..` segments collapsed) before any exemption or deny
|
|
11
|
+
// regex runs, so a traversal segment cannot smuggle a denied path through an exempt one.
|
|
12
|
+
import { posix } from "node:path";
|
|
13
|
+
|
|
14
|
+
let data = "";
|
|
15
|
+
process.stdin.on("data", (c) => (data += c));
|
|
16
|
+
process.stdin.on("end", () => {
|
|
17
|
+
let j;
|
|
18
|
+
try {
|
|
19
|
+
j = JSON.parse(data);
|
|
20
|
+
} catch {
|
|
21
|
+
process.exit(0);
|
|
22
|
+
}
|
|
23
|
+
const tool = j.tool_name || "";
|
|
24
|
+
if (!/^(Write|Edit|MultiEdit)$/.test(tool)) process.exit(0);
|
|
25
|
+
|
|
26
|
+
const ti = j.tool_input || {};
|
|
27
|
+
const raw = ti.file_path || ti.path || "";
|
|
28
|
+
if (!raw) process.exit(0);
|
|
29
|
+
|
|
30
|
+
const p = posix.normalize(String(raw).replace(/\\/g, "/"));
|
|
31
|
+
const base = p.split("/").pop() || "";
|
|
32
|
+
|
|
33
|
+
if (/\/\.claude-mem(\/|$)/.test(p)) process.exit(0);
|
|
34
|
+
if (/\/(openspec|docs|node_modules)\//.test(p)) process.exit(0);
|
|
35
|
+
if (/\/\.claude\/agent-memory(-local)?\//.test(p)) process.exit(0);
|
|
36
|
+
|
|
37
|
+
const md = /\.md$/i.test(base);
|
|
38
|
+
const noext = base.replace(/\.md$/i, "");
|
|
39
|
+
const memName = md && /(^|[._-])(memory|memoria|memories|memorias)([._-]|$)/i.test(noext);
|
|
40
|
+
const memDir = md && /\/memor(y|ies|ia|ias)\//i.test(p);
|
|
41
|
+
const claudeDataTree = /\/\.claude\//.test(p);
|
|
42
|
+
const sourceTree = !claudeDataTree && /\/(packages\/memory|vendor)\//.test(p);
|
|
43
|
+
|
|
44
|
+
if (memName || (!sourceTree && memDir)) {
|
|
45
|
+
process.stdout.write(
|
|
46
|
+
JSON.stringify({
|
|
47
|
+
hookSpecificOutput: {
|
|
48
|
+
hookEventName: "PreToolUse",
|
|
49
|
+
permissionDecision: "deny",
|
|
50
|
+
permissionDecisionReason:
|
|
51
|
+
"FreeHarness: memory is managed exclusively by the free-mem plugin. Do not hand-write memory .md files (it wastes output tokens). Persist context via the free-mem plugin instead.",
|
|
52
|
+
},
|
|
53
|
+
}),
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
process.exit(0);
|
|
57
|
+
});
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
// FreeHarness output pruner (Claude Code PostToolUse hook, matcher Bash).
|
|
2
|
+
// Replaces stdout via `updatedToolOutput`; the command, exit codes and on-disk effects stay
|
|
3
|
+
// untouched (a PreToolUse `updatedInput` pipe rewrite would change what `Bash(...)` allow
|
|
4
|
+
// rules match and hide the real exit status). Oversize stdout is written in full to
|
|
5
|
+
// ~/.claude/freeharness/tool-output/<session>/<n>.txt; the model gets head + tail with
|
|
6
|
+
// `[freeharness: N lines pruned ...]` markers, and a `{kind:"pruned"}` record lands in the
|
|
7
|
+
// cost log. Allowlisted commands and test-runner failure blocks pass through whole. Never
|
|
8
|
+
// blocks; exit 0 always. Optional --digest <url> summarizes a marker via a local model on a
|
|
9
|
+
// loopback host only (a pruned region can carry secrets); credential-shaped lines are dropped.
|
|
10
|
+
// Disable: FREEHARNESS_HOOK_OUTPUT_PRUNER=off.
|
|
11
|
+
import fs from "node:fs";
|
|
12
|
+
import os from "node:os";
|
|
13
|
+
import path from "node:path";
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
15
|
+
|
|
16
|
+
/** Defaults; overridden with --max-bytes / --max-lines. */
|
|
17
|
+
export const DEFAULTS = Object.freeze({ maxBytes: 8 * 1024, maxLines: 200, headShare: 0.6, blockCap: 120, digestMs: 1500 });
|
|
18
|
+
|
|
19
|
+
/** System prompt for every digest request: facts only, bounded, no chatter. */
|
|
20
|
+
const DIGEST_SYSTEM_PROMPT =
|
|
21
|
+
"Produce a factual digest of the following tool output in at most 12 lines. No commentary, no preamble: facts only.";
|
|
22
|
+
|
|
23
|
+
/** Preview commands the model must see whole. */
|
|
24
|
+
const ALLOWLIST = /\bgit\s+(diff|show|apply|format-patch|log\s+-p)\b|(^|[\s;|&])(diff|patch|colordiff)\b|--dry-run\b/;
|
|
25
|
+
|
|
26
|
+
/** Test runners kept whole. */
|
|
27
|
+
const TEST_RUNNERS = /\bpytest\b|\bpython3?\s+-m\s+pytest\b|\bnode\s+(--import\s+\S+\s+)?--test\b|\bvitest\b|\bnpm\s+(run\s+)?test\b|\bnpx\s+vitest\b|\bjest\b/;
|
|
28
|
+
|
|
29
|
+
/** Failure-block grammars: `start` opens, `end` closes; `blockCap` bounds a runaway block. */
|
|
30
|
+
const BLOCKS = [
|
|
31
|
+
// pytest: FAILURES / ERRORS sections, each `____ test_x ____` block, summary/result lines.
|
|
32
|
+
{ start: /^={3,}\s+(FAILURES|ERRORS|short test summary info)\s+={3,}$|^_{3,}\s.+\s_{3,}$/, end: /^={3,}.*={3,}$|^_{3,}\s.+\s_{3,}$/ },
|
|
33
|
+
{ start: /^(FAILED|ERROR)\s/, end: /^(?!(FAILED|ERROR)\s)/ },
|
|
34
|
+
{ start: /^={3,}\s+\d+\s+(failed|passed|error|errors|skipped|warnings?)\b.*={3,}$/, end: /^/ },
|
|
35
|
+
// node --test (TAP) and vitest/jest FAIL blocks.
|
|
36
|
+
{ start: /^\s*not ok\b/, end: /^\s*(ok|not ok|#|1\.\.)/ },
|
|
37
|
+
{ start: /^\s*(FAIL|×|✗|✕)\s|^\s*(AssertionError|Error):|^\s*Traceback \(most recent call last\)/, end: /^\s*(PASS|✓|√|Test Files|Tests|Snapshots|Time:|Ran all)\b/ },
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
/** Parses CLI options with env fallbacks for the byte/line limits. */
|
|
41
|
+
export function parseOptions(argv) {
|
|
42
|
+
const opts = { maxBytes: DEFAULTS.maxBytes, maxLines: DEFAULTS.maxLines, digestUrl: null, digestMs: DEFAULTS.digestMs };
|
|
43
|
+
const env = (k) => process.env[k];
|
|
44
|
+
const num = (v, fallback) => {
|
|
45
|
+
const n = Number.parseInt(String(v), 10);
|
|
46
|
+
return Number.isInteger(n) && n > 0 ? n : fallback;
|
|
47
|
+
};
|
|
48
|
+
if (env("FREEHARNESS_PRUNER_MAX_BYTES")) opts.maxBytes = num(env("FREEHARNESS_PRUNER_MAX_BYTES"), opts.maxBytes);
|
|
49
|
+
if (env("FREEHARNESS_PRUNER_MAX_LINES")) opts.maxLines = num(env("FREEHARNESS_PRUNER_MAX_LINES"), opts.maxLines);
|
|
50
|
+
for (let i = 0; i < argv.length; i++) {
|
|
51
|
+
if (argv[i] === "--max-bytes") opts.maxBytes = num(argv[++i], opts.maxBytes);
|
|
52
|
+
else if (argv[i] === "--max-lines") opts.maxLines = num(argv[++i], opts.maxLines);
|
|
53
|
+
else if (argv[i] === "--digest") opts.digestUrl = argv[++i] || null;
|
|
54
|
+
else if (argv[i] === "--digest-ms") opts.digestMs = num(argv[++i], opts.digestMs);
|
|
55
|
+
}
|
|
56
|
+
return opts;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Splits text into lines without a trailing empty element. */
|
|
60
|
+
function splitLines(text) {
|
|
61
|
+
if (text.length === 0) return [];
|
|
62
|
+
const lines = text.split("\n");
|
|
63
|
+
if (text.endsWith("\n")) lines.pop();
|
|
64
|
+
return lines;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Failure blocks as [start, end] ranges, bounded by DEFAULTS.blockCap lines each. */
|
|
68
|
+
export function failureBlocks(lines) {
|
|
69
|
+
const ranges = [];
|
|
70
|
+
let i = 0;
|
|
71
|
+
while (i < lines.length) {
|
|
72
|
+
const g = BLOCKS.find((b) => b.start.test(lines[i]));
|
|
73
|
+
if (!g) {
|
|
74
|
+
i += 1;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
let j = i + 1;
|
|
78
|
+
while (j < lines.length && j - i < DEFAULTS.blockCap && !g.end.test(lines[j])) j += 1;
|
|
79
|
+
ranges.push([i, j - 1]);
|
|
80
|
+
i = j;
|
|
81
|
+
}
|
|
82
|
+
return ranges;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Prunes to the dual limit: head + tail kept, failure blocks kept whole, each gap replaced by a marker. Null when within both limits. */
|
|
86
|
+
export function prune(text, limits, keepFailures, copyPath) {
|
|
87
|
+
const totalBytes = Buffer.byteLength(text, "utf8");
|
|
88
|
+
const lines = splitLines(text);
|
|
89
|
+
const totalLines = lines.length;
|
|
90
|
+
if (totalBytes <= limits.maxBytes && totalLines <= limits.maxLines) return null;
|
|
91
|
+
|
|
92
|
+
const keep = new Uint8Array(lines.length);
|
|
93
|
+
const budgetLines = Math.max(4, limits.maxLines);
|
|
94
|
+
let head = Math.floor(budgetLines * DEFAULTS.headShare);
|
|
95
|
+
let tail = budgetLines - head;
|
|
96
|
+
const mark = (from, to) => {
|
|
97
|
+
for (let k = Math.max(0, from); k <= Math.min(lines.length - 1, to); k++) keep[k] = 1;
|
|
98
|
+
};
|
|
99
|
+
const protectedIdx = new Uint8Array(lines.length);
|
|
100
|
+
if (keepFailures) {
|
|
101
|
+
for (const [a, b] of failureBlocks(lines)) {
|
|
102
|
+
mark(a, b);
|
|
103
|
+
for (let k = a; k <= b; k++) protectedIdx[k] = 1;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
mark(0, head - 1);
|
|
107
|
+
mark(lines.length - tail, lines.length - 1);
|
|
108
|
+
|
|
109
|
+
// Byte limit: shrink head then tail, never a protected line, until under budget.
|
|
110
|
+
const bytesOf = () => {
|
|
111
|
+
let n = 0;
|
|
112
|
+
for (let k = 0; k < lines.length; k++) if (keep[k]) n += Buffer.byteLength(lines[k], "utf8") + 1;
|
|
113
|
+
return n;
|
|
114
|
+
};
|
|
115
|
+
const budgetBytes = Math.max(512, limits.maxBytes);
|
|
116
|
+
while (bytesOf() > budgetBytes && (head > 2 || tail > 2)) {
|
|
117
|
+
if (head > 2) {
|
|
118
|
+
head -= 1;
|
|
119
|
+
if (!protectedIdx[head]) keep[head] = 0;
|
|
120
|
+
}
|
|
121
|
+
if (bytesOf() > budgetBytes && tail > 2) {
|
|
122
|
+
tail -= 1;
|
|
123
|
+
const idx = lines.length - tail - 1;
|
|
124
|
+
if (idx >= 0 && !protectedIdx[idx]) keep[idx] = 0;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const out = [];
|
|
129
|
+
const regions = [];
|
|
130
|
+
let prunedLines = 0;
|
|
131
|
+
let prunedBytes = 0;
|
|
132
|
+
let k = 0;
|
|
133
|
+
while (k < lines.length) {
|
|
134
|
+
if (keep[k]) {
|
|
135
|
+
const line = lines[k];
|
|
136
|
+
out.push(line.length > budgetBytes ? `${line.slice(0, budgetBytes)} …[line truncated]` : line);
|
|
137
|
+
k += 1;
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
const from = k;
|
|
141
|
+
let gapBytes = 0;
|
|
142
|
+
const gapLines = [];
|
|
143
|
+
while (k < lines.length && !keep[k]) {
|
|
144
|
+
gapLines.push(lines[k]);
|
|
145
|
+
gapBytes += Buffer.byteLength(lines[k], "utf8") + 1;
|
|
146
|
+
k += 1;
|
|
147
|
+
}
|
|
148
|
+
const n = k - from;
|
|
149
|
+
prunedLines += n;
|
|
150
|
+
prunedBytes += gapBytes;
|
|
151
|
+
const marker = `[freeharness: ${n} lines pruned (${from + 1}-${k})]`;
|
|
152
|
+
regions.push({ marker, gapText: gapLines.join("\n"), n, from: from + 1, to: k });
|
|
153
|
+
out.push(marker);
|
|
154
|
+
}
|
|
155
|
+
out.push(`[freeharness: ${prunedLines} lines pruned, full output at ${copyPath}]`);
|
|
156
|
+
return { text: `${out.join("\n")}\n`, prunedLines, prunedBytes, totalLines, totalBytes, regions };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Per-session copy directory (FREEHARNESS_TOOL_OUTPUT_DIR overrides). */
|
|
160
|
+
function copyDir(sessionId) {
|
|
161
|
+
const root = process.env.FREEHARNESS_TOOL_OUTPUT_DIR || path.join(os.homedir(), ".claude", "freeharness", "tool-output");
|
|
162
|
+
return path.join(root, sessionId.replace(/[^\w.-]/g, "_") || "unknown");
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Writes output as the next numbered file; throws on failure. */
|
|
166
|
+
function writeCopy(dir, text) {
|
|
167
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
168
|
+
const n = fs.readdirSync(dir).filter((f) => /^\d+\.txt$/.test(f)).length + 1;
|
|
169
|
+
const file = path.join(dir, `${n}.txt`);
|
|
170
|
+
fs.writeFileSync(file, text, "utf8");
|
|
171
|
+
return file;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Appends the pruned-bytes record; failures report on stderr only. */
|
|
175
|
+
function appendCostRecord(record) {
|
|
176
|
+
try {
|
|
177
|
+
const file = process.env.FREEHARNESS_COST_LOG || path.join(os.homedir(), ".claude", "freeharness", "cost", "usage.jsonl");
|
|
178
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
179
|
+
fs.appendFileSync(file, `${JSON.stringify(record)}\n`, "utf8");
|
|
180
|
+
} catch (e) {
|
|
181
|
+
process.stderr.write(`freeharness-output-pruner: cost log not written: ${e instanceof Error ? e.message : String(e)}\n`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Loopback hosts a digest request may target. */
|
|
186
|
+
const LOOPBACK_HOSTS = new Set(["127.0.0.1", "localhost", "::1"]);
|
|
187
|
+
|
|
188
|
+
/** True when `url`'s host is loopback. */
|
|
189
|
+
function isLoopbackUrl(url) {
|
|
190
|
+
try {
|
|
191
|
+
return LOOPBACK_HOSTS.has(new URL(url).hostname);
|
|
192
|
+
} catch {
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** Drops lines shaped like a credential. */
|
|
198
|
+
function redactSecrets(text) {
|
|
199
|
+
return text
|
|
200
|
+
.split("\n")
|
|
201
|
+
.filter((line) => !/(KEY|SECRET|TOKEN|PASSWORD)\s*[=:]/i.test(line))
|
|
202
|
+
.join("\n");
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** Asks the local model server for a digest of one region (secrets redacted); failure resolves to null, named on stderr. */
|
|
206
|
+
export async function requestDigest(url, timeoutMs, text) {
|
|
207
|
+
if (!isLoopbackUrl(url)) {
|
|
208
|
+
process.stderr.write(`freeharness-output-pruner: digest request refused: non-loopback host in ${url}\n`);
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
try {
|
|
212
|
+
const res = await fetch(`${url.replace(/\/+$/, "")}/v1/chat/completions`, {
|
|
213
|
+
method: "POST",
|
|
214
|
+
headers: { "content-type": "application/json" },
|
|
215
|
+
body: JSON.stringify({
|
|
216
|
+
model: "local",
|
|
217
|
+
temperature: 0,
|
|
218
|
+
max_tokens: 400,
|
|
219
|
+
stream: false,
|
|
220
|
+
messages: [
|
|
221
|
+
{ role: "system", content: DIGEST_SYSTEM_PROMPT },
|
|
222
|
+
{ role: "user", content: redactSecrets(text) },
|
|
223
|
+
],
|
|
224
|
+
}),
|
|
225
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
226
|
+
});
|
|
227
|
+
if (!res.ok) {
|
|
228
|
+
process.stderr.write(`freeharness-output-pruner: digest request failed: HTTP ${res.status}\n`);
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
const body = await res.json();
|
|
232
|
+
const content = body && body.choices && body.choices[0] && body.choices[0].message && body.choices[0].message.content;
|
|
233
|
+
if (typeof content !== "string" || !content.trim()) {
|
|
234
|
+
process.stderr.write("freeharness-output-pruner: digest request failed: no digest in response\n");
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
return content.trim();
|
|
238
|
+
} catch (e) {
|
|
239
|
+
process.stderr.write(`freeharness-output-pruner: digest request failed: ${e instanceof Error ? e.message : String(e)}\n`);
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** Regions digested per output, and the pass deadline as a multiple of --digest-ms. */
|
|
245
|
+
const MAX_DIGESTED_REGIONS = 3;
|
|
246
|
+
const OVERALL_DEADLINE_MULTIPLE = 2;
|
|
247
|
+
|
|
248
|
+
/** Replaces each marker with a digest no longer than the region; failure keeps the marker. Bounded to `MAX_DIGESTED_REGIONS` attempts, one deadline. */
|
|
249
|
+
export async function digestResult(result, url, timeoutMs) {
|
|
250
|
+
let text = result.text;
|
|
251
|
+
const deadline = Date.now() + timeoutMs * OVERALL_DEADLINE_MULTIPLE;
|
|
252
|
+
let attempts = 0;
|
|
253
|
+
for (const region of result.regions) {
|
|
254
|
+
if (attempts >= MAX_DIGESTED_REGIONS) break;
|
|
255
|
+
const remaining = deadline - Date.now();
|
|
256
|
+
if (remaining <= 0) break;
|
|
257
|
+
attempts += 1;
|
|
258
|
+
const digest = await requestDigest(url, Math.min(timeoutMs, remaining), region.gapText);
|
|
259
|
+
if (!digest || digest.length > region.gapText.length) continue;
|
|
260
|
+
text = text.replace(region.marker, () => `[freeharness: ${region.n} lines digested (${region.from}-${region.to})] ${digest}`);
|
|
261
|
+
}
|
|
262
|
+
return text;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/** Builds the hook response for one PostToolUse payload, or null for pass-through. */
|
|
266
|
+
export async function respond(payload, opts) {
|
|
267
|
+
if (payload.tool_name !== "Bash") return null;
|
|
268
|
+
const input = payload.tool_input && typeof payload.tool_input === "object" ? payload.tool_input : {};
|
|
269
|
+
const command = typeof input.command === "string" ? input.command : "";
|
|
270
|
+
if (ALLOWLIST.test(command)) return null;
|
|
271
|
+
const response = payload.tool_response;
|
|
272
|
+
if (!response || typeof response !== "object" || typeof response.stdout !== "string") return null;
|
|
273
|
+
const keepFailures = TEST_RUNNERS.test(command);
|
|
274
|
+
const sessionId = typeof payload.session_id === "string" ? payload.session_id : "unknown";
|
|
275
|
+
const dir = copyDir(sessionId);
|
|
276
|
+
const probe = prune(response.stdout, opts, keepFailures, "<pending>");
|
|
277
|
+
if (!probe) return null;
|
|
278
|
+
let copyPath;
|
|
279
|
+
try {
|
|
280
|
+
copyPath = writeCopy(dir, response.stdout);
|
|
281
|
+
} catch (e) {
|
|
282
|
+
process.stderr.write(`freeharness-output-pruner: disk copy failed, output passed through unpruned: ${e instanceof Error ? e.message : String(e)}\n`);
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
const result = prune(response.stdout, opts, keepFailures, copyPath);
|
|
286
|
+
if (!result) return null;
|
|
287
|
+
const cwd = typeof payload.cwd === "string" && payload.cwd ? payload.cwd : process.cwd();
|
|
288
|
+
// Digest runs before the cost record is appended: a failure never leaves a cost line
|
|
289
|
+
// pointing at a run that did not finish.
|
|
290
|
+
const stdout = opts.digestUrl ? await digestResult(result, opts.digestUrl, opts.digestMs) : result.text;
|
|
291
|
+
appendCostRecord({
|
|
292
|
+
kind: "pruned",
|
|
293
|
+
ts: new Date().toISOString(),
|
|
294
|
+
session_id: sessionId,
|
|
295
|
+
project: path.basename(cwd),
|
|
296
|
+
cwd,
|
|
297
|
+
bytes: result.prunedBytes,
|
|
298
|
+
lines: result.prunedLines,
|
|
299
|
+
total_bytes: result.totalBytes,
|
|
300
|
+
total_lines: result.totalLines,
|
|
301
|
+
path: copyPath,
|
|
302
|
+
command: command.slice(0, 160),
|
|
303
|
+
});
|
|
304
|
+
return {
|
|
305
|
+
hookSpecificOutput: {
|
|
306
|
+
hookEventName: "PostToolUse",
|
|
307
|
+
updatedToolOutput: { ...response, stdout },
|
|
308
|
+
},
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/** Entry point: read stdin, respond or stay silent. */
|
|
313
|
+
async function main() {
|
|
314
|
+
if ((process.env.FREEHARNESS_HOOK_OUTPUT_PRUNER || "").toLowerCase() === "off") return;
|
|
315
|
+
let payload = {};
|
|
316
|
+
try {
|
|
317
|
+
const raw = fs.readFileSync(0, "utf8");
|
|
318
|
+
if (raw.trim()) payload = JSON.parse(raw);
|
|
319
|
+
} catch {
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
if (!payload || typeof payload !== "object") return;
|
|
323
|
+
try {
|
|
324
|
+
const out = await respond(payload, parseOptions(process.argv.slice(2)));
|
|
325
|
+
if (out) process.stdout.write(JSON.stringify(out));
|
|
326
|
+
} catch (e) {
|
|
327
|
+
process.stderr.write(`freeharness-output-pruner: ${e instanceof Error ? e.message : String(e)}\n`);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (process.argv[1] && fs.realpathSync(process.argv[1]) === fs.realpathSync(fileURLToPath(import.meta.url))) {
|
|
332
|
+
main().finally(() => process.exit(0));
|
|
333
|
+
}
|