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,483 @@
|
|
|
1
|
+
// `openspec archive <id>` — folds a change's delta specs into openspec/specs/
|
|
2
|
+
// and moves the change into openspec/changes/archive/.
|
|
3
|
+
//
|
|
4
|
+
// Non-interactive and store-free. Built on the vendored specs-apply slice
|
|
5
|
+
// (vendor/openspec/apply/specs-apply.ts) and the vendored Validator. This is the
|
|
6
|
+
// one command that writes the spec source of truth, so it (1) refuses to archive
|
|
7
|
+
// an invalid change and (2) folds every delta before moving the change, never a
|
|
8
|
+
// move-without-fold that would leave openspec/specs/ stale.
|
|
9
|
+
//
|
|
10
|
+
// V4-P4 additions:
|
|
11
|
+
// - Archive blockers (#1710 findArchiveBlockers, reported INFO by the validator)
|
|
12
|
+
// and unchecked tasks are listed before anything is written; without `--yes`
|
|
13
|
+
// they refuse the archive (upstream prompts; this shim is non-interactive).
|
|
14
|
+
// - Supersession: a `Supersedes: <change-id>` line in the change's design.md
|
|
15
|
+
// names an archived change this one replaces. Archive prints the link, writes
|
|
16
|
+
// the reverse link (`Superseded-by: <id>`) into the archived record, and
|
|
17
|
+
// refuses a MODIFIED requirement that carries a superseded requirement's text
|
|
18
|
+
// unchanged (an in-place edit of a published decision).
|
|
19
|
+
// - Unresolved delta headers: a MODIFIED / REMOVED / RENAMED whose header is not
|
|
20
|
+
// in openspec/specs/<cap>/spec.md is listed with the blockers and refuses the
|
|
21
|
+
// archive unless `--force`, which folds the rest and skips those deltas
|
|
22
|
+
// (vendor/openspec/validation/delta-resolution.ts).
|
|
23
|
+
// - Metadata `depends_on` / `supersedes`: an unarchived `depends_on` change is
|
|
24
|
+
// listed among the blockers (refusal, same as an unchecked task); `supersedes`
|
|
25
|
+
// cross-links archived changes the same way the design.md `Supersedes:` line
|
|
26
|
+
// does, as the union of both sources.
|
|
27
|
+
import { promises as fs } from 'node:fs';
|
|
28
|
+
import * as fssync from 'node:fs';
|
|
29
|
+
import * as path from 'node:path';
|
|
30
|
+
import { Validator } from '../../vendor/openspec/validation/validator.js';
|
|
31
|
+
import { findSpecUpdates, buildUpdatedSpec, writeUpdatedSpec, } from '../../vendor/openspec/apply/specs-apply.js';
|
|
32
|
+
import { parseDeltaSpec } from '../../vendor/openspec/validation/parsers/requirement-blocks.js';
|
|
33
|
+
import { discoverSpecFiles } from '../../vendor/openspec/validation/spec-discovery.js';
|
|
34
|
+
import { findUnresolvedDeltaHeaders, } from '../../vendor/openspec/validation/delta-resolution.js';
|
|
35
|
+
import { parseAnnotatedTaskLines } from '../../vendor/openspec/validation/task-lines.js';
|
|
36
|
+
import { readChangeMetadata } from '../../vendor/openspec/engine/change-metadata.js';
|
|
37
|
+
import { changesDirFor, resolveChangeId, taskLabel, validateChangeExists } from './shared.js';
|
|
38
|
+
import { readFollowUps } from './follow-up.js';
|
|
39
|
+
const SUPERSEDES_LINE = /^\s*Supersedes:\s*(.+?)\s*$/gm;
|
|
40
|
+
const SUPERSEDED_BY_PREFIX = 'Superseded-by:';
|
|
41
|
+
/**
|
|
42
|
+
* Reads the `Supersedes: <change-id>[, <change-id>...]` lines of a design.md.
|
|
43
|
+
* Convention: one or more lines, each `Supersedes:` followed by archived change
|
|
44
|
+
* ids separated by commas. Absent lines -> empty list. Template placeholders
|
|
45
|
+
* live in HTML comments; a line left as `Supersedes: <!-- ... -->` names nothing.
|
|
46
|
+
*/
|
|
47
|
+
function parseDesignSupersedes(designText) {
|
|
48
|
+
const content = designText.replace(/<!--[\s\S]*?-->/g, '');
|
|
49
|
+
const ids = [];
|
|
50
|
+
for (const match of content.matchAll(SUPERSEDES_LINE)) {
|
|
51
|
+
for (const id of match[1].split(',')) {
|
|
52
|
+
const trimmed = id.trim().replace(/^`|`$/g, '');
|
|
53
|
+
if (trimmed && !ids.includes(trimmed))
|
|
54
|
+
ids.push(trimmed);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return ids;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Resolves the ids a change supersedes: the deduplicated union of design.md's
|
|
61
|
+
* `Supersedes:` lines and the metadata `supersedes` list, design.md first.
|
|
62
|
+
*/
|
|
63
|
+
export function readSupersedes(designText, metadata) {
|
|
64
|
+
const ids = parseDesignSupersedes(designText);
|
|
65
|
+
for (const id of metadata?.supersedes ?? []) {
|
|
66
|
+
if (!ids.includes(id))
|
|
67
|
+
ids.push(id);
|
|
68
|
+
}
|
|
69
|
+
return ids;
|
|
70
|
+
}
|
|
71
|
+
/** Collapses whitespace so two requirement blocks compare on their words. */
|
|
72
|
+
function normalizeBlockText(raw) {
|
|
73
|
+
return raw.replace(/\s+/g, ' ').trim();
|
|
74
|
+
}
|
|
75
|
+
/** Every requirement block (any delta section) declared by a change's specs. */
|
|
76
|
+
async function collectRequirementBlocks(changeDir) {
|
|
77
|
+
const specsDir = path.join(changeDir, 'specs');
|
|
78
|
+
const out = [];
|
|
79
|
+
for (const file of await safeDiscover(specsDir)) {
|
|
80
|
+
const content = await fs.readFile(file.specFile, 'utf-8');
|
|
81
|
+
const plan = parseDeltaSpec(content);
|
|
82
|
+
for (const block of [...plan.added, ...plan.modified])
|
|
83
|
+
out.push({ spec: file.id, block });
|
|
84
|
+
}
|
|
85
|
+
return out;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Resolves the change's `Supersedes:` lines and enforces the supersession
|
|
89
|
+
* rules: every id names an archived change, and no MODIFIED requirement of this
|
|
90
|
+
* change repeats a superseded requirement's text unchanged.
|
|
91
|
+
*/
|
|
92
|
+
export async function checkSupersession(changeName, changeDir, changesDir, metadata) {
|
|
93
|
+
const designPath = path.join(changeDir, 'design.md');
|
|
94
|
+
const designText = fssync.existsSync(designPath) ? fssync.readFileSync(designPath, 'utf-8') : '';
|
|
95
|
+
const ids = readSupersedes(designText, metadata);
|
|
96
|
+
if (ids.length === 0)
|
|
97
|
+
return [];
|
|
98
|
+
const links = [];
|
|
99
|
+
// Only MODIFIED blocks are in-place edits; ADDED blocks are new decisions.
|
|
100
|
+
const modifiedOnly = [];
|
|
101
|
+
for (const file of await safeDiscover(path.join(changeDir, 'specs'))) {
|
|
102
|
+
const plan = parseDeltaSpec(await fs.readFile(file.specFile, 'utf-8'));
|
|
103
|
+
for (const block of plan.modified)
|
|
104
|
+
modifiedOnly.push({ spec: file.id, block });
|
|
105
|
+
}
|
|
106
|
+
for (const id of ids) {
|
|
107
|
+
if (id === changeName) {
|
|
108
|
+
throw new Error(`Change '${changeName}' declares 'Supersedes: ${id}' naming itself. Fix: name the archived change it replaces.`);
|
|
109
|
+
}
|
|
110
|
+
const archived = path.join(changesDir, 'archive', id);
|
|
111
|
+
if (!fssync.existsSync(archived) || !fssync.statSync(archived).isDirectory()) {
|
|
112
|
+
throw new Error(`Change '${changeName}' declares 'Supersedes: ${id}' but openspec/changes/archive/${id} does not exist. ` +
|
|
113
|
+
`Fix: supersede only archived changes (check the id with 'ls openspec/changes/archive'), or archive '${id}' first.`);
|
|
114
|
+
}
|
|
115
|
+
const superseded = await collectRequirementBlocks(archived);
|
|
116
|
+
for (const { spec, block } of modifiedOnly) {
|
|
117
|
+
const same = superseded.find((s) => normalizeBlockText(s.block.raw) === normalizeBlockText(block.raw));
|
|
118
|
+
if (same) {
|
|
119
|
+
throw new Error(`Change '${changeName}' supersedes '${id}' but MODIFIED "${block.name}" (specs/${spec}) repeats the ` +
|
|
120
|
+
`superseded requirement text unchanged: a published decision is never edited in place. ` +
|
|
121
|
+
`Fix: state the new decision as ADDED (or RENAMED) with its own text and leave the superseded text in ` +
|
|
122
|
+
`openspec/changes/archive/${id}; when only part of the decision changes, keep the original requirement ` +
|
|
123
|
+
`active and cross-link it.`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
links.push({ change: id, path: archived });
|
|
127
|
+
}
|
|
128
|
+
return links;
|
|
129
|
+
}
|
|
130
|
+
async function safeDiscover(specsDir) {
|
|
131
|
+
try {
|
|
132
|
+
return await discoverSpecFiles(specsDir);
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
if (error.code === 'ENOENT')
|
|
136
|
+
return [];
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/** Appends the reverse link to the archived record's design.md (idempotent). */
|
|
141
|
+
async function writeSupersededBy(link, changeName) {
|
|
142
|
+
const designPath = path.join(link.path, 'design.md');
|
|
143
|
+
const line = `${SUPERSEDED_BY_PREFIX} ${changeName}`;
|
|
144
|
+
const existing = fssync.existsSync(designPath) ? await fs.readFile(designPath, 'utf-8') : '';
|
|
145
|
+
if (existing.split('\n').some((l) => l.trim() === line))
|
|
146
|
+
return;
|
|
147
|
+
const separator = existing.length === 0 ? '' : existing.endsWith('\n') ? '\n' : '\n\n';
|
|
148
|
+
await fs.writeFile(designPath, `${existing}${separator}${line}\n`, 'utf-8');
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Lines that describe what archive would refuse or what is still open. The
|
|
152
|
+
* unresolved headers are listed here as well so one refusal names everything.
|
|
153
|
+
*/
|
|
154
|
+
export function listArchiveBlockers(changeDir, issues, unresolved = []) {
|
|
155
|
+
const blockers = [];
|
|
156
|
+
for (const issue of issues) {
|
|
157
|
+
if (issue.level === 'INFO' && /^Archive would refuse this delta/.test(issue.message)) {
|
|
158
|
+
blockers.push(`[INFO] ${issue.path}: ${issue.message}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
for (const u of unresolved)
|
|
162
|
+
blockers.push(`[WARNING] ${u.entryPath}: ${u.message}`);
|
|
163
|
+
const tasksPath = path.join(changeDir, 'tasks.md');
|
|
164
|
+
if (fssync.existsSync(tasksPath)) {
|
|
165
|
+
// A dispositioned task is resolved outside this change (see
|
|
166
|
+
// dispositionedTasks below), so it is never a blocker; only a plain
|
|
167
|
+
// unchecked task refuses the archive.
|
|
168
|
+
const open = parseAnnotatedTaskLines(fssync.readFileSync(tasksPath, 'utf-8')).filter((t) => !t.done && !t.disposition);
|
|
169
|
+
if (open.length > 0) {
|
|
170
|
+
blockers.push(`[INFO] tasks.md: ${open.length} unchecked task(s); first: ${open[0].description || '(no description)'}`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return blockers;
|
|
174
|
+
}
|
|
175
|
+
const DISPOSITION_KINDS = new Set(['superseded', 'moved', 'external']);
|
|
176
|
+
/**
|
|
177
|
+
* Why a task's disposition does not resolve it, or null when it does: a kind
|
|
178
|
+
* outside the three documented ones, an `external` line without a reference,
|
|
179
|
+
* or a `superseded`/`moved` target `resolveChangeId` cannot place in flight or
|
|
180
|
+
* archived. Re-checked here (not merely trusted from an earlier `validate`
|
|
181
|
+
* run) the same way `listDependencyBlockers` below re-checks `depends_on`.
|
|
182
|
+
*/
|
|
183
|
+
function dispositionBlockerReason(projectRoot, disposition) {
|
|
184
|
+
const { kind, target } = disposition;
|
|
185
|
+
if (!DISPOSITION_KINDS.has(kind)) {
|
|
186
|
+
return `disposition kind '${kind}' is none of superseded, moved, external`;
|
|
187
|
+
}
|
|
188
|
+
if (kind === 'external') {
|
|
189
|
+
return target.trim() === '' ? 'an external disposition needs a reference' : null;
|
|
190
|
+
}
|
|
191
|
+
return resolveChangeId(projectRoot, target) === null
|
|
192
|
+
? `${kind} disposition names unresolved change '${target}'`
|
|
193
|
+
: null;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* The change's dispositioned tasks (unchecked, carrying a `Disposition:`
|
|
197
|
+
* line), for the archive result to print grouped by kind. `listArchiveBlockers`
|
|
198
|
+
* above already excludes these from the unchecked-task blocker; a task whose
|
|
199
|
+
* disposition does not resolve is still listed here for display, but also
|
|
200
|
+
* comes back as a blocker from `listDispositionBlockers`.
|
|
201
|
+
*/
|
|
202
|
+
function dispositionedTasks(changeDir) {
|
|
203
|
+
const tasksPath = path.join(changeDir, 'tasks.md');
|
|
204
|
+
if (!fssync.existsSync(tasksPath))
|
|
205
|
+
return [];
|
|
206
|
+
const tasks = parseAnnotatedTaskLines(fssync.readFileSync(tasksPath, 'utf-8'));
|
|
207
|
+
const out = [];
|
|
208
|
+
for (const task of tasks) {
|
|
209
|
+
if (task.done || !task.disposition)
|
|
210
|
+
continue;
|
|
211
|
+
out.push({
|
|
212
|
+
kind: task.disposition.kind,
|
|
213
|
+
task: taskLabel(task),
|
|
214
|
+
target: task.disposition.target,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
return out;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Lists every unchecked task whose disposition does not resolve it (an
|
|
221
|
+
* unknown kind, an unresolved `superseded`/`moved` target, or an empty
|
|
222
|
+
* `external` reference), naming the task and the reason. Such a task stays a
|
|
223
|
+
* blocker exactly like a plain unchecked task; a resolving disposition never
|
|
224
|
+
* appears here.
|
|
225
|
+
*/
|
|
226
|
+
function listDispositionBlockers(projectRoot, changeDir) {
|
|
227
|
+
const tasksPath = path.join(changeDir, 'tasks.md');
|
|
228
|
+
if (!fssync.existsSync(tasksPath))
|
|
229
|
+
return [];
|
|
230
|
+
const tasks = parseAnnotatedTaskLines(fssync.readFileSync(tasksPath, 'utf-8'));
|
|
231
|
+
const blockers = [];
|
|
232
|
+
for (const task of tasks) {
|
|
233
|
+
if (task.done || !task.disposition)
|
|
234
|
+
continue;
|
|
235
|
+
const reason = dispositionBlockerReason(projectRoot, task.disposition);
|
|
236
|
+
if (reason) {
|
|
237
|
+
blockers.push(`[INFO] tasks.md: task ${taskLabel(task)} disposition does not resolve it (${reason})`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return blockers;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Lists every `depends_on` change that is not archived, naming it and its
|
|
244
|
+
* current state (in flight, or unresolved). Has the same effect as an
|
|
245
|
+
* unchecked task: archiving without `--yes` is refused while any are open.
|
|
246
|
+
*/
|
|
247
|
+
function listDependencyBlockers(projectRoot, metadata) {
|
|
248
|
+
const blockers = [];
|
|
249
|
+
for (const id of metadata?.depends_on ?? []) {
|
|
250
|
+
const resolution = resolveChangeId(projectRoot, id);
|
|
251
|
+
if (resolution !== 'archived') {
|
|
252
|
+
blockers.push(`[INFO] depends_on: '${id}' is not archived (${resolution ?? 'unresolved'})`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return blockers;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* The validator's "Archive would refuse this delta" pass re-run the way a
|
|
259
|
+
* `--force` fold runs (unresolved headers skipped), so a waived header is not
|
|
260
|
+
* still listed as a refusal while a real conflict (an ADDED that already
|
|
261
|
+
* exists) still is.
|
|
262
|
+
*/
|
|
263
|
+
async function forcedFoldBlockers(changeDir, specsDir, changeName) {
|
|
264
|
+
const out = [];
|
|
265
|
+
let updates;
|
|
266
|
+
try {
|
|
267
|
+
updates = await findSpecUpdates(changeDir, specsDir);
|
|
268
|
+
}
|
|
269
|
+
catch (error) {
|
|
270
|
+
return [`[INFO] specs: Could not check archive merge conflicts: ${error instanceof Error ? error.message : String(error)}`];
|
|
271
|
+
}
|
|
272
|
+
for (const update of updates) {
|
|
273
|
+
try {
|
|
274
|
+
await buildUpdatedSpec(update, changeName, { silent: true, skipUnresolved: true });
|
|
275
|
+
}
|
|
276
|
+
catch (error) {
|
|
277
|
+
if (error?.code !== undefined)
|
|
278
|
+
continue;
|
|
279
|
+
out.push(`[INFO] ${update.id}/spec.md: Archive would refuse this delta: ${error instanceof Error ? error.message : String(error)}`);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return out;
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Archive a change: validate it, fold its delta specs into openspec/specs/, then
|
|
286
|
+
* move openspec/changes/<name>/ to openspec/changes/archive/<name>/.
|
|
287
|
+
*
|
|
288
|
+
* Throws (with an actionable message) rather than partially archiving when the
|
|
289
|
+
* change is invalid, a fold fails, or the archive destination is already taken.
|
|
290
|
+
*/
|
|
291
|
+
export async function archiveChange(changeName, { projectRoot, yes, force }) {
|
|
292
|
+
// Resolve + guard the change name against the active changes directory.
|
|
293
|
+
const name = validateChangeExists(changeName, projectRoot);
|
|
294
|
+
const changesDir = changesDirFor(projectRoot);
|
|
295
|
+
const changeDir = path.join(changesDir, name);
|
|
296
|
+
const specsDir = path.join(projectRoot, 'openspec', 'specs');
|
|
297
|
+
// Metadata (.openspec.yaml) drives the depends_on blockers and the supersedes
|
|
298
|
+
// union below. Metadata that cannot be read degrades to "no dependency/supersedes
|
|
299
|
+
// data" rather than aborting the archive, but the lost gate is listed among the
|
|
300
|
+
// blockers: without that line the archive would run with its dependency check
|
|
301
|
+
// silently skipped.
|
|
302
|
+
let metadata;
|
|
303
|
+
let metadataBlockers = [];
|
|
304
|
+
try {
|
|
305
|
+
metadata = readChangeMetadata(changeDir, projectRoot);
|
|
306
|
+
}
|
|
307
|
+
catch (error) {
|
|
308
|
+
metadata = null;
|
|
309
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
310
|
+
metadataBlockers = [`[INFO] metadata unreadable: ${reason}; depends_on and supersedes not checked`];
|
|
311
|
+
}
|
|
312
|
+
// 1. Validate the change. This is the authoritative delta-spec pass — the same
|
|
313
|
+
// verdict `openspec validate <change>` uses. Abort on any ERROR; an empty
|
|
314
|
+
// change (nothing to validate) reports valid and proceeds.
|
|
315
|
+
const validator = new Validator(false);
|
|
316
|
+
const report = await validator.validateChangeDeltaSpecs(changeDir, {
|
|
317
|
+
mainSpecsDir: specsDir,
|
|
318
|
+
projectRoot,
|
|
319
|
+
});
|
|
320
|
+
if (!report.valid) {
|
|
321
|
+
const details = report.issues
|
|
322
|
+
.filter((i) => i.level === 'ERROR')
|
|
323
|
+
.map((i) => ` [${i.level}] ${i.line ? `${i.path}:${i.line}` : i.path}: ${i.message}`)
|
|
324
|
+
.join('\n');
|
|
325
|
+
throw new Error(`Change '${name}' is not valid; refusing to archive:\n${details}`);
|
|
326
|
+
}
|
|
327
|
+
// 1b. Blockers: deltas archive would refuse (validator INFO, #1710),
|
|
328
|
+
// unresolved delta headers (unless --force already waives them) and
|
|
329
|
+
// unchecked tasks. Listed together; without --yes they refuse the archive
|
|
330
|
+
// with nothing written (upstream asks; this shim cannot).
|
|
331
|
+
const unresolved = await findUnresolvedDeltaHeaders(changeDir, specsDir, changesDir);
|
|
332
|
+
const dependencyBlockers = listDependencyBlockers(projectRoot, metadata);
|
|
333
|
+
const dispositionBlockers = listDispositionBlockers(projectRoot, changeDir);
|
|
334
|
+
const blockers = force
|
|
335
|
+
? [
|
|
336
|
+
...(await forcedFoldBlockers(changeDir, specsDir, name)),
|
|
337
|
+
...listArchiveBlockers(changeDir, report.issues.filter((i) => !/^Archive would refuse this delta/.test(i.message))),
|
|
338
|
+
...metadataBlockers,
|
|
339
|
+
...dependencyBlockers,
|
|
340
|
+
...dispositionBlockers,
|
|
341
|
+
]
|
|
342
|
+
: [
|
|
343
|
+
...listArchiveBlockers(changeDir, report.issues, unresolved),
|
|
344
|
+
...metadataBlockers,
|
|
345
|
+
...dependencyBlockers,
|
|
346
|
+
...dispositionBlockers,
|
|
347
|
+
];
|
|
348
|
+
if (blockers.length > 0 && !yes) {
|
|
349
|
+
throw new Error(`Change '${name}' has archive blockers; nothing was written:\n ${blockers.join('\n ')}\n` +
|
|
350
|
+
`Fix the blockers, or pass --yes to archive with open tasks (a refused delta still aborts the fold` +
|
|
351
|
+
`${unresolved.length > 0 ? '; an unresolved header needs --force' : ''}).`);
|
|
352
|
+
}
|
|
353
|
+
// 1c. Unresolved headers are never waived by --yes: the fold would drop them
|
|
354
|
+
// silently and the published spec would keep the old requirement. --force
|
|
355
|
+
// archives anyway and skips them, each named below.
|
|
356
|
+
if (unresolved.length > 0 && !force) {
|
|
357
|
+
throw new Error(`Change '${name}' has ${unresolved.length} unresolved delta header(s); nothing was written:\n ` +
|
|
358
|
+
`${unresolved.map((u) => `[WARNING] ${u.entryPath}: ${u.message}`).join('\n ')}\n` +
|
|
359
|
+
`Pass --force to archive anyway (those deltas are skipped; the change record keeps their text).`);
|
|
360
|
+
}
|
|
361
|
+
// 1d. Supersession links (design.md `Supersedes:`), checked before any write.
|
|
362
|
+
const supersedes = await checkSupersession(name, changeDir, changesDir, metadata);
|
|
363
|
+
// 1e. Dispositioned tasks, read while the change still sits at changeDir (the
|
|
364
|
+
// move below relocates it).
|
|
365
|
+
const dispositions = dispositionedTasks(changeDir);
|
|
366
|
+
// 2. Fold the change's delta specs into openspec/specs/. buildUpdatedSpec does
|
|
367
|
+
// the ADDED/MODIFIED/REMOVED/RENAMED merge; writeUpdatedSpec writes it in
|
|
368
|
+
// place. A fold that resolves to zero operations is an already-synced
|
|
369
|
+
// (early-sync) delta and is skipped, matching upstream's write gate.
|
|
370
|
+
//
|
|
371
|
+
// Capability RETIREMENT (deleting a main spec whose last requirement a
|
|
372
|
+
// delta removed) is gated upstream behind an explicit retire_capabilities
|
|
373
|
+
// marker and is out of this vendored slice; here every fold rebuilds and
|
|
374
|
+
// writes the spec, never deletes one.
|
|
375
|
+
// Build every fold BEFORE writing any: buildUpdatedSpec throws on an
|
|
376
|
+
// unapplicable delta, so planning the whole change first means one bad
|
|
377
|
+
// delta aborts the archive with openspec/specs/ untouched, rather than
|
|
378
|
+
// leaving earlier specs half-written.
|
|
379
|
+
const updates = await findSpecUpdates(changeDir, specsDir);
|
|
380
|
+
const plan = [];
|
|
381
|
+
const skipUnresolved = force === true && unresolved.length > 0;
|
|
382
|
+
for (const update of updates) {
|
|
383
|
+
const built = await buildUpdatedSpec(update, name, { silent: true, skipUnresolved });
|
|
384
|
+
plan.push({ update, rebuilt: built.rebuilt, counts: built.counts });
|
|
385
|
+
}
|
|
386
|
+
const applied = [];
|
|
387
|
+
for (const { update, rebuilt, counts } of plan) {
|
|
388
|
+
const total = counts.added + counts.modified + counts.removed + counts.renamed;
|
|
389
|
+
if (total === 0)
|
|
390
|
+
continue; // already in sync — nothing to write
|
|
391
|
+
await writeUpdatedSpec(update, rebuilt, counts, { silent: true });
|
|
392
|
+
applied.push(update.id);
|
|
393
|
+
}
|
|
394
|
+
// 3. Move the change into changes/archive/<name>/. Refuse to clobber an
|
|
395
|
+
// existing archived change of the same name.
|
|
396
|
+
const archiveDir = path.join(changesDir, 'archive');
|
|
397
|
+
const archivedTo = path.join(archiveDir, name);
|
|
398
|
+
await fs.mkdir(archiveDir, { recursive: true });
|
|
399
|
+
if (fssync.existsSync(archivedTo)) {
|
|
400
|
+
throw new Error(`An archived change already exists at ${archivedTo}; move or remove it before archiving '${name}'.`);
|
|
401
|
+
}
|
|
402
|
+
await fs.rename(changeDir, archivedTo);
|
|
403
|
+
// 4. Reverse links: the superseded archived record names its successor.
|
|
404
|
+
for (const link of supersedes)
|
|
405
|
+
await writeSupersededBy(link, name);
|
|
406
|
+
// 5. Open follow-ups, read from the archived location (the file moved with the
|
|
407
|
+
// directory in step 3): printed as a notice, never a blocker. An item is open
|
|
408
|
+
// while its checkbox is unchecked, the rule `follow-up list`, `follow-up claim`
|
|
409
|
+
// and the session-start pointer read it by, so the four never disagree.
|
|
410
|
+
const followUps = readFollowUps(archivedTo).filter((item) => !item.done);
|
|
411
|
+
return { applied, archivedTo, supersedes, skipped: skipUnresolved ? unresolved : [], dispositions, followUps };
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* CLI wrapper for `openspec archive <id>`. Returns the process exit code and
|
|
415
|
+
* emits either the single-document `--json` result or human-readable lines.
|
|
416
|
+
*/
|
|
417
|
+
export async function archiveHandler(projectRoot, changeName, options) {
|
|
418
|
+
try {
|
|
419
|
+
const result = await archiveChange(changeName, {
|
|
420
|
+
projectRoot,
|
|
421
|
+
...(options.yes !== undefined ? { yes: options.yes } : {}),
|
|
422
|
+
...(options.force !== undefined ? { force: options.force } : {}),
|
|
423
|
+
});
|
|
424
|
+
if (options.json) {
|
|
425
|
+
console.log(JSON.stringify({
|
|
426
|
+
archived: true,
|
|
427
|
+
change: changeName,
|
|
428
|
+
applied: result.applied,
|
|
429
|
+
archivedTo: result.archivedTo,
|
|
430
|
+
supersedes: result.supersedes,
|
|
431
|
+
skipped: result.skipped.map((u) => ({ operation: u.operation, header: u.header, spec: u.specId, ...(u.addedBy ? { addedBy: u.addedBy } : {}) })),
|
|
432
|
+
dispositions: result.dispositions,
|
|
433
|
+
followUps: result.followUps,
|
|
434
|
+
}, null, 2));
|
|
435
|
+
}
|
|
436
|
+
else {
|
|
437
|
+
if (result.applied.length > 0) {
|
|
438
|
+
console.log(`Applied spec updates: ${result.applied.join(', ')}`);
|
|
439
|
+
}
|
|
440
|
+
else {
|
|
441
|
+
console.log('No spec updates to apply (already in sync).');
|
|
442
|
+
}
|
|
443
|
+
for (const link of result.supersedes) {
|
|
444
|
+
console.log(`Supersedes ${link.change} (${link.path}); reverse link written to its design.md`);
|
|
445
|
+
}
|
|
446
|
+
for (const u of result.skipped) {
|
|
447
|
+
console.log(`Warning (--force): skipped ${u.operation} "${u.header}" in specs/${u.entryPath} — ${u.message}`);
|
|
448
|
+
}
|
|
449
|
+
if (result.dispositions.length > 0) {
|
|
450
|
+
console.log('Dispositions:');
|
|
451
|
+
const byKind = new Map();
|
|
452
|
+
for (const d of result.dispositions) {
|
|
453
|
+
const group = byKind.get(d.kind) ?? [];
|
|
454
|
+
group.push(d);
|
|
455
|
+
byKind.set(d.kind, group);
|
|
456
|
+
}
|
|
457
|
+
for (const group of byKind.values()) {
|
|
458
|
+
for (const d of group)
|
|
459
|
+
console.log(` ${d.kind}: ${d.task} -> ${d.target}`);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
if (result.followUps.length > 0) {
|
|
463
|
+
console.log(`follow-ups open: ${result.followUps.length}`);
|
|
464
|
+
for (const item of result.followUps) {
|
|
465
|
+
const suffix = item.from ? ` (from ${item.from})` : '';
|
|
466
|
+
console.log(` ${item.index}. ${item.text}${suffix}`);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
console.log(`Archived change to ${result.archivedTo}`);
|
|
470
|
+
}
|
|
471
|
+
return 0;
|
|
472
|
+
}
|
|
473
|
+
catch (error) {
|
|
474
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
475
|
+
if (options.json) {
|
|
476
|
+
console.log(JSON.stringify({ archived: false, status: [{ severity: 'error', code: 'archive_error', message }] }, null, 2));
|
|
477
|
+
}
|
|
478
|
+
else {
|
|
479
|
+
console.error(message);
|
|
480
|
+
}
|
|
481
|
+
return 1;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// `openspec context [--json]` — store-free minimal working set.
|
|
2
|
+
//
|
|
3
|
+
// Upstream `context` renders the multi-repo working set (root + referenced
|
|
4
|
+
// stores) from the store registry. This slice dropped the store, so the working
|
|
5
|
+
// set is always "this root alone": a `{ root: { path }, members: [] }` brief.
|
|
6
|
+
// When no openspec/ root is found, it reports the `no_openspec_root` diagnostic
|
|
7
|
+
// the /opsx propose workflow checks for (then it falls back to `schemas --json`
|
|
8
|
+
// from the cwd). The `--store` flag, if passed, is accepted and ignored.
|
|
9
|
+
import { planningHomeStub, printJson } from './shared.js';
|
|
10
|
+
export function contextHandler(projectRoot, options) {
|
|
11
|
+
if (!projectRoot) {
|
|
12
|
+
const payload = {
|
|
13
|
+
root: null,
|
|
14
|
+
members: [],
|
|
15
|
+
status: [
|
|
16
|
+
{
|
|
17
|
+
severity: 'error',
|
|
18
|
+
code: 'no_openspec_root',
|
|
19
|
+
message: 'No OpenSpec root found from the current directory.',
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
};
|
|
23
|
+
if (options.json) {
|
|
24
|
+
printJson(payload);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
console.error('No OpenSpec root found from the current directory.');
|
|
28
|
+
}
|
|
29
|
+
return 1;
|
|
30
|
+
}
|
|
31
|
+
const workingSet = {
|
|
32
|
+
root: { path: projectRoot },
|
|
33
|
+
planningHome: planningHomeStub(projectRoot),
|
|
34
|
+
members: [],
|
|
35
|
+
status: [],
|
|
36
|
+
};
|
|
37
|
+
if (options.json) {
|
|
38
|
+
printJson(workingSet);
|
|
39
|
+
return 0;
|
|
40
|
+
}
|
|
41
|
+
console.log(`Working context for ${projectRoot}`);
|
|
42
|
+
console.log();
|
|
43
|
+
console.log('OpenSpec root');
|
|
44
|
+
console.log(` ${projectRoot}`);
|
|
45
|
+
console.log();
|
|
46
|
+
console.log('No references declared; the working set is this root alone.');
|
|
47
|
+
return 0;
|
|
48
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/** One follow-up item, its 1-based position among item lines in file order. */
|
|
2
|
+
export interface FollowUpItem {
|
|
3
|
+
index: number;
|
|
4
|
+
text: string;
|
|
5
|
+
from: string | null;
|
|
6
|
+
/** Checkbox state: a checked item is settled, whether or not it names a claimant. */
|
|
7
|
+
done: boolean;
|
|
8
|
+
/** The claiming change id, null for an item checked by hand with no `-> claimed by` suffix. */
|
|
9
|
+
claimedBy: string | null;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Reads every item line of a change's follow-ups.md, ignoring any other line
|
|
13
|
+
* (heading, blank, comment). Index is the 1-based position among item lines in
|
|
14
|
+
* file order. Absent file -> empty list.
|
|
15
|
+
*/
|
|
16
|
+
export declare function readFollowUps(dir: string): FollowUpItem[];
|
|
17
|
+
export interface AddFollowUpResult {
|
|
18
|
+
change: string;
|
|
19
|
+
index: number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Appends an unclaimed item to a change's follow-ups.md, creating the file from
|
|
23
|
+
* the template when absent. Throws when the change resolves neither in flight
|
|
24
|
+
* nor archived.
|
|
25
|
+
*/
|
|
26
|
+
export declare function addFollowUp(projectRoot: string, changeId: string, text: string, from?: string): AddFollowUpResult;
|
|
27
|
+
export interface ListedFollowUp {
|
|
28
|
+
change: string;
|
|
29
|
+
index: number;
|
|
30
|
+
text: string;
|
|
31
|
+
from: string | null;
|
|
32
|
+
state: 'open';
|
|
33
|
+
}
|
|
34
|
+
/** Every open (unchecked) item of every in-flight and archived change. */
|
|
35
|
+
export declare function listFollowUps(projectRoot: string): ListedFollowUp[];
|
|
36
|
+
export interface ClaimFollowUpResult {
|
|
37
|
+
change: string;
|
|
38
|
+
index: number;
|
|
39
|
+
by: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Rewrites an item as claimed. Refuses an index without an item, an item
|
|
43
|
+
* already claimed, or a `--by` id that resolves neither in flight nor
|
|
44
|
+
* archived; every other line of the file is left byte-identical.
|
|
45
|
+
*/
|
|
46
|
+
export declare function claimFollowUp(projectRoot: string, changeId: string, index: number, byId: string): ClaimFollowUpResult;
|
|
47
|
+
export interface FollowUpCommandOptions {
|
|
48
|
+
json?: boolean;
|
|
49
|
+
from?: string;
|
|
50
|
+
by?: string;
|
|
51
|
+
}
|
|
52
|
+
/** CLI wrapper for `openspec follow-up add <change-id> "<text>" [--from <task>]`. */
|
|
53
|
+
export declare function followUpAddHandler(projectRoot: string, changeId: string | undefined, text: string | undefined, options: FollowUpCommandOptions): number;
|
|
54
|
+
/** CLI wrapper for `openspec follow-up list [--json]`. */
|
|
55
|
+
export declare function followUpListHandler(projectRoot: string, options: FollowUpCommandOptions): number;
|
|
56
|
+
/** CLI wrapper for `openspec follow-up claim <change-id> <index> --by <change-id>`. */
|
|
57
|
+
export declare function followUpClaimHandler(projectRoot: string, changeId: string | undefined, indexArg: string | undefined, options: FollowUpCommandOptions): number;
|