@xulthekl/team-flow 0.22.4
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/.agents/plugins/marketplace.json +20 -0
- package/.agents/skills +1 -0
- package/.claude/always/phase-guard.md +3 -0
- package/.claude-plugin/marketplace.json +20 -0
- package/.claude-plugin/plugin.json +31 -0
- package/.codex-plugin/plugin.json +45 -0
- package/.codexignore +6 -0
- package/.cursor-plugin/marketplace.json +17 -0
- package/.cursor-plugin/plugin.json +31 -0
- package/.github/copilot-instructions.md +15 -0
- package/.github/dependabot.yml +10 -0
- package/.github/plugin/marketplace.json +42 -0
- package/.github/workflows/ci.yml +98 -0
- package/.github/workflows/hol-plugin-scanner.yml +17 -0
- package/.opencode/INSTALL.md +61 -0
- package/.opencode/plugins/team-flow.js +70 -0
- package/AGENTS.md +280 -0
- package/CHANGELOG.md +635 -0
- package/CONTRIBUTING.md +115 -0
- package/GEMINI.md +14 -0
- package/HANDOFF.md +219 -0
- package/INSTALL.md +810 -0
- package/LICENSE +21 -0
- package/README.md +185 -0
- package/SECURITY.md +37 -0
- package/agents/bug-investigator.md +180 -0
- package/agents/change-split-auditor.md +257 -0
- package/agents/code-reviewer.md +171 -0
- package/agents/cross-change-consistency-checker.md +215 -0
- package/agents/prd-completeness-reviewer.md +197 -0
- package/agents/prototype-builder.md +230 -0
- package/agents/prototype-env-scout.md +156 -0
- package/agents/prototype-reviewer.md +226 -0
- package/assets/icon.svg +6 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +5 -0
- package/dist/parsing/change-parser.d.ts +12 -0
- package/dist/parsing/change-parser.js +54 -0
- package/dist/parsing/requirement-blocks.d.ts +31 -0
- package/dist/parsing/requirement-blocks.js +192 -0
- package/dist/schema/base.d.ts +7 -0
- package/dist/schema/base.js +1 -0
- package/dist/schema/change.d.ts +25 -0
- package/dist/schema/change.js +1 -0
- package/dist/schema/index.d.ts +3 -0
- package/dist/schema/index.js +1 -0
- package/dist/schema/spec.d.ts +11 -0
- package/dist/schema/spec.js +1 -0
- package/dist/validation/constants.d.ts +42 -0
- package/dist/validation/constants.js +42 -0
- package/dist/validation/tokenizer.d.ts +17 -0
- package/dist/validation/tokenizer.js +173 -0
- package/dist/validation/types.d.ts +40 -0
- package/dist/validation/types.js +1 -0
- package/dist/validation/validator.d.ts +21 -0
- package/dist/validation/validator.js +489 -0
- package/docs/README_en.md +358 -0
- package/docs/artifact-contract.md +96 -0
- package/docs/decision-points.md +88 -0
- package/docs/e2e-integration-design.md +207 -0
- package/docs/examples/README.md +25 -0
- package/docs/examples/add-dark-mode/README.md +13 -0
- package/docs/examples/add-dark-mode/design.md +49 -0
- package/docs/examples/add-dark-mode/execution-contract.md +56 -0
- package/docs/examples/add-dark-mode/proposal.md +46 -0
- package/docs/examples/add-dark-mode/specs/ui-theme/spec.md +35 -0
- package/docs/examples/add-dark-mode/tasks.md +26 -0
- package/docs/examples/refactor-auth-boundary/README.md +15 -0
- package/docs/examples/refactor-auth-boundary/design.md +41 -0
- package/docs/examples/refactor-auth-boundary/execution-contract.md +61 -0
- package/docs/examples/refactor-auth-boundary/proposal.md +47 -0
- package/docs/examples/refactor-auth-boundary/specs/auth-boundary/spec.md +45 -0
- package/docs/examples/refactor-auth-boundary/tasks.md +25 -0
- package/docs/plans/2026-07-07-fix-batch-design.md +68 -0
- package/docs/plans/2026-07-20-001-refactor-skill-collaboration-protocol-plan.md +128 -0
- package/docs/platform-matrix.md +45 -0
- package/docs/prototype-design-research.md +206 -0
- package/docs/release-checklist.md +96 -0
- package/docs/showcase.html +699 -0
- package/docs/skill-rename-v0.8.0.md +49 -0
- package/docs/state-machine.md +162 -0
- package/gemini-extension.json +6 -0
- package/hooks/hooks-cursor.json +8 -0
- package/hooks/hooks.json +26 -0
- package/hooks/pre-tool-use-guard +116 -0
- package/hooks/session-start +14 -0
- package/llms.txt +51 -0
- package/package.json +48 -0
- package/plugin.json +27 -0
- package/prd/v1/plan.md +443 -0
- package/prd/v1/prd.md +580 -0
- package/scripts/check-update.mjs +91 -0
- package/scripts/check-version-consistency.mjs +345 -0
- package/scripts/ensure-branch.mjs +71 -0
- package/scripts/get-config +31 -0
- package/scripts/guard/checks/artifacts-exist.mjs +36 -0
- package/scripts/guard/checks/contract-current.mjs +33 -0
- package/scripts/guard/checks/contract-fresh.mjs +17 -0
- package/scripts/guard/checks/dp-gate-passed.mjs +35 -0
- package/scripts/guard/checks/dp3-approved.mjs +22 -0
- package/scripts/guard/checks/execution-plan-ready.mjs +40 -0
- package/scripts/guard/checks/execution-reviews-passed.mjs +36 -0
- package/scripts/guard/checks/schema-valid.mjs +63 -0
- package/scripts/guard/checks/specs-merged.mjs +51 -0
- package/scripts/guard/checks/tasks-complete.mjs +31 -0
- package/scripts/guard/checks/tests-passing.mjs +31 -0
- package/scripts/guard/design-token-guard.mjs +237 -0
- package/scripts/guard/guard.mjs +205 -0
- package/scripts/infer-workflow.mjs +136 -0
- package/scripts/install-amazon-q.mjs +9 -0
- package/scripts/install-cline.mjs +9 -0
- package/scripts/install-continue.mjs +9 -0
- package/scripts/install-cursor.mjs +265 -0
- package/scripts/install-git-hooks.mjs +82 -0
- package/scripts/install-kiro.mjs +9 -0
- package/scripts/install-pi.mjs +9 -0
- package/scripts/install-qoder.mjs +9 -0
- package/scripts/install-qwen.mjs +9 -0
- package/scripts/install-roocode.mjs +9 -0
- package/scripts/install-windsurf.mjs +9 -0
- package/scripts/install-zcode.mjs +265 -0
- package/scripts/lib/cmd-audit.mjs +127 -0
- package/scripts/lib/cmd-checkpoint.mjs +118 -0
- package/scripts/lib/cmd-config.mjs +88 -0
- package/scripts/lib/cmd-doctor.mjs +267 -0
- package/scripts/lib/cmd-execution.mjs +206 -0
- package/scripts/lib/cmd-handoff.mjs +94 -0
- package/scripts/lib/cmd-inject.mjs +310 -0
- package/scripts/lib/cmd-install-amazon-q.mjs +11 -0
- package/scripts/lib/cmd-install-cline.mjs +11 -0
- package/scripts/lib/cmd-install-continue.mjs +11 -0
- package/scripts/lib/cmd-install-cursor.mjs +14 -0
- package/scripts/lib/cmd-install-kiro.mjs +11 -0
- package/scripts/lib/cmd-install-pi.mjs +11 -0
- package/scripts/lib/cmd-install-qoder.mjs +11 -0
- package/scripts/lib/cmd-install-qwen.mjs +11 -0
- package/scripts/lib/cmd-install-roocode.mjs +11 -0
- package/scripts/lib/cmd-install-windsurf.mjs +11 -0
- package/scripts/lib/cmd-install-workbuddy.mjs +359 -0
- package/scripts/lib/cmd-install-zcode.mjs +14 -0
- package/scripts/lib/cmd-isolate.mjs +37 -0
- package/scripts/lib/cmd-list.mjs +71 -0
- package/scripts/lib/cmd-runtime.mjs +85 -0
- package/scripts/lib/cmd-solutions.mjs +71 -0
- package/scripts/lib/cmd-state.mjs +231 -0
- package/scripts/lib/cmd-sync.mjs +102 -0
- package/scripts/lib/cmd-validate.mjs +105 -0
- package/scripts/lib/cmd-version.mjs +148 -0
- package/scripts/lib/config-loader.mjs +107 -0
- package/scripts/lib/execution-plan.mjs +492 -0
- package/scripts/lib/execution-recommendation.mjs +221 -0
- package/scripts/lib/hash.mjs +74 -0
- package/scripts/lib/install.mjs +267 -0
- package/scripts/lib/platform-runtime-inventory.mjs +27 -0
- package/scripts/lib/platforms.mjs +165 -0
- package/scripts/lib/prototype-sync.mjs +192 -0
- package/scripts/lib/sdd-overlay.mjs +262 -0
- package/scripts/lib/shell-quote.mjs +4 -0
- package/scripts/lib/solutions-capture.mjs +102 -0
- package/scripts/lib/solutions-index-gen.mjs +112 -0
- package/scripts/lib/solutions-inject.mjs +77 -0
- package/scripts/lib/solutions-promote.mjs +145 -0
- package/scripts/lib/spec-paths.mjs +85 -0
- package/scripts/lib/state-loader.mjs +154 -0
- package/scripts/lint/lint-skills.mjs +149 -0
- package/scripts/lint/rules/behavior-consistency.mjs +56 -0
- package/scripts/lint/rules/dp-trigger-points.mjs +60 -0
- package/scripts/lint/rules/exception-handling.mjs +59 -0
- package/scripts/lint/rules/no-contradictory-instructions.mjs +67 -0
- package/scripts/lint/rules/no-redundant-checks.mjs +50 -0
- package/scripts/lint/rules/token-rules.mjs +122 -0
- package/scripts/review-package +44 -0
- package/scripts/task-brief +40 -0
- package/scripts/team-flow.mjs +164 -0
- package/scripts/token-baseline.mjs +158 -0
- package/scripts/validate-artifacts +96 -0
- package/scripts/verify-marketplace-release.mjs +59 -0
- package/skills/architecture-design/SKILL.md +140 -0
- package/skills/architecture-design/chapters/ch01-4a-domains.md +27 -0
- package/skills/architecture-design/chapters/ch02-change-cascade.md +27 -0
- package/skills/architecture-design/chapters/ch03-architecture-outputs.md +25 -0
- package/skills/architecture-design/chapters/ch04-entity-to-aggregate.md +27 -0
- package/skills/architecture-design/chapters/ch05-cqrs.md +32 -0
- package/skills/architecture-design/chapters/ch06-integration.md +54 -0
- package/skills/architecture-design/cheatsheet.md +51 -0
- package/skills/architecture-design/glossary.md +23 -0
- package/skills/architecture-design/patterns.md +32 -0
- package/skills/bug-investigator/SKILL.md +77 -0
- package/skills/build-executor/SKILL.md +163 -0
- package/skills/build-executor/implementer-prompt.md +162 -0
- package/skills/build-executor/references/execution-modes.md +86 -0
- package/skills/build-executor/task-reviewer-prompt.md +195 -0
- package/skills/ce-brainstorm/SKILL.md +415 -0
- package/skills/ce-brainstorm/references/agents/slack-researcher.md +127 -0
- package/skills/ce-brainstorm/references/blindspot-pass.md +70 -0
- package/skills/ce-brainstorm/references/brainstorm-sections.md +285 -0
- package/skills/ce-brainstorm/references/handoff.md +168 -0
- package/skills/ce-brainstorm/references/html-rendering.md +634 -0
- package/skills/ce-brainstorm/references/markdown-rendering.md +241 -0
- package/skills/ce-brainstorm/references/model-tiers.md +9 -0
- package/skills/ce-brainstorm/references/prd-mapping.md +71 -0
- package/skills/ce-brainstorm/references/product-pressure-test.md +42 -0
- package/skills/ce-brainstorm/references/reasoning-elevation.md +63 -0
- package/skills/ce-brainstorm/references/settled-decisions.md +45 -0
- package/skills/ce-brainstorm/references/synthesis-summary.md +282 -0
- package/skills/ce-brainstorm/references/universal-brainstorming.md +73 -0
- package/skills/ce-brainstorm/references/verdict-routing.md +26 -0
- package/skills/ce-brainstorm/references/visual-probes.md +158 -0
- package/skills/ce-brainstorm/scripts/visual-probe-server.js +418 -0
- package/skills/ce-compound/SKILL.md +114 -0
- package/skills/ce-compound/assets/resolution-template.md +94 -0
- package/skills/ce-compound/references/agents/best-practices-researcher.md +115 -0
- package/skills/ce-compound/references/agents/data-integrity-guardian.md +68 -0
- package/skills/ce-compound/references/agents/framework-docs-researcher.md +93 -0
- package/skills/ce-compound/references/agents/pattern-recognition-specialist.md +55 -0
- package/skills/ce-compound/references/agents/performance-oracle.md +108 -0
- package/skills/ce-compound/references/agents/security-sentinel.md +91 -0
- package/skills/ce-compound/references/agents/session-historian.md +83 -0
- package/skills/ce-compound/references/concepts-vocabulary.md +78 -0
- package/skills/ce-compound/references/full-mode-workflow.md +413 -0
- package/skills/ce-compound/references/grounding-validation.md +84 -0
- package/skills/ce-compound/references/lightweight-mode.md +53 -0
- package/skills/ce-compound/references/output-standards.md +182 -0
- package/skills/ce-compound/references/promotion-rules.md +38 -0
- package/skills/ce-compound/references/schema.yaml +231 -0
- package/skills/ce-compound/references/three-tier-index.md +55 -0
- package/skills/ce-compound/references/v0.5-upgrade.md +32 -0
- package/skills/ce-compound/references/write-flow.md +36 -0
- package/skills/ce-compound/references/yaml-schema.md +118 -0
- package/skills/ce-compound/scripts/session-history/discover-sessions.sh +130 -0
- package/skills/ce-compound/scripts/session-history/extract-errors.py +254 -0
- package/skills/ce-compound/scripts/session-history/extract-metadata.py +456 -0
- package/skills/ce-compound/scripts/session-history/extract-skeleton.py +575 -0
- package/skills/ce-compound/scripts/validate-doc-claims.py +346 -0
- package/skills/ce-compound/scripts/validate-frontmatter.py +137 -0
- package/skills/ce-ideate/SKILL.md +402 -0
- package/skills/ce-ideate/references/agents/issue-intelligence-analyst.md +200 -0
- package/skills/ce-ideate/references/agents/learnings-researcher.md +247 -0
- package/skills/ce-ideate/references/agents/slack-researcher.md +127 -0
- package/skills/ce-ideate/references/agents/web-researcher.md +121 -0
- package/skills/ce-ideate/references/divergent-ideation.md +89 -0
- package/skills/ce-ideate/references/html-rendering.md +634 -0
- package/skills/ce-ideate/references/ideation-sections.md +191 -0
- package/skills/ce-ideate/references/markdown-rendering.md +238 -0
- package/skills/ce-ideate/references/post-ideation-workflow.md +167 -0
- package/skills/ce-ideate/references/universal-ideation.md +107 -0
- package/skills/ce-ideate/references/web-research-cache.md +56 -0
- package/skills/ce-plan/SKILL.md +137 -0
- package/skills/ce-plan/references/agents/agent-native-planning-strategist.md +62 -0
- package/skills/ce-plan/references/agents/architecture-strategist.md +46 -0
- package/skills/ce-plan/references/agents/best-practices-researcher.md +114 -0
- package/skills/ce-plan/references/agents/data-integrity-guardian.md +68 -0
- package/skills/ce-plan/references/agents/data-migration-reviewer.md +103 -0
- package/skills/ce-plan/references/agents/deployment-verification-agent.md +157 -0
- package/skills/ce-plan/references/agents/framework-docs-researcher.md +93 -0
- package/skills/ce-plan/references/agents/git-history-analyzer.md +40 -0
- package/skills/ce-plan/references/agents/learnings-researcher.md +247 -0
- package/skills/ce-plan/references/agents/pattern-recognition-specialist.md +55 -0
- package/skills/ce-plan/references/agents/performance-oracle.md +108 -0
- package/skills/ce-plan/references/agents/repo-research-analyst.md +258 -0
- package/skills/ce-plan/references/agents/security-sentinel.md +91 -0
- package/skills/ce-plan/references/agents/slack-researcher.md +127 -0
- package/skills/ce-plan/references/agents/spec-flow-analyzer.md +80 -0
- package/skills/ce-plan/references/agents/web-researcher.md +121 -0
- package/skills/ce-plan/references/approach-altitude.md +55 -0
- package/skills/ce-plan/references/change-splitting.md +65 -0
- package/skills/ce-plan/references/completion-contract.md +33 -0
- package/skills/ce-plan/references/core-principles.md +40 -0
- package/skills/ce-plan/references/deepening-workflow.md +263 -0
- package/skills/ce-plan/references/dependency-graph.md +36 -0
- package/skills/ce-plan/references/html-rendering.md +634 -0
- package/skills/ce-plan/references/intake-routing.md +189 -0
- package/skills/ce-plan/references/markdown-rendering.md +238 -0
- package/skills/ce-plan/references/plan-handoff.md +150 -0
- package/skills/ce-plan/references/plan-sections.md +427 -0
- package/skills/ce-plan/references/plan-structure.md +120 -0
- package/skills/ce-plan/references/planning-modes.md +69 -0
- package/skills/ce-plan/references/reasoning-elevation.md +63 -0
- package/skills/ce-plan/references/research-workflow.md +165 -0
- package/skills/ce-plan/references/review-and-write.md +127 -0
- package/skills/ce-plan/references/settled-decisions.md +45 -0
- package/skills/ce-plan/references/synthesis-summary.md +418 -0
- package/skills/ce-plan/references/universal-planning.md +168 -0
- package/skills/ce-proof/SKILL.md +346 -0
- package/skills/ce-strategy/SKILL.md +97 -0
- package/skills/ce-strategy/references/interview.md +143 -0
- package/skills/ce-strategy/references/strategy-template.md +89 -0
- package/skills/code-reviewer/SKILL.md +84 -0
- package/skills/code-reviewer/code-reviewer-prompt.md +195 -0
- package/skills/contract-builder/SKILL.md +72 -0
- package/skills/design-system/SKILL.md +75 -0
- package/skills/design-system/references/agents/design-system-architect.md +138 -0
- package/skills/design-system/references/creation-flow.md +170 -0
- package/skills/design-system/references/preview-template.html +319 -0
- package/skills/design-system/references/token-derivation.md +115 -0
- package/skills/design-system/references/variant-schema.md +60 -0
- package/skills/e2e/SKILL.md +56 -0
- package/skills/e2e/references/test-verifier.md +40 -0
- package/skills/need-explorer/SKILL.md +79 -0
- package/skills/prototype/SKILL.md +98 -0
- package/skills/prototype/references/agents/design-system-architect.md +122 -0
- package/skills/prototype/references/checklist.md +82 -0
- package/skills/prototype/references/craft/accessibility-baseline.md +71 -0
- package/skills/prototype/references/craft/anti-ai-slop.md +87 -0
- package/skills/prototype/references/craft/laws-of-ux.md +60 -0
- package/skills/prototype/references/craft/state-coverage.md +81 -0
- package/skills/prototype/references/craft/typography-hierarchy.md +50 -0
- package/skills/prototype/references/layouts.md +223 -0
- package/skills/prototype/references/orchestration-flow.md +101 -0
- package/skills/prototype/references/prototype-scaffold/assets/design-tokens.css +19 -0
- package/skills/prototype/references/prototype-scaffold/components/button.html +3 -0
- package/skills/prototype/references/prototype-scaffold/design-system.md +7 -0
- package/skills/prototype/references/prototype-scaffold/flow.md +11 -0
- package/skills/prototype/references/prototype-scaffold/index.html +20 -0
- package/skills/prototype/references/prototype-scaffold/pages/index.html +11 -0
- package/skills/prototype/references/template.html +396 -0
- package/skills/release-archivist/SKILL.md +166 -0
- package/skills/release-archivist/references/closing-procedures.md +81 -0
- package/skills/session-handoff/SKILL.md +110 -0
- package/skills/session-handoff/references/context-extraction.md +70 -0
- package/skills/session-handoff/references/handoff-template.md +90 -0
- package/skills/session-handoff/references/skill-recommendation.md +43 -0
- package/skills/spec-merger/SKILL.md +66 -0
- package/skills/spec-writer/SKILL.md +149 -0
- package/skills/workflow-bootstrap/SKILL.md +148 -0
- package/skills/workflow-bootstrap/references/agents/codebase-recon-analyst.md +121 -0
- package/skills/workflow-bootstrap/references/b1-reconnaissance.md +80 -0
- package/skills/workflow-bootstrap/scripts/recon-probe.sh +328 -0
- package/skills/workflow-feedback/SKILL.md +120 -0
- package/skills/workflow-feedback/references/category-guide.md +121 -0
- package/skills/workflow-feedback/references/feedback-template.md +73 -0
- package/skills/workflow-feedback/references/review-mode.md +92 -0
- package/skills/workflow-orchestrator/SKILL.md +94 -0
- package/skills/workflow-orchestrator/references/feedback-loops.md +56 -0
- package/skills/workflow-orchestrator/references/s1-path-router.md +81 -0
- package/skills/workflow-orchestrator/references/s2-prd-prototype-loop.md +96 -0
- package/skills/workflow-orchestrator/references/s3-plan-pipeline.md +50 -0
- package/skills/workflow-orchestrator/references/s4-split-validate.md +108 -0
- package/skills/workflow-orchestrator/references/s5-monitoring.md +61 -0
- package/skills/workflow-orchestrator/references/state-model.md +185 -0
- package/skills/workflow-start/SKILL.md +147 -0
- package/skills/workflow-start/references/routing-rules.md +155 -0
- package/src/index.ts +16 -0
- package/src/parsing/change-parser.ts +79 -0
- package/src/parsing/requirement-blocks.ts +244 -0
- package/src/schema/base.ts +8 -0
- package/src/schema/change.ts +29 -0
- package/src/schema/index.ts +3 -0
- package/src/schema/spec.ts +12 -0
- package/src/validation/constants.ts +53 -0
- package/src/validation/tokenizer.ts +180 -0
- package/src/validation/types.ts +47 -0
- package/src/validation/validator.ts +552 -0
- package/templates/abandonment-summary.md +29 -0
- package/templates/design.md +37 -0
- package/templates/execution-contract.md +99 -0
- package/templates/plan.md +86 -0
- package/templates/prd.md +468 -0
- package/templates/proposal.md +37 -0
- package/templates/spec.md +31 -0
- package/templates/tasks.md +47 -0
- package/tests/e2e.test.mjs +266 -0
- package/tests/lib/cmd-audit.test.mjs +202 -0
- package/tests/lib/cmd-checkpoint.test.mjs +98 -0
- package/tests/lib/cmd-config.test.mjs +101 -0
- package/tests/lib/cmd-doctor.test.mjs +450 -0
- package/tests/lib/cmd-execution.test.mjs +647 -0
- package/tests/lib/cmd-handoff.test.mjs +137 -0
- package/tests/lib/cmd-inject.test.mjs +218 -0
- package/tests/lib/cmd-install-workbuddy.test.mjs +113 -0
- package/tests/lib/cmd-install-zcode.test.mjs +42 -0
- package/tests/lib/cmd-isolate.test.mjs +68 -0
- package/tests/lib/cmd-list.test.mjs +127 -0
- package/tests/lib/cmd-runtime.test.mjs +83 -0
- package/tests/lib/cmd-state-missing.test.mjs +65 -0
- package/tests/lib/cmd-state.test.mjs +365 -0
- package/tests/lib/cmd-sync-paths.test.mjs +84 -0
- package/tests/lib/cmd-validate-paths.test.mjs +90 -0
- package/tests/lib/config-loader.test.mjs +175 -0
- package/tests/lib/ensure-branch.test.mjs +59 -0
- package/tests/lib/execution-control-plane.test.mjs +168 -0
- package/tests/lib/execution-plan.test.mjs +474 -0
- package/tests/lib/guard-specs-merged.test.mjs +108 -0
- package/tests/lib/guard-tests-passing.test.mjs +112 -0
- package/tests/lib/guard-transitions.test.mjs +262 -0
- package/tests/lib/guard.test.mjs +611 -0
- package/tests/lib/hash.test.mjs +138 -0
- package/tests/lib/infer-workflow.test.mjs +161 -0
- package/tests/lib/install-git-hooks.test.mjs +20 -0
- package/tests/lib/marketplace-release-docs.test.mjs +31 -0
- package/tests/lib/minimality-discipline.test.mjs +37 -0
- package/tests/lib/model-profiles-docs.test.mjs +33 -0
- package/tests/lib/node20-compatibility.test.mjs +73 -0
- package/tests/lib/node20-test-entry.test.mjs +17 -0
- package/tests/lib/platform-runtime-distribution.test.mjs +150 -0
- package/tests/lib/raw-mode-smoke.test.mjs +42 -0
- package/tests/lib/sdd-overlay.test.mjs +118 -0
- package/tests/lib/spec-paths.test.mjs +80 -0
- package/tests/lib/state-loader.test.mjs +251 -0
- package/tests/lib/token-baseline.test.mjs +72 -0
- package/tests/lib/token-rules.test.mjs +94 -0
- package/tests/lib/verify-marketplace-release.test.mjs +128 -0
- package/tests/tsconfig.json +12 -0
- package/token-baseline.json +95 -0
- package/tsconfig.json +14 -0
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
// tf doctor — health check for team-flow installation and project
|
|
2
|
+
import { readFileSync, readdirSync, existsSync, statSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { loadConfig } from './config-loader.mjs';
|
|
5
|
+
import { PLATFORM_RUNTIME_INVENTORY } from './platform-runtime-inventory.mjs';
|
|
6
|
+
|
|
7
|
+
const RUNTIME_SKILLS = new Set([
|
|
8
|
+
'workflow-start', 'need-explorer', 'spec-writer', 'contract-builder',
|
|
9
|
+
'build-executor', 'code-reviewer', 'bug-investigator', 'release-archivist', 'spec-merger',
|
|
10
|
+
]);
|
|
11
|
+
|
|
12
|
+
function readJsonIfExists(filePath) {
|
|
13
|
+
if (!existsSync(filePath)) return null;
|
|
14
|
+
try { return JSON.parse(readFileSync(filePath, 'utf-8')); } catch { return null; }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function checkVersionConsistency(root) {
|
|
18
|
+
const files = [
|
|
19
|
+
{ name: 'package.json', path: ['version'] },
|
|
20
|
+
{ name: 'plugin.json', path: ['version'] },
|
|
21
|
+
{ name: '.claude-plugin/plugin.json', path: ['version'] },
|
|
22
|
+
{ name: '.claude-plugin/marketplace.json', path: ['plugins', '0', 'version'] },
|
|
23
|
+
{ name: '.cursor-plugin/plugin.json', path: ['version'] },
|
|
24
|
+
{ name: '.cursor-plugin/marketplace.json', path: ['metadata', 'version'] },
|
|
25
|
+
{ name: '.codex-plugin/plugin.json', path: ['version'] },
|
|
26
|
+
{ name: 'gemini-extension.json', path: ['version'] },
|
|
27
|
+
{ name: '.github/plugin/marketplace.json', path: ['metadata', 'version'] },
|
|
28
|
+
{ name: '.github/plugin/marketplace.json', path: ['plugins', '0', 'version'] },
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
const versions = {};
|
|
32
|
+
for (const f of files) {
|
|
33
|
+
const key = `${f.name}:${f.path.join('.')}`;
|
|
34
|
+
const data = readJsonIfExists(join(root, f.name));
|
|
35
|
+
if (!data) { versions[key] = null; continue; }
|
|
36
|
+
let val = data;
|
|
37
|
+
for (const p of f.path) val = val?.[p];
|
|
38
|
+
versions[key] = val || null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const uniqueVersions = [...new Set(Object.values(versions).filter(Boolean))];
|
|
42
|
+
const pkgVersion = versions['package.json:version'];
|
|
43
|
+
|
|
44
|
+
if (uniqueVersions.length <= 1) {
|
|
45
|
+
return { pass: true, message: `Version: ${pkgVersion} (consistent across ${Object.keys(versions).filter(k => versions[k]).length} manifest fields)` };
|
|
46
|
+
}
|
|
47
|
+
const mismatches = Object.entries(versions)
|
|
48
|
+
.filter(([, v]) => v !== pkgVersion)
|
|
49
|
+
.map(([name, v]) => `${name}=${v}`)
|
|
50
|
+
.join(', ');
|
|
51
|
+
return { pass: false, message: `Version mismatch: ${pkgVersion} (package.json) vs ${mismatches}` };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function checkHooks(root) {
|
|
55
|
+
const hooksPath = join(root, 'hooks', 'hooks.json');
|
|
56
|
+
if (!existsSync(hooksPath)) {
|
|
57
|
+
return { pass: false, message: 'hooks/hooks.json not found' };
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
const hooks = JSON.parse(readFileSync(hooksPath, 'utf-8'));
|
|
61
|
+
if (hooks.hooks && typeof hooks.hooks === 'object' && !Array.isArray(hooks.hooks)) {
|
|
62
|
+
return { pass: true, message: 'valid format' };
|
|
63
|
+
}
|
|
64
|
+
return { pass: false, message: 'invalid format (expected record, got ' + typeof hooks.hooks + ')' };
|
|
65
|
+
} catch (e) {
|
|
66
|
+
return { pass: false, message: `parse error: ${e.message}` };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function checkCodexManifest(root) {
|
|
71
|
+
const manifestPath = join(root, '.codex-plugin', 'plugin.json');
|
|
72
|
+
const manifest = readJsonIfExists(manifestPath);
|
|
73
|
+
if (!manifest) {
|
|
74
|
+
return { pass: false, message: '.codex-plugin/plugin.json not found' };
|
|
75
|
+
}
|
|
76
|
+
if (JSON.stringify(manifest.hooks) !== '{}') {
|
|
77
|
+
return {
|
|
78
|
+
pass: false,
|
|
79
|
+
message: 'Codex manifest must set hooks to {} to suppress hooks/hooks.json auto-discovery',
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
if (manifest.interface?.category !== 'Developer Tools') {
|
|
83
|
+
return { pass: false, message: 'Codex category should be Developer Tools' };
|
|
84
|
+
}
|
|
85
|
+
return { pass: true, message: 'hooks suppressed, category Developer Tools' };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function checkSkills(root) {
|
|
89
|
+
const skillsDir = join(root, 'skills');
|
|
90
|
+
if (!existsSync(skillsDir)) {
|
|
91
|
+
return { pass: false, message: 'skills/ directory not found' };
|
|
92
|
+
}
|
|
93
|
+
const dirs = readdirSync(skillsDir).filter(f => {
|
|
94
|
+
try { return statSync(join(skillsDir, f)).isDirectory(); } catch { return false; }
|
|
95
|
+
});
|
|
96
|
+
const withSkillMd = dirs.filter(d => existsSync(join(skillsDir, d, 'SKILL.md')));
|
|
97
|
+
if (withSkillMd.length === dirs.length) {
|
|
98
|
+
return { pass: true, message: `${dirs.length}/${dirs.length} present` };
|
|
99
|
+
}
|
|
100
|
+
const missing = dirs.filter(d => !withSkillMd.includes(d));
|
|
101
|
+
return { pass: false, message: `${withSkillMd.length}/${dirs.length} present, missing SKILL.md: ${missing.join(', ')}` };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function localRuntimePaths(content) {
|
|
105
|
+
const doubleQuoted = [...content.matchAll(/node\s+"([^"]+\/scripts\/team-flow\.mjs)"/g)]
|
|
106
|
+
.map(match => match[1]);
|
|
107
|
+
const singleQuoted = [...content.matchAll(/node\s+'(.+\/scripts\/team-flow\.mjs)'/g)]
|
|
108
|
+
.map(match => match[1].split("'\\''").join("'"));
|
|
109
|
+
return [...doubleQuoted, ...singleQuoted];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function checkRuntimeDistribution(root) {
|
|
113
|
+
const pkg = readJsonIfExists(join(root, 'package.json'));
|
|
114
|
+
const skillsDir = join(root, 'skills');
|
|
115
|
+
if (!pkg?.version || !existsSync(skillsDir)) {
|
|
116
|
+
return { pass: false, message: 'runtime distribution cannot be checked: package.json or skills/ is missing; reinstall team-flow' };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const canonicalPrefix = `npx --yes --package @xulthekl/team-flow@${pkg.version} tf`;
|
|
120
|
+
const issues = [];
|
|
121
|
+
for (const name of readdirSync(skillsDir)) {
|
|
122
|
+
if (!RUNTIME_SKILLS.has(name)) continue;
|
|
123
|
+
const skillPath = join(skillsDir, name, 'SKILL.md');
|
|
124
|
+
if (!existsSync(skillPath)) continue;
|
|
125
|
+
const content = readFileSync(skillPath, 'utf8');
|
|
126
|
+
if (/\$\{CLAUDE_PLUGIN_ROOT\}|\$\{PLUGIN_ROOT\}/.test(content)) {
|
|
127
|
+
issues.push(`${name}: plugin-root placeholder remains`);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (content.includes(canonicalPrefix)) continue;
|
|
131
|
+
const localPaths = localRuntimePaths(content);
|
|
132
|
+
if (localPaths.length > 0) {
|
|
133
|
+
if (localPaths.every(existsSync)) continue;
|
|
134
|
+
issues.push(`${name}: local runtime tree is missing`);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
issues.push(`${name}: runtime prefix or local runtime tree is missing`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (issues.length > 0) {
|
|
141
|
+
return { pass: false, message: `${issues.join('; ')}; reinstall or upgrade team-flow instead of editing skill files manually` };
|
|
142
|
+
}
|
|
143
|
+
return { pass: true, message: `${PLATFORM_RUNTIME_INVENTORY.length} platforms inventoried; canonical skills and local runtime are resolvable` };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function checkDist(root) {
|
|
147
|
+
const distDir = join(root, 'dist');
|
|
148
|
+
if (!existsSync(distDir)) {
|
|
149
|
+
return { pass: false, message: 'dist/ not found (run npm run build)' };
|
|
150
|
+
}
|
|
151
|
+
const indexJs = join(distDir, 'index.js');
|
|
152
|
+
if (!existsSync(indexJs)) {
|
|
153
|
+
return { pass: false, message: 'dist/index.js not found' };
|
|
154
|
+
}
|
|
155
|
+
return { pass: true, message: 'compiled' };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function checkRootPluginAuthor(root) {
|
|
159
|
+
const pluginPath = join(root, 'plugin.json');
|
|
160
|
+
const plugin = readJsonIfExists(pluginPath);
|
|
161
|
+
if (!plugin) {
|
|
162
|
+
return { pass: false, message: 'plugin.json not found' };
|
|
163
|
+
}
|
|
164
|
+
if (!plugin.author) {
|
|
165
|
+
return { pass: false, message: 'missing author field' };
|
|
166
|
+
}
|
|
167
|
+
if (typeof plugin.author === 'string') {
|
|
168
|
+
return { pass: false, message: 'author must be an object (got string)' };
|
|
169
|
+
}
|
|
170
|
+
if (typeof plugin.author === 'object' && !Array.isArray(plugin.author) && plugin.author.name) {
|
|
171
|
+
return { pass: true, message: `author.name = ${plugin.author.name}` };
|
|
172
|
+
}
|
|
173
|
+
return { pass: false, message: 'author object must contain a name property' };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function checkNodeVersion(version = process.version) {
|
|
177
|
+
const major = parseInt(version.slice(1).split('.')[0], 10);
|
|
178
|
+
if (major >= 20) {
|
|
179
|
+
return { pass: true, message: version };
|
|
180
|
+
}
|
|
181
|
+
return { pass: false, message: `${version} (requires >= 20)` };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function checkDocs(root) {
|
|
185
|
+
const warnings = [];
|
|
186
|
+
const pkg = readJsonIfExists(join(root, 'package.json'));
|
|
187
|
+
if (!pkg) return { pass: true, message: 'skipped (no package.json)' };
|
|
188
|
+
|
|
189
|
+
const pkgVersion = pkg.version;
|
|
190
|
+
|
|
191
|
+
// Check CHANGELOG has current version
|
|
192
|
+
const changelogPath = join(root, 'CHANGELOG.md');
|
|
193
|
+
if (existsSync(changelogPath)) {
|
|
194
|
+
const changelog = readFileSync(changelogPath, 'utf-8');
|
|
195
|
+
if (!changelog.includes(`## [${pkgVersion}]`)) {
|
|
196
|
+
warnings.push(`CHANGELOG.md missing entry for v${pkgVersion}`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Check skills count in README
|
|
201
|
+
const readmePath = join(root, 'README.md');
|
|
202
|
+
if (existsSync(readmePath)) {
|
|
203
|
+
const skillsDir = join(root, 'skills');
|
|
204
|
+
if (existsSync(skillsDir)) {
|
|
205
|
+
const actualSkills = readdirSync(skillsDir).filter(f => {
|
|
206
|
+
try { return statSync(join(skillsDir, f)).isDirectory(); } catch { return false; }
|
|
207
|
+
}).length;
|
|
208
|
+
const readme = readFileSync(readmePath, 'utf-8');
|
|
209
|
+
// Count skill table rows (pattern: | N | `skill-name` | where N is a row number)
|
|
210
|
+
const skillRefs = readme.match(/\|\s*\d+\s*\|\s*`[a-z-]+`/g) || [];
|
|
211
|
+
if (skillRefs.length > 0 && skillRefs.length !== actualSkills) {
|
|
212
|
+
warnings.push(`README lists ${skillRefs.length} skills, but skills/ has ${actualSkills}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (warnings.length === 0) {
|
|
218
|
+
return { pass: true, message: 'consistent' };
|
|
219
|
+
}
|
|
220
|
+
return { pass: false, message: warnings.join('; ') };
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export async function run(args) {
|
|
224
|
+
const root = process.cwd();
|
|
225
|
+
const config = loadConfig(root);
|
|
226
|
+
|
|
227
|
+
console.log('team-flow doctor:\n');
|
|
228
|
+
|
|
229
|
+
const checks = [
|
|
230
|
+
['Version', checkVersionConsistency(root)],
|
|
231
|
+
['Root plugin author', checkRootPluginAuthor(root)],
|
|
232
|
+
['Hooks', checkHooks(root)],
|
|
233
|
+
['Codex manifest', checkCodexManifest(root)],
|
|
234
|
+
['Skills', checkSkills(root)],
|
|
235
|
+
['Runtime distribution', checkRuntimeDistribution(root)],
|
|
236
|
+
['dist/', checkDist(root)],
|
|
237
|
+
['Node.js', checkNodeVersion()],
|
|
238
|
+
['Docs', checkDocs(root)],
|
|
239
|
+
];
|
|
240
|
+
|
|
241
|
+
// Config check
|
|
242
|
+
const configPath = join(root, 'team-flow.config.json');
|
|
243
|
+
if (existsSync(configPath)) {
|
|
244
|
+
try {
|
|
245
|
+
JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
246
|
+
checks.push(['Config', { pass: true, message: 'valid JSON' }]);
|
|
247
|
+
} catch (e) {
|
|
248
|
+
checks.push(['Config', { pass: false, message: `invalid JSON: ${e.message}` }]);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
let hasFailure = false;
|
|
253
|
+
for (const [name, result] of checks) {
|
|
254
|
+
const icon = result.pass ? '✅' : '⚠️ ';
|
|
255
|
+
console.log(` ${icon} ${name}: ${result.message}`);
|
|
256
|
+
if (!result.pass) hasFailure = true;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
console.log('');
|
|
260
|
+
if (hasFailure) {
|
|
261
|
+
console.log('⚠️ Some checks need attention.');
|
|
262
|
+
} else {
|
|
263
|
+
console.log('✅ All checks passed.');
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export { checkVersionConsistency, checkHooks, checkCodexManifest, checkSkills, checkRuntimeDistribution, checkDist, checkRootPluginAuthor, checkNodeVersion, checkDocs };
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { parseArgs } from 'node:util';
|
|
2
|
+
import { createPlan, describeWaves, EXECUTION_MODES, readPlan, recordReview, validatePlan, writePlan } from './execution-plan.mjs';
|
|
3
|
+
import {
|
|
4
|
+
createRecommendationReceipt,
|
|
5
|
+
readCurrentRecommendationReceipt,
|
|
6
|
+
writeRecommendationReceipt,
|
|
7
|
+
} from './execution-recommendation.mjs';
|
|
8
|
+
import { readState, writeState } from './state-loader.mjs';
|
|
9
|
+
|
|
10
|
+
const SUBCOMMANDS = ['recommend', 'plan', 'show', 'revise', 'review'];
|
|
11
|
+
|
|
12
|
+
export async function run(args) {
|
|
13
|
+
const { positionals, values } = parseArgs({
|
|
14
|
+
args,
|
|
15
|
+
options: {
|
|
16
|
+
mode: { type: 'string' },
|
|
17
|
+
reason: { type: 'string' },
|
|
18
|
+
wave: { type: 'string', multiple: true },
|
|
19
|
+
confirm: { type: 'boolean', default: false },
|
|
20
|
+
'acknowledge-recommendation': { type: 'boolean', default: false },
|
|
21
|
+
base: { type: 'string' },
|
|
22
|
+
head: { type: 'string' },
|
|
23
|
+
report: { type: 'string' },
|
|
24
|
+
verdict: { type: 'string' },
|
|
25
|
+
json: { type: 'boolean', default: false },
|
|
26
|
+
help: { type: 'boolean', default: false },
|
|
27
|
+
},
|
|
28
|
+
allowPositionals: true,
|
|
29
|
+
});
|
|
30
|
+
const subcommand = positionals[0];
|
|
31
|
+
const changeDir = positionals[1];
|
|
32
|
+
|
|
33
|
+
if (values.help || subcommand === undefined) {
|
|
34
|
+
printHelp();
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (!SUBCOMMANDS.includes(subcommand)) usage(`Unknown execution subcommand: ${subcommand}`);
|
|
38
|
+
if (!changeDir) usage('Usage: tf execution <subcommand> <change-dir> [options]');
|
|
39
|
+
|
|
40
|
+
switch (subcommand) {
|
|
41
|
+
case 'recommend':
|
|
42
|
+
return recommendAndPrint(changeDir, values);
|
|
43
|
+
case 'plan':
|
|
44
|
+
return createAndPrintPlan(changeDir, values, false);
|
|
45
|
+
case 'show':
|
|
46
|
+
return showPlan(changeDir, values.json);
|
|
47
|
+
case 'revise':
|
|
48
|
+
return createAndPrintPlan(changeDir, values, true);
|
|
49
|
+
case 'review':
|
|
50
|
+
return recordAndPrintReview(changeDir, values);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function createAndPrintPlan(changeDir, values, revise) {
|
|
55
|
+
requireMode(values.mode);
|
|
56
|
+
requireOption(values.reason, '--reason');
|
|
57
|
+
requireSafeReason(values.reason);
|
|
58
|
+
const waves = parseWaves(values.wave);
|
|
59
|
+
const existing = readPlan(changeDir);
|
|
60
|
+
if (revise) {
|
|
61
|
+
if (!existing) throw new Error('Cannot revise an execution plan before it is created');
|
|
62
|
+
if (values.mode !== 'sdd') {
|
|
63
|
+
throw new Error('Execution plan revisions must retain or upgrade to sdd; execution-plan downgrades are not allowed');
|
|
64
|
+
}
|
|
65
|
+
} else if (existing) {
|
|
66
|
+
throw new Error('An execution plan already exists; use "tf execution revise" to create a new SDD revision');
|
|
67
|
+
}
|
|
68
|
+
const recommendationReceipt = readCurrentRecommendationReceipt(changeDir, waves, revise ? existing.revision : null);
|
|
69
|
+
const recommendation = recommendationReceipt.recommendation;
|
|
70
|
+
if (!recommendation.available_modes.includes(values.mode)) {
|
|
71
|
+
throw new Error(`${values.mode} is not an available execution mode for this change; choose one of: ${recommendation.available_modes.join(', ')}`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!values.confirm) {
|
|
75
|
+
throw new Error('Execution mode selection requires --confirm after reviewing "tf execution recommend" output');
|
|
76
|
+
}
|
|
77
|
+
const followedRecommendation = values.mode === recommendation.recommendation.mode;
|
|
78
|
+
if (!followedRecommendation && !values['acknowledge-recommendation']) {
|
|
79
|
+
throw new Error(`${values.mode} differs from the ${recommendation.recommendation.mode} recommendation; pass --acknowledge-recommendation to record the informed choice`);
|
|
80
|
+
}
|
|
81
|
+
if (followedRecommendation && values['acknowledge-recommendation']) {
|
|
82
|
+
throw new Error('--acknowledge-recommendation is only valid when selecting a non-recommended mode');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const plan = createPlan(changeDir, {
|
|
86
|
+
mode: values.mode,
|
|
87
|
+
source: revise ? 'user-confirmed-revision' : 'user-confirmed',
|
|
88
|
+
rationale: values.reason,
|
|
89
|
+
waves,
|
|
90
|
+
recommendation,
|
|
91
|
+
recommendationReceipt,
|
|
92
|
+
selection: {
|
|
93
|
+
confirmed: true,
|
|
94
|
+
followed_recommendation: followedRecommendation,
|
|
95
|
+
acknowledged_non_recommendation: !followedRecommendation,
|
|
96
|
+
},
|
|
97
|
+
revision: revise ? existing.revision + 1 : undefined,
|
|
98
|
+
});
|
|
99
|
+
const saved = writePlan(changeDir, plan);
|
|
100
|
+
writeExecutionSummary(changeDir, saved);
|
|
101
|
+
print(values.json, { ok: true, plan: saved }, `Execution plan revision ${saved.revision} recorded (${saved.mode}).`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function recommendAndPrint(changeDir, values) {
|
|
105
|
+
const waves = values.wave?.length ? parseWaves(values.wave) : [];
|
|
106
|
+
const receipt = writeRecommendationReceipt(changeDir, createRecommendationReceipt(changeDir, waves));
|
|
107
|
+
const recommendation = receipt.recommendation;
|
|
108
|
+
const lines = [
|
|
109
|
+
'Available execution modes:',
|
|
110
|
+
...recommendation.available_modes.map(mode => `- ${mode}`),
|
|
111
|
+
`Recommended: ${recommendation.recommendation.mode}`,
|
|
112
|
+
...recommendation.recommendation.reasons.map(reason => `- ${reason}`),
|
|
113
|
+
];
|
|
114
|
+
print(values.json, { ok: true, recommendation, receipt }, lines.join('\n'));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function showPlan(changeDir, json) {
|
|
118
|
+
const plan = readPlan(changeDir);
|
|
119
|
+
if (!plan) throw new Error('No execution plan has been recorded');
|
|
120
|
+
const validation = validatePlan(changeDir, plan);
|
|
121
|
+
const current = validation.valid;
|
|
122
|
+
const waves = describeWaves(changeDir, plan);
|
|
123
|
+
print(json, { ok: current, current, plan, valid: current, failures: validation.failures, waves },
|
|
124
|
+
validation.valid ? `Execution plan revision ${plan.revision} is current.` : validation.failures.join('\n'));
|
|
125
|
+
if (!validation.valid) process.exitCode = 1;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function recordAndPrintReview(changeDir, values) {
|
|
129
|
+
requireOption(values.wave?.[0], '--wave');
|
|
130
|
+
if (values.wave.length !== 1) throw new Error('Review requires exactly one --wave value');
|
|
131
|
+
requireOption(values.base, '--base');
|
|
132
|
+
requireOption(values.head, '--head');
|
|
133
|
+
requireOption(values.report, '--report');
|
|
134
|
+
if (!['pass', 'fail'].includes(values.verdict)) throw new Error("--verdict must be 'pass' or 'fail'");
|
|
135
|
+
const receipt = recordReview(changeDir, values.wave[0], {
|
|
136
|
+
status: values.verdict,
|
|
137
|
+
base: values.base,
|
|
138
|
+
head: values.head,
|
|
139
|
+
report: values.report,
|
|
140
|
+
});
|
|
141
|
+
print(values.json, { ok: true, wave: values.wave[0], receipt }, `Review for ${values.wave[0]} recorded: ${receipt.status}.`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function writeExecutionSummary(changeDir, plan) {
|
|
145
|
+
const summary = `${plan.mode}: plan revision ${plan.revision}; ${plan.source}; ${plan.rationale}`;
|
|
146
|
+
const state = readState(changeDir);
|
|
147
|
+
state.revision = plan.revision;
|
|
148
|
+
state.execution_mode = plan.mode;
|
|
149
|
+
state.execution_plan_hash = plan.hash;
|
|
150
|
+
state.execution_plan_revision = plan.revision;
|
|
151
|
+
state.dp_4_result = summary;
|
|
152
|
+
state.dp_4_timestamp = new Date().toISOString();
|
|
153
|
+
writeState(changeDir, state);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function parseWaves(values) {
|
|
157
|
+
if (!values || values.length === 0) throw new Error('At least one --wave <id>:<strategy>:<task,...> is required');
|
|
158
|
+
return values.map(value => {
|
|
159
|
+
const [id, strategy, taskList, dependencyList, ...extra] = value.split(':');
|
|
160
|
+
if (extra.length > 0 || !id || !strategy || !taskList) {
|
|
161
|
+
throw new Error(`Invalid --wave '${value}'; expected <id>:<strategy>:<task,...>[:<depends-on,...>]`);
|
|
162
|
+
}
|
|
163
|
+
const tasks = taskList.split(',').map(task => task.trim()).filter(Boolean);
|
|
164
|
+
if (tasks.length === 0) throw new Error(`Invalid --wave '${value}'; at least one task is required`);
|
|
165
|
+
const depends_on = dependencyList === undefined ? [] : dependencyList.split(',').map(id => id.trim()).filter(Boolean);
|
|
166
|
+
if (dependencyList !== undefined && depends_on.length === 0) {
|
|
167
|
+
throw new Error(`Invalid --wave '${value}'; dependencies must name at least one wave`);
|
|
168
|
+
}
|
|
169
|
+
return { id, strategy, tasks, depends_on };
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function requireMode(mode) {
|
|
174
|
+
if (!EXECUTION_MODES.includes(mode)) {
|
|
175
|
+
throw new Error(`--mode must be one of: ${EXECUTION_MODES.join(', ')}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function requireOption(value, option) {
|
|
180
|
+
if (typeof value !== 'string' || !value.trim()) throw new Error(`${option} is required`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function requireSafeReason(reason) {
|
|
184
|
+
if (/[\p{Cc}\p{Zl}\p{Zp}]/u.test(reason)) {
|
|
185
|
+
throw new Error('--reason must not contain control characters or line separators');
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function usage(message) {
|
|
190
|
+
console.error(message);
|
|
191
|
+
printHelp();
|
|
192
|
+
process.exit(2);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function print(json, value, message) {
|
|
196
|
+
console.log(json ? JSON.stringify(value) : message);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function printHelp() {
|
|
200
|
+
console.log(`Usage:
|
|
201
|
+
tf execution recommend <dir> [--wave <id>:<strategy>:<task,...>[:<depends-on,...>]] [--json]
|
|
202
|
+
tf execution plan <dir> --mode <mode> --confirm --reason <text> --wave <id>:<strategy>:<task,...>[:<depends-on,...>] [--acknowledge-recommendation]
|
|
203
|
+
tf execution show <dir> [--json]
|
|
204
|
+
tf execution revise <dir> --mode sdd --confirm --reason <text> --wave <id>:<strategy>:<task,...>[:<depends-on,...>] [--acknowledge-recommendation]
|
|
205
|
+
tf execution review <dir> --wave <id> --base <sha> --head <sha> --report <path> --verdict pass|fail`);
|
|
206
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// tf handoff contract lifecycle commands.
|
|
2
|
+
import { parseArgs } from 'node:util';
|
|
3
|
+
import {
|
|
4
|
+
HANDOFF_DECISIONS, HANDOFF_TYPES, createHandoff, finishHandoff, listHandoffs, resolveHandoff,
|
|
5
|
+
} from './sdd-overlay.mjs';
|
|
6
|
+
|
|
7
|
+
const USAGE = 'Usage: tf handoff <create|list|finish|resolve> <change-dir> [options]';
|
|
8
|
+
|
|
9
|
+
export async function run(args) {
|
|
10
|
+
const { positionals, values } = parseArgs({
|
|
11
|
+
args,
|
|
12
|
+
allowPositionals: true,
|
|
13
|
+
options: {
|
|
14
|
+
type: { type: 'string' }, objective: { type: 'string' },
|
|
15
|
+
'expected-output': { type: 'string' }, acceptance: { type: 'string' },
|
|
16
|
+
boundary: { type: 'string', multiple: true }, decision: { type: 'string' },
|
|
17
|
+
'acknowledge-source-drift': { type: 'boolean', default: false },
|
|
18
|
+
json: { type: 'boolean', default: false },
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const subcommand = positionals[0];
|
|
23
|
+
const changeDir = positionals[1];
|
|
24
|
+
if (!subcommand || !changeDir) usage();
|
|
25
|
+
|
|
26
|
+
if (!['create', 'list', 'finish', 'resolve'].includes(subcommand)) {
|
|
27
|
+
console.error(`Unknown handoff subcommand: ${subcommand}. Valid: create, list, finish, resolve`);
|
|
28
|
+
process.exit(2);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (subcommand === 'create') {
|
|
32
|
+
if (!hasText(values.type) || !hasText(values.objective)
|
|
33
|
+
|| !hasText(values['expected-output']) || !hasText(values.acceptance)) {
|
|
34
|
+
usage('Usage: tf handoff create <change-dir> --type <type> --objective <text> --expected-output <text> --acceptance <text>');
|
|
35
|
+
}
|
|
36
|
+
if (!HANDOFF_TYPES.has(values.type)) {
|
|
37
|
+
throw new Error(`Unsupported handoff type '${values.type}'`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const handoff = createHandoff(changeDir, {
|
|
41
|
+
type: values.type,
|
|
42
|
+
title: values.objective,
|
|
43
|
+
question: `Expected output: ${values['expected-output']}`,
|
|
44
|
+
context: `Acceptance: ${values.acceptance}`,
|
|
45
|
+
source: renderBoundaries(values.boundary),
|
|
46
|
+
});
|
|
47
|
+
output(values.json, { ok: true, handoff }, `Handoff created: ${handoff.id}`);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (subcommand === 'list') {
|
|
52
|
+
const handoffs = listHandoffs(changeDir);
|
|
53
|
+
output(values.json, { ok: true, handoffs }, handoffs.length === 0
|
|
54
|
+
? 'No handoffs found.'
|
|
55
|
+
: handoffs.map(handoff => `${handoff.id} type=${handoff.type} status=${handoff.status}`).join('\n'));
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const id = positionals[2];
|
|
60
|
+
if (!hasText(id)) usage(`Usage: tf handoff ${subcommand} <change-dir> <id>${subcommand === 'resolve' ? ' --decision <accept|reject|defer>' : ''}`);
|
|
61
|
+
|
|
62
|
+
if (subcommand === 'finish') {
|
|
63
|
+
const handoff = finishHandoff(changeDir, id);
|
|
64
|
+
output(values.json, { ok: true, handoff }, `Handoff finished: ${handoff.id}`);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!hasText(values.decision)) {
|
|
69
|
+
usage('Usage: tf handoff resolve <change-dir> <id> --decision <accept|reject|defer>');
|
|
70
|
+
}
|
|
71
|
+
if (!HANDOFF_DECISIONS.has(values.decision)) {
|
|
72
|
+
throw new Error(`Unsupported handoff decision '${values.decision}'`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const handoff = resolveHandoff(changeDir, id, values.decision, values['acknowledge-source-drift']);
|
|
76
|
+
output(values.json, { ok: true, handoff }, `Handoff resolved: ${handoff.id} (${handoff.decision})`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function hasText(value) {
|
|
80
|
+
return typeof value === 'string' && value.trim().length > 0;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function renderBoundaries(boundaries) {
|
|
84
|
+
return boundaries?.length ? `Boundaries:\n${boundaries.map(boundary => `- ${boundary}`).join('\n')}` : 'Not recorded';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function output(json, payload, text) {
|
|
88
|
+
console.log(json ? JSON.stringify(payload) : text);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function usage(message = USAGE) {
|
|
92
|
+
console.error(message);
|
|
93
|
+
process.exit(2);
|
|
94
|
+
}
|