@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,165 @@
|
|
|
1
|
+
// scripts/lib/platforms.mjs
|
|
2
|
+
// Platform registry for team-flow cross-platform installers.
|
|
3
|
+
//
|
|
4
|
+
// All paths verified against comet (https://github.com/rpamis/comet)
|
|
5
|
+
// src/core/platforms.ts and each platform's documented conventions.
|
|
6
|
+
// team-flow's guard mechanism is the phase-guard RULE file (auto-included
|
|
7
|
+
// by the platform), not a PreToolUse hook; therefore `supportsHooks`/
|
|
8
|
+
// `hookFormat` are recorded for documentation accuracy only — the shared
|
|
9
|
+
// installer does not write hook configs for these platforms (SessionStart
|
|
10
|
+
// wiring is deferred until each platform's session-start support is
|
|
11
|
+
// individually verified).
|
|
12
|
+
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @typedef {Object} PlatformConfig
|
|
17
|
+
* @property {string} id - platform identifier (cli arg / install-<id>)
|
|
18
|
+
* @property {string} name - human-readable name
|
|
19
|
+
* @property {string} skillsDir - project-local skills base dir (e.g. '.cline')
|
|
20
|
+
* @property {string} [globalSkillsDir] - global (user-level) skills base dir
|
|
21
|
+
* @property {string} [rulesBaseDir] - base dir for rules; '' = project root;
|
|
22
|
+
* defaults to skillsDir when omitted
|
|
23
|
+
* @property {string} [rulesDir] - rules subdirectory (omit => no rules)
|
|
24
|
+
* @property {'md'|'mdc'|'copilot'} [rulesFormat] - rule file format
|
|
25
|
+
* @property {boolean} [supportsHooks] - platform supports hooks (info only)
|
|
26
|
+
* @property {string} [hookFormat] - native hook config format (info only)
|
|
27
|
+
* @property {string} [notes] - install / usage notes
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/** Platforms added by the v0.8.13 platform-expansion PR. */
|
|
31
|
+
export const NEW_PLATFORMS = [
|
|
32
|
+
{
|
|
33
|
+
id: 'cline',
|
|
34
|
+
name: 'Cline',
|
|
35
|
+
skillsDir: '.cline',
|
|
36
|
+
globalSkillsDir: '.cline',
|
|
37
|
+
// Cline reads .clinerules/*.md at project root (not inside .cline/).
|
|
38
|
+
rulesBaseDir: '',
|
|
39
|
+
rulesDir: '.clinerules',
|
|
40
|
+
rulesFormat: 'md',
|
|
41
|
+
supportsHooks: false,
|
|
42
|
+
notes: 'Cline auto-includes .clinerules/*.md as always-on context.',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: 'kiro',
|
|
46
|
+
name: 'Kiro',
|
|
47
|
+
skillsDir: '.kiro',
|
|
48
|
+
globalSkillsDir: '.kiro',
|
|
49
|
+
rulesDir: 'steering',
|
|
50
|
+
rulesFormat: 'md',
|
|
51
|
+
supportsHooks: true,
|
|
52
|
+
hookFormat: 'kiro',
|
|
53
|
+
notes: 'AWS Kiro IDE reads .kiro/steering/*.md as steering rules.',
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: 'windsurf',
|
|
57
|
+
name: 'Windsurf',
|
|
58
|
+
skillsDir: '.windsurf',
|
|
59
|
+
globalSkillsDir: '.windsurf',
|
|
60
|
+
rulesDir: 'rules',
|
|
61
|
+
rulesFormat: 'md',
|
|
62
|
+
supportsHooks: true,
|
|
63
|
+
hookFormat: 'windsurf',
|
|
64
|
+
notes: 'Codeium Windsurf reads .windsurf/rules/*.md as rules.',
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: 'qwen',
|
|
68
|
+
name: 'Qwen Code',
|
|
69
|
+
skillsDir: '.qwen',
|
|
70
|
+
globalSkillsDir: '.qwen',
|
|
71
|
+
rulesDir: 'rules',
|
|
72
|
+
rulesFormat: 'md',
|
|
73
|
+
supportsHooks: true,
|
|
74
|
+
hookFormat: 'qwen',
|
|
75
|
+
notes: 'Qwen Code CLI (Gemini CLI fork) reads .qwen/rules/*.md.',
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
id: 'amazon-q',
|
|
79
|
+
name: 'Amazon Q Developer',
|
|
80
|
+
skillsDir: '.amazonq',
|
|
81
|
+
globalSkillsDir: '.amazonq',
|
|
82
|
+
rulesDir: 'rules',
|
|
83
|
+
rulesFormat: 'md',
|
|
84
|
+
supportsHooks: true,
|
|
85
|
+
hookFormat: 'claude-code',
|
|
86
|
+
notes: 'Amazon Q Developer CLI reads .amazonq/rules/*.md as rules.',
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
id: 'roocode',
|
|
90
|
+
name: 'Roo Code',
|
|
91
|
+
skillsDir: '.roo',
|
|
92
|
+
globalSkillsDir: '.roo',
|
|
93
|
+
rulesDir: 'rules',
|
|
94
|
+
rulesFormat: 'md',
|
|
95
|
+
supportsHooks: false,
|
|
96
|
+
notes: 'Roo Code (Roo Cline) reads .roo/rules/*.md as rules.',
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
id: 'continue',
|
|
100
|
+
name: 'Continue',
|
|
101
|
+
skillsDir: '.continue',
|
|
102
|
+
globalSkillsDir: '.continue',
|
|
103
|
+
rulesDir: 'rules',
|
|
104
|
+
rulesFormat: 'md',
|
|
105
|
+
supportsHooks: false,
|
|
106
|
+
notes: 'Continue (VS Code) reads .continue/rules/*.md as rules.',
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
id: 'pi',
|
|
110
|
+
name: 'Pi',
|
|
111
|
+
skillsDir: '.pi',
|
|
112
|
+
globalSkillsDir: '.pi/agent',
|
|
113
|
+
// Pi has no rules directory — skills-only deployment.
|
|
114
|
+
rulesDir: undefined,
|
|
115
|
+
rulesFormat: undefined,
|
|
116
|
+
supportsHooks: false,
|
|
117
|
+
notes: 'Pi agent reads skills from .pi/ (global: .pi/agent/). No rules dir; invoke "/workflow-start" manually.',
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
id: 'qoder',
|
|
121
|
+
name: 'Qoder',
|
|
122
|
+
skillsDir: '.qoder',
|
|
123
|
+
globalSkillsDir: '.qoder',
|
|
124
|
+
rulesDir: 'rules',
|
|
125
|
+
rulesFormat: 'md',
|
|
126
|
+
supportsHooks: false,
|
|
127
|
+
notes: 'Qoder reads .qoder/rules/*.md as rules.',
|
|
128
|
+
},
|
|
129
|
+
];
|
|
130
|
+
|
|
131
|
+
/** Lookup a new platform config by id. */
|
|
132
|
+
export function getPlatform(id) {
|
|
133
|
+
const p = NEW_PLATFORMS.find(x => x.id === id);
|
|
134
|
+
if (!p) {
|
|
135
|
+
throw new Error(
|
|
136
|
+
`Unknown platform: "${id}". Available: ${NEW_PLATFORMS.map(x => x.id).join(', ')}`,
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
return p;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** All platform ids added by this PR. */
|
|
143
|
+
export const NEW_PLATFORM_IDS = NEW_PLATFORMS.map(p => p.id);
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Compute the project-local rules target directory for a platform.
|
|
147
|
+
* Mirrors comet: rules go to (rulesBaseDir || skillsDir)/rulesDir.
|
|
148
|
+
* For Cline, rulesBaseDir='' places .clinerules/ at project root.
|
|
149
|
+
*/
|
|
150
|
+
export function rulesTargetDir(platform, targetRoot) {
|
|
151
|
+
if (!platform.rulesDir) return null;
|
|
152
|
+
const base = platform.rulesBaseDir !== undefined ? platform.rulesBaseDir : platform.skillsDir;
|
|
153
|
+
// join(targetRoot, '', '.clinerules') === targetRoot/.clinerules
|
|
154
|
+
return join(targetRoot, base, platform.rulesDir);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Phase-guard rule file name for a rules format. */
|
|
158
|
+
export function phaseGuardFileName(rulesFormat) {
|
|
159
|
+
switch (rulesFormat) {
|
|
160
|
+
case 'mdc': return 'phase-guard.mdc';
|
|
161
|
+
case 'copilot': return 'team-flow.instructions.md';
|
|
162
|
+
case 'md':
|
|
163
|
+
default: return 'phase-guard.md';
|
|
164
|
+
}
|
|
165
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* prototype-sync — change 完成后将 UX 增量合并回全局 prototype/ + design-system.md
|
|
4
|
+
*
|
|
5
|
+
* v0.3 定义,v0.5 实现为 CLI 子命令
|
|
6
|
+
* 用法:tf prototype-sync <change-dir> --source <ux-delta-path>
|
|
7
|
+
*
|
|
8
|
+
* 功能:
|
|
9
|
+
* 1. 读取 change 的 UX 增量(从 execution-contract.md 或指定 --source 路径)
|
|
10
|
+
* 2. 合并进全局 prototype/(pages/components/assets/flow.md)
|
|
11
|
+
* 3. 若涉及设计系统迭代(新组件/token/anti-pattern),合并进 prototype/design-system.md
|
|
12
|
+
* 4. 输出合并报告
|
|
13
|
+
*
|
|
14
|
+
* 回写顺序:arch-merge → prototype-sync(同一 change closing 内,顺序提交)
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { readFileSync, writeFileSync, existsSync, cpSync, mkdirSync } from 'node:fs';
|
|
18
|
+
import { join, basename, relative } from 'node:path';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 解析 CLI 参数数组为结构化对象
|
|
22
|
+
* @param {string[]} argv - 原始参数数组,如 ['changes/my-change/', '--source', 'delta.md']
|
|
23
|
+
* @returns {{ _: string[], source?: string, prototypeDir?: string }}
|
|
24
|
+
*/
|
|
25
|
+
function parseArgv(argv) {
|
|
26
|
+
const parsed = { _: [] };
|
|
27
|
+
for (let i = 0; i < argv.length; i++) {
|
|
28
|
+
if (argv[i] === '--source' && argv[i + 1] && !argv[i + 1].startsWith('--')) {
|
|
29
|
+
parsed.source = argv[++i];
|
|
30
|
+
} else if (argv[i] === '--prototype-dir' && argv[i + 1] && !argv[i + 1].startsWith('--')) {
|
|
31
|
+
parsed.prototypeDir = argv[++i];
|
|
32
|
+
} else if (!argv[i].startsWith('--')) {
|
|
33
|
+
parsed._.push(argv[i]);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return parsed;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 执行 prototype-sync 合并
|
|
41
|
+
* @param {string[]|object} args - CLI 参数数组(从 tf 框架传入)或已解析的对象
|
|
42
|
+
* @returns {{ synced: boolean, pages?: number, components?: number, designSystem?: boolean, conflicts?: string[], reason?: string }}
|
|
43
|
+
*/
|
|
44
|
+
export function run(args = {}) {
|
|
45
|
+
// 支持两种调用方式:数组(CLI 框架 mod.run(commandArgs))或对象(直接调用)
|
|
46
|
+
if (Array.isArray(args)) {
|
|
47
|
+
args = parseArgv(args);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const changeDir = args._?.[0] || args.changeDir;
|
|
51
|
+
const sourcePath = args.source;
|
|
52
|
+
const prototypeDir = args.prototypeDir || 'prototype';
|
|
53
|
+
|
|
54
|
+
if (!changeDir) {
|
|
55
|
+
console.error('Usage: tf prototype-sync <change-dir> [--source <ux-delta-path>] [--prototype-dir <path>]');
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 1. 确定 UX 增量来源
|
|
60
|
+
let uxDeltaContent = '';
|
|
61
|
+
let uxDeltaSource = '';
|
|
62
|
+
|
|
63
|
+
if (sourcePath && existsSync(sourcePath)) {
|
|
64
|
+
uxDeltaContent = readFileSync(sourcePath, 'utf-8');
|
|
65
|
+
uxDeltaSource = sourcePath;
|
|
66
|
+
} else {
|
|
67
|
+
// 默认从 execution-contract.md 提取 UX 增量
|
|
68
|
+
const contractPath = join(changeDir, 'execution-contract.md');
|
|
69
|
+
if (existsSync(contractPath)) {
|
|
70
|
+
const contract = readFileSync(contractPath, 'utf-8');
|
|
71
|
+
// 提取 UX 相关章节(## UX 增量 或 ## Prototype Changes)
|
|
72
|
+
const uxMatch = contract.match(/## (?:UX 增量|Prototype Changes|UX Delta)\n([\s\S]*?)(?=\n## |\n---|\n$)/);
|
|
73
|
+
if (uxMatch) {
|
|
74
|
+
uxDeltaContent = uxMatch[1].trim();
|
|
75
|
+
uxDeltaSource = contractPath;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!uxDeltaContent) {
|
|
81
|
+
console.log('No UX delta found in change, nothing to sync');
|
|
82
|
+
return { synced: false, reason: 'no-ux-delta' };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
console.log(`UX delta source: ${uxDeltaSource}`);
|
|
86
|
+
|
|
87
|
+
// 2. 确保全局 prototype/ 目录存在
|
|
88
|
+
if (!existsSync(prototypeDir)) {
|
|
89
|
+
console.log(`Creating prototype directory: ${prototypeDir}`);
|
|
90
|
+
mkdirSync(prototypeDir, { recursive: true });
|
|
91
|
+
mkdirSync(join(prototypeDir, 'pages'), { recursive: true });
|
|
92
|
+
mkdirSync(join(prototypeDir, 'components'), { recursive: true });
|
|
93
|
+
mkdirSync(join(prototypeDir, 'assets'), { recursive: true });
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 3. 解析 UX 增量中的文件操作
|
|
97
|
+
// UX 增量格式约定:
|
|
98
|
+
// - 新增页面:`[ADD] pages/xxx.html`
|
|
99
|
+
// - 修改页面:`[MODIFY] pages/xxx.html`
|
|
100
|
+
// - 新增组件:`[ADD] components/xxx.html`
|
|
101
|
+
// - 设计系统更新:`[DS] design-system.md` + 具体内容
|
|
102
|
+
const operations = [];
|
|
103
|
+
const lines = uxDeltaContent.split('\n');
|
|
104
|
+
let currentOp = null;
|
|
105
|
+
let currentContent = [];
|
|
106
|
+
|
|
107
|
+
for (const line of lines) {
|
|
108
|
+
const opMatch = line.match(/^\[(ADD|MODIFY|DS)\]\s+(.+)$/);
|
|
109
|
+
if (opMatch) {
|
|
110
|
+
if (currentOp) {
|
|
111
|
+
operations.push({ ...currentOp, content: currentContent.join('\n') });
|
|
112
|
+
}
|
|
113
|
+
currentOp = { action: opMatch[1], target: opMatch[2].trim() };
|
|
114
|
+
currentContent = [];
|
|
115
|
+
} else if (currentOp) {
|
|
116
|
+
currentContent.push(line);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (currentOp) {
|
|
120
|
+
operations.push({ ...currentOp, content: currentContent.join('\n') });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// 4. 执行合并
|
|
124
|
+
const report = { synced: true, pages: 0, components: 0, designSystem: false, conflicts: [] };
|
|
125
|
+
|
|
126
|
+
for (const op of operations) {
|
|
127
|
+
const targetPath = join(prototypeDir, op.target);
|
|
128
|
+
|
|
129
|
+
if (op.action === 'DS') {
|
|
130
|
+
// 设计系统更新
|
|
131
|
+
const dsPath = join(prototypeDir, 'design-system.md');
|
|
132
|
+
if (existsSync(dsPath)) {
|
|
133
|
+
const existing = readFileSync(dsPath, 'utf-8');
|
|
134
|
+
// 追加到 design-system.md 末尾(不覆盖,避免冲突)
|
|
135
|
+
writeFileSync(dsPath, existing + '\n\n<!-- prototype-sync: ' + new Date().toISOString().slice(0, 10) + ' -->\n' + op.content + '\n', 'utf-8');
|
|
136
|
+
} else {
|
|
137
|
+
writeFileSync(dsPath, op.content, 'utf-8');
|
|
138
|
+
}
|
|
139
|
+
report.designSystem = true;
|
|
140
|
+
console.log(` [DS] Updated design-system.md`);
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// 页面/组件文件操作
|
|
145
|
+
if (op.action === 'ADD' && existsSync(targetPath)) {
|
|
146
|
+
report.conflicts.push(`ADD conflict: ${op.target} already exists`);
|
|
147
|
+
console.log(` [CONFLICT] ${op.target} already exists, skipping ADD`);
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// 确保目录存在
|
|
152
|
+
const targetDir = join(prototypeDir, op.target.includes('/') ? op.target.split('/').slice(0, -1).join('/') : '');
|
|
153
|
+
if (targetDir && !existsSync(targetDir)) {
|
|
154
|
+
mkdirSync(targetDir, { recursive: true });
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
writeFileSync(targetPath, op.content, 'utf-8');
|
|
158
|
+
|
|
159
|
+
if (op.target.startsWith('pages/')) report.pages++;
|
|
160
|
+
if (op.target.startsWith('components/')) report.components++;
|
|
161
|
+
console.log(` [${op.action}] ${op.target}`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// 5. 输出合并报告
|
|
165
|
+
console.log(`\nPrototype sync complete:`);
|
|
166
|
+
console.log(` Pages: ${report.pages} synced`);
|
|
167
|
+
console.log(` Components: ${report.components} synced`);
|
|
168
|
+
console.log(` Design system: ${report.designSystem ? 'updated' : 'unchanged'}`);
|
|
169
|
+
if (report.conflicts.length > 0) {
|
|
170
|
+
console.log(` Conflicts: ${report.conflicts.length}`);
|
|
171
|
+
for (const c of report.conflicts) {
|
|
172
|
+
console.log(` - ${c}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return report;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// 支持直接执行:node prototype-sync.mjs <change-dir> --source <path>
|
|
180
|
+
if (process.argv[1]?.includes('prototype-sync')) {
|
|
181
|
+
const args = { _: [] };
|
|
182
|
+
for (let i = 2; i < process.argv.length; i++) {
|
|
183
|
+
if (process.argv[i].startsWith('--')) {
|
|
184
|
+
const key = process.argv[i].replace('--', '');
|
|
185
|
+
const val = process.argv[i + 1] && !process.argv[i + 1].startsWith('--') ? process.argv[++i] : true;
|
|
186
|
+
args[key] = val;
|
|
187
|
+
} else {
|
|
188
|
+
args._.push(process.argv[i]);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
run(args);
|
|
192
|
+
}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import {
|
|
3
|
+
existsSync, mkdirSync, readdirSync, readFileSync, renameSync, writeFileSync,
|
|
4
|
+
} from 'node:fs';
|
|
5
|
+
import { dirname, join } from 'node:path';
|
|
6
|
+
import { computeArtifactsHash } from './hash.mjs';
|
|
7
|
+
import { readState } from './state-loader.mjs';
|
|
8
|
+
|
|
9
|
+
export const HANDOFF_TYPES = new Set(['prototype', 'research', 'experiment']);
|
|
10
|
+
export const HANDOFF_DECISIONS = new Set(['accept', 'reject', 'defer']);
|
|
11
|
+
export const RESULT_HEADINGS = [
|
|
12
|
+
'Conclusion', 'Evidence', 'Produced Artifacts', 'Risks', 'Suggested Changes',
|
|
13
|
+
];
|
|
14
|
+
const HANDOFF_RESULT_FILE = 'HANDOFF_RESULT.md';
|
|
15
|
+
|
|
16
|
+
export function getOverlayPaths(changeDir) {
|
|
17
|
+
const root = join(changeDir, '.superpowers', 'sdd');
|
|
18
|
+
return {
|
|
19
|
+
root,
|
|
20
|
+
checkpoints: join(root, 'checkpoints'),
|
|
21
|
+
handoffs: join(root, 'handoffs'),
|
|
22
|
+
executionPlan: join(root, 'execution-plan.json'),
|
|
23
|
+
executionRecommendation: join(root, 'execution-recommendation.json'),
|
|
24
|
+
reviews: join(root, 'reviews'),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function computeTaskHash(changeDir, taskId) {
|
|
29
|
+
const tasks = readFileSync(join(changeDir, 'tasks.md'), 'utf8');
|
|
30
|
+
const escaped = taskId.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
31
|
+
const match = tasks.match(new RegExp(`^- \\[([ xX])\\] ${escaped}\\s+.+$`, 'm'));
|
|
32
|
+
if (!match) throw new Error(`Task '${taskId}' was not found in tasks.md`);
|
|
33
|
+
return `sha256:${createHash('sha256').update(match[0]).digest('hex')}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function saveCheckpoint(changeDir, input) {
|
|
37
|
+
requireText(input?.taskId, 'taskId');
|
|
38
|
+
requireText(input?.next, 'next');
|
|
39
|
+
const paths = getOverlayPaths(changeDir);
|
|
40
|
+
mkdirSync(paths.checkpoints, { recursive: true });
|
|
41
|
+
const record = {
|
|
42
|
+
task_id: input.taskId,
|
|
43
|
+
task_hash: computeTaskHash(changeDir, input.taskId),
|
|
44
|
+
next: input.next,
|
|
45
|
+
completed: input.completed ?? 'Not recorded',
|
|
46
|
+
evidence: input.evidence ?? 'Not recorded',
|
|
47
|
+
review: input.review ?? 'Not recorded',
|
|
48
|
+
risk: input.risk ?? 'Not recorded',
|
|
49
|
+
commit_start: input.commitStart ?? 'Not recorded',
|
|
50
|
+
commit_end: input.commitEnd ?? 'Not recorded',
|
|
51
|
+
created_at: new Date().toISOString(),
|
|
52
|
+
};
|
|
53
|
+
const targetPath = join(paths.checkpoints, `${safeName(input.taskId)}.md`);
|
|
54
|
+
atomicWrite(targetPath, renderRecord(record, `# Checkpoint: ${input.taskId}`, checkpointBody(record)));
|
|
55
|
+
return readCheckpoint(targetPath);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function listCheckpoints(changeDir) {
|
|
59
|
+
const directory = getOverlayPaths(changeDir).checkpoints;
|
|
60
|
+
if (!existsSync(directory)) return [];
|
|
61
|
+
return readdirSync(directory).filter(name => name.endsWith('.md')).sort()
|
|
62
|
+
.map(name => readCheckpoint(join(directory, name)));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function getCheckpoint(changeDir, taskId) {
|
|
66
|
+
const filePath = join(getOverlayPaths(changeDir).checkpoints, `${safeName(taskId)}.md`);
|
|
67
|
+
if (!existsSync(filePath)) return null;
|
|
68
|
+
return readCheckpoint(filePath);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function createHandoff(changeDir, input) {
|
|
72
|
+
requireText(input?.type, 'type');
|
|
73
|
+
if (!HANDOFF_TYPES.has(input.type)) throw new Error(`Unsupported handoff type '${input.type}'`);
|
|
74
|
+
requireText(input?.title, 'title');
|
|
75
|
+
requireText(input?.question, 'question');
|
|
76
|
+
const paths = getOverlayPaths(changeDir);
|
|
77
|
+
const id = input.id || `${Date.now()}-${randomUUID().slice(0, 8)}`;
|
|
78
|
+
const directory = join(paths.handoffs, safeName(id));
|
|
79
|
+
mkdirSync(directory, { recursive: true });
|
|
80
|
+
const state = readState(changeDir);
|
|
81
|
+
const metadata = {
|
|
82
|
+
id,
|
|
83
|
+
type: input.type,
|
|
84
|
+
title: input.title,
|
|
85
|
+
question: input.question,
|
|
86
|
+
context: input.context ?? 'Not recorded',
|
|
87
|
+
source: input.source ?? 'Not recorded',
|
|
88
|
+
core_state: state.state,
|
|
89
|
+
state: state.state,
|
|
90
|
+
source_artifacts_hash: computeArtifactsHash(changeDir),
|
|
91
|
+
status: 'active',
|
|
92
|
+
created_at: new Date().toISOString(),
|
|
93
|
+
};
|
|
94
|
+
atomicWrite(join(directory, 'HANDOFF.md'), renderRecord(
|
|
95
|
+
metadata,
|
|
96
|
+
`# Handoff: ${input.title}`,
|
|
97
|
+
handoffBody(metadata),
|
|
98
|
+
));
|
|
99
|
+
atomicWrite(join(directory, HANDOFF_RESULT_FILE), renderResultTemplate());
|
|
100
|
+
return readHandoff(directory);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function listHandoffs(changeDir) {
|
|
104
|
+
const directory = getOverlayPaths(changeDir).handoffs;
|
|
105
|
+
if (!existsSync(directory)) return [];
|
|
106
|
+
return readdirSync(directory, { withFileTypes: true }).filter(entry => entry.isDirectory())
|
|
107
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
108
|
+
.map(entry => readHandoff(join(directory, entry.name)));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function finishHandoff(changeDir, id) {
|
|
112
|
+
const handoff = getHandoff(changeDir, id);
|
|
113
|
+
if (!handoff) throw new Error(`Handoff '${id}' was not found`);
|
|
114
|
+
const resultPath = join(handoff.directory, HANDOFF_RESULT_FILE);
|
|
115
|
+
const result = parseResult(readFileSync(resultPath, 'utf8'));
|
|
116
|
+
for (const heading of RESULT_HEADINGS) {
|
|
117
|
+
if (!result[heading]) throw new Error(`${heading} must contain non-empty content`);
|
|
118
|
+
}
|
|
119
|
+
if (handoff.status !== 'active') throw new Error(`Handoff '${id}' is not active`);
|
|
120
|
+
const metadata = { ...handoff.metadata, status: 'result-ready', result_ready_at: new Date().toISOString() };
|
|
121
|
+
atomicWrite(join(handoff.directory, 'HANDOFF.md'), renderRecord(
|
|
122
|
+
metadata,
|
|
123
|
+
`# Handoff: ${metadata.title}`,
|
|
124
|
+
handoffBody(metadata),
|
|
125
|
+
));
|
|
126
|
+
return readHandoff(handoff.directory);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function resolveHandoff(changeDir, id, decision, acknowledgeSourceDrift) {
|
|
130
|
+
if (!HANDOFF_DECISIONS.has(decision)) throw new Error(`Unsupported handoff decision '${decision}'`);
|
|
131
|
+
const handoff = getHandoff(changeDir, id);
|
|
132
|
+
if (!handoff) throw new Error(`Handoff '${id}' was not found`);
|
|
133
|
+
if (handoff.status !== 'result-ready') throw new Error(`Handoff '${id}' is not result-ready`);
|
|
134
|
+
const sourceDrift = handoff.source_artifacts_hash !== computeArtifactsHash(changeDir);
|
|
135
|
+
if (sourceDrift && acknowledgeSourceDrift !== true) {
|
|
136
|
+
throw new Error('resolve requires acknowledge-source-drift');
|
|
137
|
+
}
|
|
138
|
+
const metadata = {
|
|
139
|
+
...handoff.metadata,
|
|
140
|
+
status: 'resolved',
|
|
141
|
+
decision,
|
|
142
|
+
resolved_at: new Date().toISOString(),
|
|
143
|
+
source_drift: sourceDrift,
|
|
144
|
+
source_drift_acknowledged: sourceDrift && acknowledgeSourceDrift === true,
|
|
145
|
+
};
|
|
146
|
+
atomicWrite(join(handoff.directory, 'HANDOFF.md'), renderRecord(
|
|
147
|
+
metadata,
|
|
148
|
+
`# Handoff: ${metadata.title}`,
|
|
149
|
+
handoffBody(metadata),
|
|
150
|
+
));
|
|
151
|
+
return readHandoff(handoff.directory);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function checkpointBody(record) {
|
|
155
|
+
return [
|
|
156
|
+
`## Next\n${record.next}`,
|
|
157
|
+
`## Completed\n${record.completed}`,
|
|
158
|
+
`## Evidence\n${record.evidence}`,
|
|
159
|
+
`## Review\n${record.review}`,
|
|
160
|
+
`## Risk\n${record.risk}`,
|
|
161
|
+
`## Commit Start\n${record.commit_start}`,
|
|
162
|
+
`## Commit End\n${record.commit_end}`,
|
|
163
|
+
].join('\n\n');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function handoffBody(metadata) {
|
|
167
|
+
return [
|
|
168
|
+
`## Question\n${metadata.question}`,
|
|
169
|
+
`## Context\n${metadata.context}`,
|
|
170
|
+
`## Source\n${metadata.source}`,
|
|
171
|
+
].join('\n\n');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function renderResultTemplate() {
|
|
175
|
+
return `${RESULT_HEADINGS.map(heading => `## ${heading}\n`).join('\n')}\n`;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function readCheckpoint(filePath) {
|
|
179
|
+
const { metadata } = parseRecord(readFileSync(filePath, 'utf8'));
|
|
180
|
+
metadata.commit_start ??= 'Not recorded';
|
|
181
|
+
metadata.commit_end ??= 'Not recorded';
|
|
182
|
+
let currentHash;
|
|
183
|
+
try {
|
|
184
|
+
currentHash = computeTaskHash(dirname(dirname(dirname(dirname(filePath)))), metadata.task_id);
|
|
185
|
+
} catch (error) {
|
|
186
|
+
if (error instanceof Error && error.message === `Task '${metadata.task_id}' was not found in tasks.md`) {
|
|
187
|
+
return { ...metadata, stale: true };
|
|
188
|
+
}
|
|
189
|
+
throw error;
|
|
190
|
+
}
|
|
191
|
+
return { ...metadata, stale: metadata.task_hash !== currentHash };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function readHandoff(directory) {
|
|
195
|
+
const { metadata } = parseRecord(readFileSync(join(directory, 'HANDOFF.md'), 'utf8'));
|
|
196
|
+
return { ...metadata, directory };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function getHandoff(changeDir, id) {
|
|
200
|
+
const directory = join(getOverlayPaths(changeDir).handoffs, safeName(id));
|
|
201
|
+
if (!existsSync(join(directory, 'HANDOFF.md'))) return null;
|
|
202
|
+
const { metadata } = parseRecord(readFileSync(join(directory, 'HANDOFF.md'), 'utf8'));
|
|
203
|
+
return { ...metadata, directory, metadata };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function renderRecord(metadata, title, body) {
|
|
207
|
+
const frontmatter = Object.entries(metadata)
|
|
208
|
+
.map(([key, value]) => `${key}: ${JSON.stringify(value)}`)
|
|
209
|
+
.join('\n');
|
|
210
|
+
return `---\n${frontmatter}\n---\n\n${title}\n\n${body}\n`;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function parseRecord(content) {
|
|
214
|
+
const lines = content.split('\n');
|
|
215
|
+
if (lines[0] !== '---') throw new Error('Record is missing frontmatter');
|
|
216
|
+
const end = lines.indexOf('---', 1);
|
|
217
|
+
if (end < 0) throw new Error('Record frontmatter is not closed');
|
|
218
|
+
const metadata = {};
|
|
219
|
+
for (const line of lines.slice(1, end)) {
|
|
220
|
+
const match = line.match(/^([A-Za-z_][\w-]*):\s*(.*)$/);
|
|
221
|
+
if (!match) continue;
|
|
222
|
+
metadata[match[1]] = parseScalar(match[2]);
|
|
223
|
+
}
|
|
224
|
+
return { metadata, body: lines.slice(end + 1).join('\n') };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function parseScalar(value) {
|
|
228
|
+
if (value.startsWith('"')) {
|
|
229
|
+
try { return JSON.parse(value); } catch { return value; }
|
|
230
|
+
}
|
|
231
|
+
return value;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function parseResult(content) {
|
|
235
|
+
const result = {};
|
|
236
|
+
let currentHeading = null;
|
|
237
|
+
for (const line of content.split('\n')) {
|
|
238
|
+
const heading = line.match(/^## (.+?)\s*$/)?.[1];
|
|
239
|
+
if (heading && RESULT_HEADINGS.includes(heading)) {
|
|
240
|
+
currentHeading = heading;
|
|
241
|
+
result[currentHeading] = '';
|
|
242
|
+
} else if (currentHeading) {
|
|
243
|
+
result[currentHeading] += `${line}\n`;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
for (const heading of RESULT_HEADINGS) result[heading] = result[heading]?.trim() || '';
|
|
247
|
+
return result;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function atomicWrite(targetPath, content) {
|
|
251
|
+
const tempPath = `${targetPath}.tmp-${process.pid}-${randomUUID()}`;
|
|
252
|
+
writeFileSync(tempPath, content, 'utf8');
|
|
253
|
+
renameSync(tempPath, targetPath);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function requireText(value, field) {
|
|
257
|
+
if (typeof value !== 'string' || !value.trim()) throw new Error(`${field} is required`);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function safeName(value) {
|
|
261
|
+
return String(value).replace(/[^A-Za-z0-9._-]/g, '_');
|
|
262
|
+
}
|