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,583 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# FreeHarness SessionStart pointer (Claude Code): reads the payload from stdin, hands it to the
|
|
3
|
+
# embedded Node program via FH_PAYLOAD (runnable by path or by `bash`). Additive: coexists with
|
|
4
|
+
# the free-mem plugin's hooks.
|
|
5
|
+
# Events: SessionStart. Sources: startup, clear, compact, resume, fork.
|
|
6
|
+
# Output: {"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":"<text>"}}:
|
|
7
|
+
# the pointer, an SDD status line and WIP advisory when applicable, the routing block from
|
|
8
|
+
# FH_ROUTING_FILE, then (compact/resume/fork) the re-orientation block; see buildContext.
|
|
9
|
+
# Disable: FREEHARNESS_HOOK_SESSION_START=off. Never blocks: exit 0 on every path.
|
|
10
|
+
set -u
|
|
11
|
+
if [ "$(printf '%s' "${FREEHARNESS_HOOK_SESSION_START:-}" | tr '[:upper:]' '[:lower:]')" = "off" ]; then exit 0; fi
|
|
12
|
+
# Routing file: next to this script first, else the canonical source layout.
|
|
13
|
+
FH_HOOK_DIR="$(cd "$(dirname "$0")" && pwd -P)"
|
|
14
|
+
if [ -r "$FH_HOOK_DIR/freeharness-routing.md" ]; then
|
|
15
|
+
FH_ROUTING_FILE="$FH_HOOK_DIR/freeharness-routing.md"
|
|
16
|
+
elif [ -r "$FH_HOOK_DIR/../rules/routing.md" ]; then
|
|
17
|
+
FH_ROUTING_FILE="$(cd "$FH_HOOK_DIR/../rules" && pwd -P)/routing.md"
|
|
18
|
+
else
|
|
19
|
+
FH_ROUTING_FILE=""
|
|
20
|
+
fi
|
|
21
|
+
export FH_ROUTING_FILE
|
|
22
|
+
FH_PAYLOAD="$(cat 2>/dev/null || true)"
|
|
23
|
+
export FH_PAYLOAD
|
|
24
|
+
|
|
25
|
+
if node --input-type=module - <<'JS'
|
|
26
|
+
import fs from "node:fs";
|
|
27
|
+
import os from "node:os";
|
|
28
|
+
import path from "node:path";
|
|
29
|
+
|
|
30
|
+
/** The v3 pointer lines (without the closing tag; the SDD status line goes before it). */
|
|
31
|
+
const POINTER_LINES = [
|
|
32
|
+
"<freeharness>",
|
|
33
|
+
"FreeHarness active. Plan non-trivial work with OpenSpec (/opsx: proposal, specs, design, tasks).",
|
|
34
|
+
"Execute with skills: test-driven-development, subagent-driven-development; review with code-review",
|
|
35
|
+
"plus the reviewer agents (python/database/security) and pr-reviewer (mle lens); gate with verification-before-completion.",
|
|
36
|
+
"Memory is the free-mem plugin. The user's CLAUDE.md/AGENTS.md always takes precedence over this pointer.",
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
/** The v3 pointer, unchanged (no openspec/changes in the project). */
|
|
40
|
+
const POINTER = [...POINTER_LINES, "</freeharness>"].join("\n");
|
|
41
|
+
|
|
42
|
+
/** Complete-but-not-archived ids listed in the status line, at most. */
|
|
43
|
+
const MAX_COMPLETE_IDS = 5;
|
|
44
|
+
|
|
45
|
+
/** The eight checkpoint sections, in order. */
|
|
46
|
+
const SECTIONS = ["Primary Request", "Files and Code", "Errors and Fixes", "Pending Jobs", "Current Work", "Next Step", "Critical Context", "Corrections"];
|
|
47
|
+
|
|
48
|
+
/** Closing rule. */
|
|
49
|
+
const RULE = "Rule: after compaction, re-read Next Step and Errors and Fixes before acting.";
|
|
50
|
+
|
|
51
|
+
/** Sources that get the re-orientation block. */
|
|
52
|
+
const REORIENT = new Set(["compact", "resume", "fork"]);
|
|
53
|
+
|
|
54
|
+
/** Tools whose calls count as file operations. */
|
|
55
|
+
const FILE_TOOLS = new Set(["Edit", "Write", "MultiEdit", "NotebookEdit"]);
|
|
56
|
+
|
|
57
|
+
/** Transcript scan cap (bytes). */
|
|
58
|
+
const MAX_TRANSCRIPT_BYTES = 64 * 1024 * 1024;
|
|
59
|
+
|
|
60
|
+
/** Routing table body cap (characters), before the `<routing>`/`</routing>` tags. */
|
|
61
|
+
const ROUTING_CAP = 6000;
|
|
62
|
+
|
|
63
|
+
/** Reserve for the `(+N rows truncated)` marker, so a truncated body still fits ROUTING_CAP. */
|
|
64
|
+
const ROUTING_MARKER_RESERVE = 48;
|
|
65
|
+
|
|
66
|
+
/** additionalContext length that starts the shortening cascade (stays under 10,000). */
|
|
67
|
+
const SHORTEN_AT = 9800;
|
|
68
|
+
|
|
69
|
+
/** State dir (FREEHARNESS_STATE_DIR overrides). */
|
|
70
|
+
function stateDir() {
|
|
71
|
+
return process.env.FREEHARNESS_STATE_DIR || path.join(os.homedir(), ".claude", "freeharness", "state");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Sanitizes a session id for use as a file name. */
|
|
75
|
+
function safeId(id) {
|
|
76
|
+
return String(id || "").replace(/[^\w.-]/g, "_") || "unknown";
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** A `-`/`*` bullet carrying a `[ ]`/`[x]` checkbox; matches the engine's task-line pattern. */
|
|
80
|
+
const TASK_LINE_PATTERN = /^\s*[-*]\s*\[([\sxX])\]\s*(.*)/;
|
|
81
|
+
|
|
82
|
+
/** A per-task annotation line, never a task; the trailing `\s*` absorbs a CRLF `\r`. */
|
|
83
|
+
const TASK_ANNOTATION_PATTERN = /^\s*(?:Verify|Interfaces|Boundary|Depends|Disposition):\s*(.*)\s*$/;
|
|
84
|
+
|
|
85
|
+
/** A `Disposition:` line and its leading kind token. */
|
|
86
|
+
const DISPOSITION_LINE_PATTERN = /^\s*Disposition:\s*(\S+)/;
|
|
87
|
+
|
|
88
|
+
/** Disposition kinds that resolve a task; any other kind leaves it open. */
|
|
89
|
+
const DISPOSITION_KINDS = new Set(["superseded", "moved", "external"]);
|
|
90
|
+
|
|
91
|
+
/** An unclaimed follow-up item line, matching `openspec follow-up list`'s shape. */
|
|
92
|
+
const FOLLOW_UP_ITEM_PATTERN = /^\s*[-*]\s*\[ \]\s*/;
|
|
93
|
+
|
|
94
|
+
/** `.freeharness/sdd.json`, relative to the project root. */
|
|
95
|
+
const WIP_CAP_FILE = ".freeharness/sdd.json";
|
|
96
|
+
|
|
97
|
+
/** `wip_cap` default when the file is absent, unreadable or holds an invalid value. */
|
|
98
|
+
const DEFAULT_WIP_CAP = 1;
|
|
99
|
+
|
|
100
|
+
/** Parses task lines with disposition awareness, mirroring the engine so the pointer agrees with `openspec status`. */
|
|
101
|
+
function parseTasksWithDisposition(content) {
|
|
102
|
+
const tasks = [];
|
|
103
|
+
let current = null;
|
|
104
|
+
for (const line of content.split("\n")) {
|
|
105
|
+
const task = TASK_LINE_PATTERN.exec(line);
|
|
106
|
+
if (task) {
|
|
107
|
+
current = { done: task[1].toLowerCase() === "x", dispositioned: false };
|
|
108
|
+
tasks.push(current);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if (current && TASK_ANNOTATION_PATTERN.test(line)) {
|
|
112
|
+
const disposition = DISPOSITION_LINE_PATTERN.exec(line);
|
|
113
|
+
if (disposition && DISPOSITION_KINDS.has(disposition[1])) current.dispositioned = true;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
current = null;
|
|
117
|
+
}
|
|
118
|
+
return tasks;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Counts a tasks.md's checkboxes: done, dispositioned (unchecked with a Disposition: line) and open. */
|
|
122
|
+
function countTasksFromContent(content) {
|
|
123
|
+
const tasks = parseTasksWithDisposition(content);
|
|
124
|
+
const done = tasks.filter((t) => t.done).length;
|
|
125
|
+
const dispositioned = tasks.filter((t) => !t.done && t.dispositioned).length;
|
|
126
|
+
const open = tasks.length - done - dispositioned;
|
|
127
|
+
return { open, done, dispositioned };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Reads `wip_cap`; absent = default, invalid = default + INFO line naming the file. */
|
|
131
|
+
function readWipCap(project) {
|
|
132
|
+
let raw;
|
|
133
|
+
try {
|
|
134
|
+
raw = fs.readFileSync(path.join(project, WIP_CAP_FILE), "utf8");
|
|
135
|
+
} catch (e) {
|
|
136
|
+
const absent = e && (e.code === "ENOENT" || e.code === "ENOTDIR");
|
|
137
|
+
if (absent) return { cap: DEFAULT_WIP_CAP, info: null };
|
|
138
|
+
return { cap: DEFAULT_WIP_CAP, info: `INFO ${WIP_CAP_FILE}: wip_cap invalid, using ${DEFAULT_WIP_CAP}` };
|
|
139
|
+
}
|
|
140
|
+
try {
|
|
141
|
+
const parsed = JSON.parse(raw);
|
|
142
|
+
const cap = parsed && parsed.wip_cap;
|
|
143
|
+
if (typeof cap === "number" && Number.isInteger(cap) && cap > 0) return { cap, info: null };
|
|
144
|
+
} catch {
|
|
145
|
+
// falls through to the default below
|
|
146
|
+
}
|
|
147
|
+
return { cap: DEFAULT_WIP_CAP, info: `INFO ${WIP_CAP_FILE}: wip_cap invalid, using ${DEFAULT_WIP_CAP}` };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** In-flight ids for the WIP cap: open task or no tasks.md, sorted for a deterministic list. */
|
|
151
|
+
function wipInFlightIds(project) {
|
|
152
|
+
const dir = path.join(project, "openspec", "changes");
|
|
153
|
+
let entries;
|
|
154
|
+
try {
|
|
155
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
156
|
+
} catch {
|
|
157
|
+
return [];
|
|
158
|
+
}
|
|
159
|
+
const ids = [];
|
|
160
|
+
for (const e of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
161
|
+
if (!e.isDirectory() || e.name === "archive" || e.name.startsWith(".")) continue;
|
|
162
|
+
let hasOpen = true;
|
|
163
|
+
try {
|
|
164
|
+
hasOpen = countTasksFromContent(fs.readFileSync(path.join(dir, e.name, "tasks.md"), "utf8")).open > 0;
|
|
165
|
+
} catch {
|
|
166
|
+
hasOpen = true;
|
|
167
|
+
}
|
|
168
|
+
if (hasOpen) ids.push(e.name);
|
|
169
|
+
}
|
|
170
|
+
return ids;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** WIP advisory lines, printed only once in-flight count exceeds the cap (one change earlier than `openspec new change`'s own warning). */
|
|
174
|
+
function wipAdvisoryLines(project) {
|
|
175
|
+
const inFlight = wipInFlightIds(project);
|
|
176
|
+
if (inFlight.length === 0) return [];
|
|
177
|
+
const { cap, info } = readWipCap(project);
|
|
178
|
+
if (inFlight.length <= cap) return [];
|
|
179
|
+
const lines = [];
|
|
180
|
+
if (info) lines.push(info);
|
|
181
|
+
const named = inFlight.map((id) => sanitizeTag(id)).join(", ");
|
|
182
|
+
lines.push(`WIP advisory: ${inFlight.length} change(s) in flight (cap ${cap}): ${named}; finish or archive before opening another`);
|
|
183
|
+
return lines;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Counts unclaimed follow-up items; item text is data, never read here. */
|
|
187
|
+
function followUpCount(project) {
|
|
188
|
+
const changesDir = path.join(project, "openspec", "changes");
|
|
189
|
+
let total = 0;
|
|
190
|
+
const countFile = (dir) => {
|
|
191
|
+
let text;
|
|
192
|
+
try {
|
|
193
|
+
text = fs.readFileSync(path.join(dir, "follow-ups.md"), "utf8");
|
|
194
|
+
} catch {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
for (const line of text.split("\n")) {
|
|
198
|
+
if (FOLLOW_UP_ITEM_PATTERN.test(line)) total += 1;
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
let entries;
|
|
202
|
+
try {
|
|
203
|
+
entries = fs.readdirSync(changesDir, { withFileTypes: true });
|
|
204
|
+
} catch {
|
|
205
|
+
return 0;
|
|
206
|
+
}
|
|
207
|
+
for (const e of entries) {
|
|
208
|
+
if (!e.isDirectory() || e.name.startsWith(".")) continue;
|
|
209
|
+
if (e.name === "archive") {
|
|
210
|
+
let archived;
|
|
211
|
+
try {
|
|
212
|
+
archived = fs.readdirSync(path.join(changesDir, "archive"), { withFileTypes: true });
|
|
213
|
+
} catch {
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
for (const a of archived) {
|
|
217
|
+
if (!a.isDirectory() || a.name.startsWith(".")) continue;
|
|
218
|
+
countFile(path.join(changesDir, "archive", a.name));
|
|
219
|
+
}
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
countFile(path.join(changesDir, e.name));
|
|
223
|
+
}
|
|
224
|
+
return total;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** Active changes with an unchecked task; path id uses stripControl, not sanitizeTag's 64-char cap, so a long id still points at its real tasks.md (sdd-flow-2#7). */
|
|
228
|
+
function activeChanges(project) {
|
|
229
|
+
const dir = path.join(project, "openspec", "changes");
|
|
230
|
+
let entries;
|
|
231
|
+
try {
|
|
232
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
233
|
+
} catch {
|
|
234
|
+
return [];
|
|
235
|
+
}
|
|
236
|
+
const out = [];
|
|
237
|
+
for (const e of entries) {
|
|
238
|
+
if (!e.isDirectory() || e.name === "archive") continue;
|
|
239
|
+
const tasks = path.join(dir, e.name, "tasks.md");
|
|
240
|
+
let text;
|
|
241
|
+
try {
|
|
242
|
+
text = fs.readFileSync(tasks, "utf8");
|
|
243
|
+
} catch {
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
const { open } = countTasksFromContent(text);
|
|
247
|
+
if (open > 0) {
|
|
248
|
+
const display = sanitizeTag(e.name);
|
|
249
|
+
const pathId = stripControl(e.name);
|
|
250
|
+
out.push(`${display} (openspec/changes/${pathId}/tasks.md, ${open} unchecked)`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return out;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** SDD status of the project's changes, from tasks.md checkbox counts; null when openspec/changes is absent. */
|
|
257
|
+
function openspecStatus(project) {
|
|
258
|
+
const dir = path.join(project, "openspec", "changes");
|
|
259
|
+
let entries;
|
|
260
|
+
try {
|
|
261
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
262
|
+
} catch {
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
const ids = [];
|
|
266
|
+
const complete = [];
|
|
267
|
+
let dispositioned = 0;
|
|
268
|
+
for (const e of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
269
|
+
if (!e.isDirectory() || e.name === "archive" || e.name.startsWith(".")) continue;
|
|
270
|
+
ids.push(e.name);
|
|
271
|
+
let text;
|
|
272
|
+
try {
|
|
273
|
+
text = fs.readFileSync(path.join(dir, e.name, "tasks.md"), "utf8");
|
|
274
|
+
} catch {
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
const counts = countTasksFromContent(text);
|
|
278
|
+
dispositioned += counts.dispositioned;
|
|
279
|
+
if (counts.open === 0 && counts.done > 0) complete.push(e.name);
|
|
280
|
+
}
|
|
281
|
+
let blocked = null;
|
|
282
|
+
try {
|
|
283
|
+
const parsed = JSON.parse(fs.readFileSync(path.join(project, ".freeharness", "sdd", "blockers.json"), "utf8"));
|
|
284
|
+
const list = Array.isArray(parsed) ? parsed : parsed && Array.isArray(parsed.blocked) ? parsed.blocked : null;
|
|
285
|
+
if (list) blocked = list.filter((id) => typeof id === "string" && ids.includes(id)).length;
|
|
286
|
+
} catch {
|
|
287
|
+
blocked = null;
|
|
288
|
+
}
|
|
289
|
+
return { inFlight: ids.length, complete, blocked, dispositioned, followUps: followUpCount(project) };
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/** One-line SDD status; null when there is no status. */
|
|
293
|
+
function openspecLine(status) {
|
|
294
|
+
if (!status) return null;
|
|
295
|
+
const shown = status.complete.slice(0, MAX_COMPLETE_IDS).map((id) => sanitizeTag(id));
|
|
296
|
+
const ids = status.complete.length > 0 ? ` (${shown.join(", ")}${status.complete.length > shown.length ? ", ..." : ""})` : "";
|
|
297
|
+
const blocked = status.blocked === null ? "" : `, ${status.blocked} blocked by unresolved headers`;
|
|
298
|
+
const dispositioned = status.dispositioned ? `, ${status.dispositioned} dispositioned` : "";
|
|
299
|
+
const followUps = status.followUps ? `, ${status.followUps} open follow-up(s)` : "";
|
|
300
|
+
return `openspec: ${status.inFlight} change(s) in flight, ${status.complete.length} complete but not archived${ids}${blocked}${dispositioned}${followUps}; rule: finish or archive before opening a new change (/opsx:archive)`;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/** The pointer block: v3 lines, SDD status line when present, WIP advisory when the cap is exceeded. */
|
|
304
|
+
function pointerFor(project) {
|
|
305
|
+
const line = openspecLine(openspecStatus(project));
|
|
306
|
+
return [...POINTER_LINES, ...(line ? [line] : []), ...wipAdvisoryLines(project), "</freeharness>"].join("\n");
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/** `<N>k`, non-finite treated as 0. */
|
|
310
|
+
function kTokens(n) {
|
|
311
|
+
const v = Number.isFinite(n) ? Number(n) : 0;
|
|
312
|
+
return `${Math.round(v / 1000)}k`;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/** Caps at 64 characters, keeps only `[\w.\-/ ,()]`: a directory name or telemetry field cannot inject a line of its own. */
|
|
316
|
+
function sanitizeTag(s) {
|
|
317
|
+
return typeof s === "string" ? s.trim().replace(/[^\w.\-/ ,()]/g, "").slice(0, 64).trim() : "";
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/** Strips control characters only, no length cap: see activeChanges (sdd-flow-2#7). */
|
|
321
|
+
function stripControl(s) {
|
|
322
|
+
return typeof s === "string" ? s.replace(/[\x00-\x1f\x7f]/g, "") : "";
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/** FH_ROUTING_FILE for the block after the pointer: strips control bytes (keeps \n/\t), truncates at ROUTING_CAP. block: null when missing, unreadable or empty. */
|
|
326
|
+
function routingBlock(file) {
|
|
327
|
+
const filePath = typeof file === "string" ? file.trim() : "";
|
|
328
|
+
if (!filePath) return { block: null, path: "" };
|
|
329
|
+
let raw;
|
|
330
|
+
try {
|
|
331
|
+
raw = fs.readFileSync(filePath, "utf8");
|
|
332
|
+
} catch {
|
|
333
|
+
return { block: null, path: filePath };
|
|
334
|
+
}
|
|
335
|
+
const stripped = raw.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, "").replace(/\n+$/, "");
|
|
336
|
+
if (!stripped.trim()) return { block: null, path: filePath };
|
|
337
|
+
if (stripped.length <= ROUTING_CAP) return { block: `<routing>\n${stripped}\n</routing>`, path: filePath };
|
|
338
|
+
const lines = stripped.split("\n");
|
|
339
|
+
let kept = "";
|
|
340
|
+
let cutAt = 0;
|
|
341
|
+
for (let i = 0; i < lines.length; i++) {
|
|
342
|
+
const next = kept ? `${kept}\n${lines[i]}` : lines[i];
|
|
343
|
+
if (next.length > ROUTING_CAP - ROUTING_MARKER_RESERVE) break;
|
|
344
|
+
kept = next;
|
|
345
|
+
cutAt = i + 1;
|
|
346
|
+
}
|
|
347
|
+
const truncated = lines.length - cutAt;
|
|
348
|
+
const body = kept ? `${kept}\n(+${truncated} rows truncated)` : `(+${truncated} rows truncated)`;
|
|
349
|
+
return { block: `<routing>\n${body}\n</routing>`, path: filePath };
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/** One telemetry line: `<change> <task> <tier>: main Nk, subagents Nk, observer Nk|n/a`. */
|
|
353
|
+
function telemetryLine(rec) {
|
|
354
|
+
const change = sanitizeTag(rec.change) || "untagged";
|
|
355
|
+
const task = sanitizeTag(rec.task) || "-";
|
|
356
|
+
const tier = sanitizeTag(rec.tier) || "-";
|
|
357
|
+
const sinks = rec && typeof rec.sinks === "object" && rec.sinks ? rec.sinks : {};
|
|
358
|
+
const main = kTokens(sinks.main && sinks.main.total);
|
|
359
|
+
const subagents = kTokens(sinks.subagents && sinks.subagents.total);
|
|
360
|
+
const observerTokens = sinks.observer && Number.isFinite(sinks.observer.tokens) ? sinks.observer.tokens : null;
|
|
361
|
+
const observer = observerTokens === null ? "n/a" : kTokens(observerTokens);
|
|
362
|
+
return `${change} ${task} ${tier}: main ${main}, subagents ${subagents}, observer ${observer}`;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/** Last N telemetry records, one line each; empty when the file is absent or carries no valid record. */
|
|
366
|
+
function telemetryTail(project, n) {
|
|
367
|
+
let text;
|
|
368
|
+
try {
|
|
369
|
+
text = fs.readFileSync(path.join(project, ".freeharness", "telemetry.jsonl"), "utf8");
|
|
370
|
+
} catch {
|
|
371
|
+
return [];
|
|
372
|
+
}
|
|
373
|
+
const records = [];
|
|
374
|
+
for (const raw of text.split("\n")) {
|
|
375
|
+
if (!raw.trim()) continue;
|
|
376
|
+
try {
|
|
377
|
+
const rec = JSON.parse(raw);
|
|
378
|
+
if (rec && typeof rec === "object") records.push(rec);
|
|
379
|
+
} catch {
|
|
380
|
+
// skip malformed line
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
return records.slice(-n).map(telemetryLine);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/** Latest cost-log snapshot context size/window for the session (usage.jsonl); null when absent. */
|
|
387
|
+
function contextFromSnapshot(sessionId) {
|
|
388
|
+
const file = process.env.FREEHARNESS_COST_LOG || path.join(os.homedir(), ".claude", "freeharness", "cost", "usage.jsonl");
|
|
389
|
+
let text;
|
|
390
|
+
try {
|
|
391
|
+
text = fs.readFileSync(file, "utf8");
|
|
392
|
+
} catch {
|
|
393
|
+
return null;
|
|
394
|
+
}
|
|
395
|
+
let best = null;
|
|
396
|
+
for (const raw of text.split("\n")) {
|
|
397
|
+
if (!raw.includes(sessionId)) continue;
|
|
398
|
+
try {
|
|
399
|
+
const rec = JSON.parse(raw);
|
|
400
|
+
if (rec && rec.session_id === sessionId && rec.usage && Number.isFinite(rec.context_tokens)) {
|
|
401
|
+
const window = Number.isInteger(rec.context_window) && rec.context_window > 0 ? rec.context_window : 200_000;
|
|
402
|
+
best = { tokens: rec.context_tokens, window };
|
|
403
|
+
}
|
|
404
|
+
} catch {
|
|
405
|
+
// skip malformed line
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
return best;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/** `context: <tokens>k of <window>k`, only at or above 60% of the window. */
|
|
412
|
+
function contextLine(sessionId) {
|
|
413
|
+
const snap = contextFromSnapshot(sessionId);
|
|
414
|
+
if (!snap) return null;
|
|
415
|
+
const pct = (snap.tokens / snap.window) * 100;
|
|
416
|
+
if (pct < 60) return null;
|
|
417
|
+
return `context: ${kTokens(snap.tokens)} of ${kTokens(snap.window)}`;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/** File operations: the PreCompact list when present, else a transcript count; null when unavailable. */
|
|
421
|
+
function fileOps(sessionId, transcript) {
|
|
422
|
+
const listed = path.join(stateDir(), `${safeId(sessionId)}.fileops.json`);
|
|
423
|
+
try {
|
|
424
|
+
const parsed = JSON.parse(fs.readFileSync(listed, "utf8"));
|
|
425
|
+
if (Array.isArray(parsed)) {
|
|
426
|
+
return parsed.map((x) => (typeof x === "string" ? x : x && typeof x.path === "string" ? `${x.path}${x.tool ? ` (${x.tool}${x.count ? ` x${x.count}` : ""})` : ""}` : "")).filter(Boolean);
|
|
427
|
+
}
|
|
428
|
+
} catch {
|
|
429
|
+
// fall through to the transcript
|
|
430
|
+
}
|
|
431
|
+
if (!transcript) return null;
|
|
432
|
+
let text;
|
|
433
|
+
try {
|
|
434
|
+
if (fs.statSync(transcript).size > MAX_TRANSCRIPT_BYTES) return null;
|
|
435
|
+
text = fs.readFileSync(transcript, "utf8");
|
|
436
|
+
} catch {
|
|
437
|
+
return null;
|
|
438
|
+
}
|
|
439
|
+
const counts = new Map();
|
|
440
|
+
for (const raw of text.split("\n")) {
|
|
441
|
+
if (!raw.includes('"tool_use"')) continue;
|
|
442
|
+
let rec;
|
|
443
|
+
try {
|
|
444
|
+
rec = JSON.parse(raw);
|
|
445
|
+
} catch {
|
|
446
|
+
continue;
|
|
447
|
+
}
|
|
448
|
+
const content = rec && rec.message && Array.isArray(rec.message.content) ? rec.message.content : [];
|
|
449
|
+
for (const block of content) {
|
|
450
|
+
if (!block || block.type !== "tool_use" || !FILE_TOOLS.has(block.name)) continue;
|
|
451
|
+
const input = block.input && typeof block.input === "object" ? block.input : {};
|
|
452
|
+
const file = input.file_path || input.notebook_path;
|
|
453
|
+
if (typeof file !== "string" || !file) continue;
|
|
454
|
+
const per = counts.get(file) || new Map();
|
|
455
|
+
per.set(block.name, (per.get(block.name) || 0) + 1);
|
|
456
|
+
counts.set(file, per);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
return [...counts.entries()].map(([file, per]) => `${file} (${[...per.entries()].map(([t, n]) => `${t} x${n}`).join(", ")})`);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/** Parses PreCompact summary: `## <Section>` headings, body verbatim. */
|
|
463
|
+
function precompactSummary(sessionId) {
|
|
464
|
+
let text;
|
|
465
|
+
try {
|
|
466
|
+
text = fs.readFileSync(path.join(stateDir(), `${safeId(sessionId)}.precompact.md`), "utf8");
|
|
467
|
+
} catch {
|
|
468
|
+
return {};
|
|
469
|
+
}
|
|
470
|
+
const out = {};
|
|
471
|
+
let current = null;
|
|
472
|
+
const buf = [];
|
|
473
|
+
const flush = () => {
|
|
474
|
+
if (current) out[current] = buf.join("\n").trim();
|
|
475
|
+
buf.length = 0;
|
|
476
|
+
};
|
|
477
|
+
for (const line of text.split("\n")) {
|
|
478
|
+
const m = /^##\s+(.+?)\s*$/.exec(line);
|
|
479
|
+
if (m) {
|
|
480
|
+
flush();
|
|
481
|
+
const name = SECTIONS.find((s) => s.toLowerCase() === m[1].toLowerCase());
|
|
482
|
+
current = name || null;
|
|
483
|
+
continue;
|
|
484
|
+
}
|
|
485
|
+
if (current) buf.push(line);
|
|
486
|
+
}
|
|
487
|
+
flush();
|
|
488
|
+
return out;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/** Renders the re-orientation block from pre-sliced pieces; the eight headings always print in full. */
|
|
492
|
+
function renderReorientation(source, activeLine, ctxLine, telemetryLines, telemetryDropped, opsLines, opsDropped, summary) {
|
|
493
|
+
const lines = [`<freeharness_reorientation source="${source}">`, activeLine];
|
|
494
|
+
if (ctxLine) lines.push(ctxLine);
|
|
495
|
+
lines.push("Telemetry (.freeharness/telemetry.jsonl, last 3):");
|
|
496
|
+
if (telemetryLines.length > 0) lines.push(...telemetryLines.map((l) => ` ${l}`));
|
|
497
|
+
else if (telemetryDropped === 0) lines.push(" (none)");
|
|
498
|
+
if (telemetryDropped > 0) lines.push(` (+${telemetryDropped} more)`);
|
|
499
|
+
lines.push("Session file operations:");
|
|
500
|
+
if (opsLines === null) {
|
|
501
|
+
lines.push(" (unavailable)");
|
|
502
|
+
} else {
|
|
503
|
+
if (opsLines.length > 0) lines.push(...opsLines.map((l) => ` - ${l}`));
|
|
504
|
+
else if (opsDropped === 0) lines.push(" (none)");
|
|
505
|
+
if (opsDropped > 0) lines.push(` - (+${opsDropped} more)`);
|
|
506
|
+
}
|
|
507
|
+
for (const s of SECTIONS) {
|
|
508
|
+
lines.push(`## ${s}`);
|
|
509
|
+
lines.push(summary[s] ? summary[s] : "(none)");
|
|
510
|
+
}
|
|
511
|
+
lines.push(RULE);
|
|
512
|
+
lines.push("</freeharness_reorientation>");
|
|
513
|
+
return lines.join("\n");
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/** Builds additionalContext: pointer, routing, re-orientation for compact/resume/fork. Past SHORTEN_AT, drops file-ops then telemetry then collapses routing to a path line; the skeleton is never shortened. */
|
|
517
|
+
function buildContext(payload) {
|
|
518
|
+
const source = typeof payload.source === "string" ? payload.source : "startup";
|
|
519
|
+
const project = process.env.CLAUDE_PROJECT_DIR || (typeof payload.cwd === "string" && payload.cwd ? payload.cwd : process.cwd());
|
|
520
|
+
const pointer = pointerFor(project);
|
|
521
|
+
const routing = routingBlock(process.env.FH_ROUTING_FILE || "");
|
|
522
|
+
let routingText = routing.block !== null ? routing.block : "Routing table unavailable: run freeharness update --scope user.";
|
|
523
|
+
if (!REORIENT.has(source)) return `${pointer}\n${routingText}`;
|
|
524
|
+
|
|
525
|
+
const sessionId = typeof payload.session_id === "string" ? payload.session_id : "";
|
|
526
|
+
const transcript = typeof payload.transcript_path === "string" ? payload.transcript_path : "";
|
|
527
|
+
const changes = activeChanges(project);
|
|
528
|
+
const activeLine = `Active change: ${changes.length > 0 ? changes.join("; ") : "(none)"}`;
|
|
529
|
+
const ctxLine = contextLine(sessionId);
|
|
530
|
+
const summary = precompactSummary(sessionId);
|
|
531
|
+
let telemetryLines = telemetryTail(project, 3);
|
|
532
|
+
let telemetryDropped = 0;
|
|
533
|
+
let opsLines = fileOps(sessionId, transcript);
|
|
534
|
+
let opsDropped = 0;
|
|
535
|
+
|
|
536
|
+
const assemble = () => `${pointer}\n${routingText}\n${renderReorientation(source, activeLine, ctxLine, telemetryLines, telemetryDropped, opsLines, opsDropped, summary)}`;
|
|
537
|
+
|
|
538
|
+
if (assemble().length > SHORTEN_AT && Array.isArray(opsLines)) {
|
|
539
|
+
while (opsLines.length > 0 && assemble().length > SHORTEN_AT) {
|
|
540
|
+
opsLines = opsLines.slice(0, -1);
|
|
541
|
+
opsDropped += 1;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
if (assemble().length > SHORTEN_AT) {
|
|
545
|
+
while (telemetryLines.length > 0 && assemble().length > SHORTEN_AT) {
|
|
546
|
+
telemetryLines = telemetryLines.slice(0, -1);
|
|
547
|
+
telemetryDropped += 1;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
if (assemble().length > SHORTEN_AT && routing.block !== null && routing.path) {
|
|
551
|
+
routingText = `Routing table: ${routing.path} (Read it before routing).`;
|
|
552
|
+
}
|
|
553
|
+
return assemble();
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/** Entry point: always print the pointer (at least). */
|
|
557
|
+
function main() {
|
|
558
|
+
if ((process.env.FREEHARNESS_HOOK_SESSION_START || "").toLowerCase() === "off") return;
|
|
559
|
+
let payload = {};
|
|
560
|
+
try {
|
|
561
|
+
const raw = process.env.FH_PAYLOAD || "";
|
|
562
|
+
if (raw.trim()) payload = JSON.parse(raw);
|
|
563
|
+
} catch {
|
|
564
|
+
payload = {};
|
|
565
|
+
}
|
|
566
|
+
if (!payload || typeof payload !== "object") payload = {};
|
|
567
|
+
let context = POINTER;
|
|
568
|
+
try {
|
|
569
|
+
context = buildContext(payload);
|
|
570
|
+
} catch (e) {
|
|
571
|
+
process.stderr.write(`freeharness-session-start: ${e instanceof Error ? e.message : String(e)}\n`);
|
|
572
|
+
}
|
|
573
|
+
process.stdout.write(JSON.stringify({ hookSpecificOutput: { hookEventName: "SessionStart", additionalContext: context } }));
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
main();
|
|
577
|
+
JS
|
|
578
|
+
then exit 0; fi
|
|
579
|
+
|
|
580
|
+
# Node missing/failed: the plain pointer, JSON-escaped by hand.
|
|
581
|
+
POINTER='<freeharness>\nFreeHarness active. Plan non-trivial work with OpenSpec (/opsx: proposal, specs, design, tasks).\nExecute with skills: test-driven-development, subagent-driven-development; review with code-review\nplus the reviewer agents (python/database/security) and pr-reviewer (mle lens); gate with verification-before-completion.\nMemory is the free-mem plugin. The user'"'"'s CLAUDE.md/AGENTS.md always takes precedence over this pointer.\n</freeharness>'
|
|
582
|
+
printf '{"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":"%s"}}\n' "$POINTER"
|
|
583
|
+
exit 0
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$comment": "FreeHarness auto-memory tier-0 spike for Claude Code (memory capability, optional; OFF by default). Merged additively into settings.json. When FREEHARNESS_AUTO_MEMORY=on, SessionEnd curates one line per project into ~/.claude/freeharness/MEMORY.md from the last entry of <cwd>/.freeharness/dogfood.md, idempotently. Without the flag the hook is a no-op. Never blocks, never prints, exit 0. timeout 10 raises the 1.5 s SessionEnd budget Claude Code applies by default.",
|
|
3
|
+
"hooks": {
|
|
4
|
+
"SessionEnd": [
|
|
5
|
+
{
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "node \"${CLAUDE_PROJECT_DIR}/.freeharness/hooks/freeharness-auto-memory.mjs\"",
|
|
10
|
+
"timeout": 10
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$comment": "FreeHarness SessionStart hook fragment for Claude Code (rules capability). The installer merges this into <project>/.claude/settings.json additively (never removing existing entries, e.g. the upstream claude-mem plugin at user level is untouched). startup|clear: the short pointer. compact|resume|fork: the pointer plus the re-orientation block (active change id, dogfood tail, session file operations, the eight-section checkpoint pre-filled from ~/.claude/freeharness/state/<session>.precompact.md, the re-read rule). Disable: FREEHARNESS_HOOK_SESSION_START=off or remove this entry.",
|
|
3
|
+
"hooks": {
|
|
4
|
+
"SessionStart": [
|
|
5
|
+
{
|
|
6
|
+
"matcher": "startup|clear|compact|resume|fork",
|
|
7
|
+
"hooks": [
|
|
8
|
+
{
|
|
9
|
+
"type": "command",
|
|
10
|
+
"command": "\"${CLAUDE_PROJECT_DIR}/.freeharness/hooks/freeharness-session-start\"",
|
|
11
|
+
"async": false
|
|
12
|
+
}
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$comment": "FreeHarness config guard for Claude Code (rules capability). Merged additively into settings.json. PreToolUse on Edit|Write|MultiEdit|Bash: denies writes to settings.json, settings.local.json, .mcp.json, CLAUDE.md, hook scripts and the plugin hooks.json unless <project>/.freeharness/allow-config-edit (or ~/.claude/freeharness/allow-config-edit) exists and is younger than --ttl minutes (30; a numeric epoch-ms or ISO body sets an explicit expiry). Bash is denied only when the command writes to such a path (redirect, sed -i, rm, mv, cp, tee, truncate, touch); reads pass. The deny reason names the marker and the `touch` one-liner for the user. `freeharness init`/`update` set and remove their own marker. Disable: FREEHARNESS_HOOK_CONFIG_GUARD=off or remove this entry.",
|
|
3
|
+
"hooks": {
|
|
4
|
+
"PreToolUse": [
|
|
5
|
+
{
|
|
6
|
+
"matcher": "Edit|Write|MultiEdit|Bash",
|
|
7
|
+
"hooks": [
|
|
8
|
+
{
|
|
9
|
+
"type": "command",
|
|
10
|
+
"command": "node \"${CLAUDE_PROJECT_DIR}/.freeharness/hooks/freeharness-config-guard.mjs\" --ttl 30"
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$comment": "FreeHarness context monitor for Claude Code (cost capability). Merged additively into settings.json. Stop: reads the session's latest cost snapshot (context_tokens) and prints one systemMessage line to the user when the context crosses 60% and then 85% of --budget tokens (200k), naming the `# Compact instructions` block of CLAUDE.md for /compact. Each threshold once per session (~/.claude/freeharness/state/<session>.context.json). Never a Stop additionalContext (that would force another turn), never blocks. Disable: FREEHARNESS_HOOK_CONTEXT_MONITOR=off or remove this entry.",
|
|
3
|
+
"hooks": {
|
|
4
|
+
"Stop": [
|
|
5
|
+
{
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "node \"${CLAUDE_PROJECT_DIR}/.freeharness/hooks/freeharness-context-monitor.mjs\" --budget 200000 --thresholds 60,85"
|
|
10
|
+
}
|
|
11
|
+
]
|
|
12
|
+
}
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$comment": "FreeHarness converge loop for Claude Code (sdd capability; shape from the anthropics/claude-code ralph-loop plugin, Apache-2.0, fitted to tasks.md). Merged additively into settings.json. Stop: a no-op unless <project>/.freeharness/converge.json exists ({\"change\": \"<id>\", \"max_rounds\": 5, \"promise\": \"<exit condition>\"}). While openspec/changes/<change>/tasks.md has unchecked tasks and rounds < max_rounds it returns {\"decision\":\"block\",\"reason\":\"<next task + its Verify line>\"} so the agent keeps working; every round is appended to .freeharness/converge.log; at the limit or when every task is checked the session ends with a systemMessage. A tasks.md that is missing or has no checkbox lines disables the loop (converge.json gets \"disabled\": true + reason). Disable: FREEHARNESS_HOOK_CONVERGE=off, delete converge.json, or remove this entry.",
|
|
3
|
+
"hooks": {
|
|
4
|
+
"Stop": [
|
|
5
|
+
{
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "node \"${CLAUDE_PROJECT_DIR}/.freeharness/hooks/freeharness-converge.mjs\""
|
|
10
|
+
}
|
|
11
|
+
]
|
|
12
|
+
}
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
}
|