@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,102 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* solutions-capture — 写入一条经验到 INDEX.md + 对应目录
|
|
4
|
+
*
|
|
5
|
+
* v0.5 复利贯穿机制脚本
|
|
6
|
+
* 用法:tf solutions capture --phase <p> --domain <d> --type <t> --severity <s> --summary "<text>"
|
|
7
|
+
*
|
|
8
|
+
* 功能:
|
|
9
|
+
* 1. 在对应阶段目录下创建经验文件
|
|
10
|
+
* 2. 在 INDEX.md 中追加一行摘要
|
|
11
|
+
* 3. INDEX.md 超过 150 行时触发 index-gen 重建
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, appendFileSync } from 'node:fs';
|
|
15
|
+
import { join } from 'node:path';
|
|
16
|
+
|
|
17
|
+
const PHASES = ['prd', 'plan', 'prototype', 'spec', 'build', 'review', 'cross-phase'];
|
|
18
|
+
const MAX_INDEX_LINES = 150;
|
|
19
|
+
|
|
20
|
+
function slugify(text) {
|
|
21
|
+
return text
|
|
22
|
+
.toLowerCase()
|
|
23
|
+
.replace(/[^a-z0-9一-鿿]+/g, '-')
|
|
24
|
+
.replace(/^-|-$/g, '')
|
|
25
|
+
.slice(0, 40);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function run(args = {}) {
|
|
29
|
+
const phase = args.phase || 'cross-phase';
|
|
30
|
+
const domain = args.domain || 'general';
|
|
31
|
+
const type = args.type || 'insight';
|
|
32
|
+
const severity = args.severity || 'medium';
|
|
33
|
+
const summary = args.summary || '(no summary)';
|
|
34
|
+
const source = args.source || '';
|
|
35
|
+
const dir = args.dir || 'docs/solutions';
|
|
36
|
+
|
|
37
|
+
if (!PHASES.includes(phase)) {
|
|
38
|
+
console.error(`Invalid phase: ${phase}. Valid: ${PHASES.join(', ')}`);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 确保目录存在
|
|
43
|
+
const phaseDir = join(dir, phase);
|
|
44
|
+
if (!existsSync(phaseDir)) mkdirSync(phaseDir, { recursive: true });
|
|
45
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
46
|
+
|
|
47
|
+
// 生成文件名
|
|
48
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
49
|
+
const slug = slugify(summary);
|
|
50
|
+
const fileName = `${date}-${slug}.md`;
|
|
51
|
+
const filePath = join(phaseDir, fileName);
|
|
52
|
+
|
|
53
|
+
// 写经验文件
|
|
54
|
+
const content = `---
|
|
55
|
+
phase: ${phase}
|
|
56
|
+
domain: ${domain}
|
|
57
|
+
type: ${type}
|
|
58
|
+
severity: ${severity}
|
|
59
|
+
date: ${date}
|
|
60
|
+
source: ${source}
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## 问题描述
|
|
64
|
+
${summary}
|
|
65
|
+
|
|
66
|
+
## 根因/模式
|
|
67
|
+
(待补充)
|
|
68
|
+
|
|
69
|
+
## 预防措施/应用方式
|
|
70
|
+
(待补充)
|
|
71
|
+
`;
|
|
72
|
+
writeFileSync(filePath, content, 'utf-8');
|
|
73
|
+
|
|
74
|
+
// 追加 INDEX.md
|
|
75
|
+
const indexPath = join(dir, 'INDEX.md');
|
|
76
|
+
if (!existsSync(indexPath)) {
|
|
77
|
+
writeFileSync(indexPath, '# Solutions Index\n<!-- 每条一行,按 severity 降序,≤150 行硬上限 -->\n| date | phase | domain | type | severity | summary | file |\n|------|-------|--------|------|----------|---------|------|\n', 'utf-8');
|
|
78
|
+
}
|
|
79
|
+
appendFileSync(indexPath, `| ${date} | ${phase} | ${domain} | ${type} | ${severity} | ${summary.slice(0, 80)} | ${phase}/${fileName} |\n`, 'utf-8');
|
|
80
|
+
|
|
81
|
+
// 检查是否超过 150 行
|
|
82
|
+
const indexContent = readFileSync(indexPath, 'utf-8');
|
|
83
|
+
const dataLines = indexContent.split('\n').filter(l => l.startsWith('|') && !l.startsWith('| date') && !l.startsWith('|--'));
|
|
84
|
+
if (dataLines.length > MAX_INDEX_LINES) {
|
|
85
|
+
console.log(`INDEX.md exceeded ${MAX_INDEX_LINES} lines, consider running: tf solutions index-gen`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
console.log(`Captured: ${phase}/${fileName} (${severity}/${type})`);
|
|
89
|
+
return { file: `${phase}/${fileName}`, phase, domain, type, severity };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (process.argv[1]?.includes('solutions-capture')) {
|
|
93
|
+
const args = {};
|
|
94
|
+
for (let i = 2; i < process.argv.length; i++) {
|
|
95
|
+
if (process.argv[i].startsWith('--')) {
|
|
96
|
+
const key = process.argv[i].replace('--', '');
|
|
97
|
+
const val = process.argv[i + 1] && !process.argv[i + 1].startsWith('--') ? process.argv[++i] : true;
|
|
98
|
+
args[key] = val;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
run(args);
|
|
102
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* solutions-index-gen — 扫描 docs/solutions/<phase>/ 各阶段目录,重建 INDEX.md
|
|
4
|
+
*
|
|
5
|
+
* v0.5 复利贯穿机制脚本
|
|
6
|
+
* 用法:tf solutions index-gen [--dir <solutions-dir>]
|
|
7
|
+
*
|
|
8
|
+
* 功能:
|
|
9
|
+
* 1. 扫描 docs/solutions/ 下所有子目录中的 .md 文件
|
|
10
|
+
* 2. 解析每个文件的 frontmatter(phase/domain/type/severity/date/summary)
|
|
11
|
+
* 3. 按 severity 降序、date 降序排列
|
|
12
|
+
* 4. 重建 INDEX.md(≤150 行硬上限)
|
|
13
|
+
* 5. 超出 150 条时,淘汰 low severity 且 date 最早的条目
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { readFileSync, writeFileSync, readdirSync, statSync, existsSync } from 'node:fs';
|
|
17
|
+
import { join } from 'node:path';
|
|
18
|
+
|
|
19
|
+
const PHASES = ['prd', 'plan', 'prototype', 'spec', 'build', 'review', 'cross-phase'];
|
|
20
|
+
const SEVERITY_ORDER = { high: 0, medium: 1, low: 2 };
|
|
21
|
+
const MAX_INDEX_LINES = 150;
|
|
22
|
+
|
|
23
|
+
function parseFrontmatter(content) {
|
|
24
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
25
|
+
if (!match) return null;
|
|
26
|
+
const fm = {};
|
|
27
|
+
for (const line of match[1].split('\n')) {
|
|
28
|
+
const idx = line.indexOf(':');
|
|
29
|
+
if (idx > 0) {
|
|
30
|
+
const key = line.slice(0, idx).trim();
|
|
31
|
+
const val = line.slice(idx + 1).trim();
|
|
32
|
+
fm[key] = val;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return fm;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function extractSummary(content) {
|
|
39
|
+
// 取 frontmatter 后第一个非空行作为摘要
|
|
40
|
+
const body = content.replace(/^---\n[\s\S]*?\n---\n*/, '');
|
|
41
|
+
const lines = body.split('\n').filter(l => l.trim() && !l.startsWith('#'));
|
|
42
|
+
return lines[0]?.trim().slice(0, 80) || '(no summary)';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function run(args = {}) {
|
|
46
|
+
const dir = args.dir || 'docs/solutions';
|
|
47
|
+
const indexPath = join(dir, 'INDEX.md');
|
|
48
|
+
|
|
49
|
+
if (!existsSync(dir)) {
|
|
50
|
+
console.error(`Solutions directory not found: ${dir}`);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const entries = [];
|
|
55
|
+
|
|
56
|
+
for (const phase of PHASES) {
|
|
57
|
+
const phaseDir = join(dir, phase);
|
|
58
|
+
if (!existsSync(phaseDir) || !statSync(phaseDir).isDirectory()) continue;
|
|
59
|
+
|
|
60
|
+
for (const file of readdirSync(phaseDir)) {
|
|
61
|
+
if (!file.endsWith('.md')) continue;
|
|
62
|
+
const filePath = join(phaseDir, file);
|
|
63
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
64
|
+
const fm = parseFrontmatter(content);
|
|
65
|
+
if (!fm) continue;
|
|
66
|
+
|
|
67
|
+
entries.push({
|
|
68
|
+
date: fm.date || '1970-01-01',
|
|
69
|
+
phase: fm.phase || phase,
|
|
70
|
+
domain: fm.domain || 'general',
|
|
71
|
+
type: fm.type || 'insight',
|
|
72
|
+
severity: fm.severity || 'low',
|
|
73
|
+
summary: extractSummary(content),
|
|
74
|
+
file: `${phase}/${file}`,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 排序:severity 降序,date 降序
|
|
80
|
+
entries.sort((a, b) => {
|
|
81
|
+
const sevDiff = (SEVERITY_ORDER[a.severity] ?? 3) - (SEVERITY_ORDER[b.severity] ?? 3);
|
|
82
|
+
if (sevDiff !== 0) return sevDiff;
|
|
83
|
+
return b.date.localeCompare(a.date);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// 截断到 150 条
|
|
87
|
+
const kept = entries.slice(0, MAX_INDEX_LINES);
|
|
88
|
+
const dropped = entries.slice(MAX_INDEX_LINES);
|
|
89
|
+
|
|
90
|
+
// 生成 INDEX.md
|
|
91
|
+
let index = '# Solutions Index\n';
|
|
92
|
+
index += '<!-- 每条一行,按 severity 降序,≤150 行硬上限 -->\n';
|
|
93
|
+
index += '| date | phase | domain | type | severity | summary | file |\n';
|
|
94
|
+
index += '|------|-------|--------|------|----------|---------|------|\n';
|
|
95
|
+
for (const e of kept) {
|
|
96
|
+
index += `| ${e.date} | ${e.phase} | ${e.domain} | ${e.type} | ${e.severity} | ${e.summary} | ${e.file} |\n`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
writeFileSync(indexPath, index, 'utf-8');
|
|
100
|
+
console.log(`INDEX.md rebuilt: ${kept.length} entries (${dropped.length} dropped)`);
|
|
101
|
+
if (dropped.length > 0) {
|
|
102
|
+
console.log(`Dropped ${dropped.length} low-priority entries (files preserved in directories)`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// CLI 直接执行
|
|
107
|
+
if (process.argv[1]?.includes('solutions-index-gen')) {
|
|
108
|
+
const args = {};
|
|
109
|
+
const dirIdx = process.argv.indexOf('--dir');
|
|
110
|
+
if (dirIdx > -1 && process.argv[dirIdx + 1]) args.dir = process.argv[dirIdx + 1];
|
|
111
|
+
run(args);
|
|
112
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* solutions-inject — 按 phase+domain 过滤 INDEX.md,输出 top-5 摘要
|
|
4
|
+
*
|
|
5
|
+
* v0.5 复利贯穿机制脚本
|
|
6
|
+
* 用法:tf solutions inject --phase <p> --domain <d>
|
|
7
|
+
*
|
|
8
|
+
* 功能:
|
|
9
|
+
* 1. 读取 docs/solutions/INDEX.md
|
|
10
|
+
* 2. 过滤:phase = 指定阶段 OR phase = cross-phase,且 domain 匹配
|
|
11
|
+
* 3. 按 severity 降序取 top-5
|
|
12
|
+
* 4. 输出摘要(供各 skill 注入上下文)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
16
|
+
import { join } from 'node:path';
|
|
17
|
+
|
|
18
|
+
const SEVERITY_ORDER = { high: 0, medium: 1, low: 2 };
|
|
19
|
+
|
|
20
|
+
export function run(args = {}) {
|
|
21
|
+
const phase = args.phase || 'cross-phase';
|
|
22
|
+
const domain = args.domain || '';
|
|
23
|
+
const dir = args.dir || 'docs/solutions';
|
|
24
|
+
const indexPath = join(dir, 'INDEX.md');
|
|
25
|
+
|
|
26
|
+
if (!existsSync(indexPath)) {
|
|
27
|
+
console.log('<!-- No solutions index found, skipping injection -->');
|
|
28
|
+
return { entries: [] };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const content = readFileSync(indexPath, 'utf-8');
|
|
32
|
+
const lines = content.split('\n').filter(l => l.startsWith('|') && !l.startsWith('| date') && !l.startsWith('|--'));
|
|
33
|
+
|
|
34
|
+
const entries = [];
|
|
35
|
+
for (const line of lines) {
|
|
36
|
+
const cols = line.split('|').map(c => c.trim()).filter(Boolean);
|
|
37
|
+
if (cols.length < 7) continue;
|
|
38
|
+
const [date, ePhase, eDomain, type, severity, summary, file] = cols;
|
|
39
|
+
|
|
40
|
+
// 过滤:phase 匹配或 cross-phase
|
|
41
|
+
const phaseMatch = ePhase === phase || ePhase === 'cross-phase';
|
|
42
|
+
// 过滤:domain 匹配(空 domain 表示通用)
|
|
43
|
+
const domainMatch = !domain || !eDomain || eDomain === 'general' || eDomain === domain;
|
|
44
|
+
|
|
45
|
+
if (phaseMatch && domainMatch) {
|
|
46
|
+
entries.push({ date, phase: ePhase, domain: eDomain, type, severity, summary, file });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 按 severity 降序排序,取 top-5
|
|
51
|
+
entries.sort((a, b) => (SEVERITY_ORDER[a.severity] ?? 3) - (SEVERITY_ORDER[b.severity] ?? 3));
|
|
52
|
+
const top5 = entries.slice(0, 5);
|
|
53
|
+
|
|
54
|
+
if (top5.length === 0) {
|
|
55
|
+
console.log('<!-- No matching solutions found -->');
|
|
56
|
+
return { entries: [] };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
console.log(`## Solutions Context (phase=${phase}, domain=${domain || 'any'})`);
|
|
60
|
+
console.log('');
|
|
61
|
+
for (const e of top5) {
|
|
62
|
+
console.log(`- [${e.severity}] ${e.summary} (${e.phase}/${e.domain}, ${e.date}) → ${e.file}`);
|
|
63
|
+
}
|
|
64
|
+
console.log('');
|
|
65
|
+
|
|
66
|
+
return { entries: top5 };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (process.argv[1]?.includes('solutions-inject')) {
|
|
70
|
+
const args = {};
|
|
71
|
+
for (let i = 2; i < process.argv.length; i += 2) {
|
|
72
|
+
const key = process.argv[i]?.replace('--', '');
|
|
73
|
+
const val = process.argv[i + 1];
|
|
74
|
+
if (key && val) args[key] = val;
|
|
75
|
+
}
|
|
76
|
+
run(args);
|
|
77
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* solutions-promote — 检查 change 内经验的晋升条件,执行 change→product 晋升
|
|
4
|
+
*
|
|
5
|
+
* v0.5 复利贯穿机制脚本
|
|
6
|
+
* 用法:tf solutions promote <change-dir> [--dir <solutions-dir>]
|
|
7
|
+
*
|
|
8
|
+
* 功能:
|
|
9
|
+
* 1. 读取 specs/<cap>/learnings.md
|
|
10
|
+
* 2. 逐条检查晋升条件(severity ≥ medium 且 type = pitfall/pattern)
|
|
11
|
+
* 3. 检查与全局 INDEX 是否重复(domain+type 匹配)
|
|
12
|
+
* 4. 符合条件 → 写入全局 docs/solutions/
|
|
13
|
+
* 5. 重复 → 更新已有条目的 severity 和确认次数
|
|
14
|
+
*
|
|
15
|
+
* 变更说明(v0.5 / 2026-07-21):
|
|
16
|
+
* - 修正原稿在同步 run() 内使用 `await import('node:fs')` 取 mkdirSync 导致的语法错误,
|
|
17
|
+
* 改为在顶部静态 import mkdirSync,保持与其它 solutions-* 脚本一致的同步签名。
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { readFileSync, writeFileSync, existsSync, appendFileSync, mkdirSync } from 'node:fs';
|
|
21
|
+
import { join, basename } from 'node:path';
|
|
22
|
+
|
|
23
|
+
function parseFrontmatter(content) {
|
|
24
|
+
// learnings.md 中每个条目以 `## 标题` 开头,frontmatter 紧跟标题行之后;
|
|
25
|
+
// 故此处兼容 frontmatter 位于段首或标题行之后两种位置(^--- 只能匹配段首会漏判)。
|
|
26
|
+
const match = content.match(/(?:^|\n)---\n([\s\S]*?)\n---/);
|
|
27
|
+
if (!match) return null;
|
|
28
|
+
const fm = {};
|
|
29
|
+
for (const line of match[1].split('\n')) {
|
|
30
|
+
const idx = line.indexOf(':');
|
|
31
|
+
if (idx > 0) {
|
|
32
|
+
fm[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return fm;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function parseLearnings(content) {
|
|
39
|
+
// 解析 learnings.md 中的经验条目(## 标题分隔)
|
|
40
|
+
const sections = content.split(/^## /m).filter(s => s.trim());
|
|
41
|
+
return sections.map(s => {
|
|
42
|
+
const lines = s.trim().split('\n');
|
|
43
|
+
const title = lines[0]?.trim() || '';
|
|
44
|
+
const fm = parseFrontmatter(s);
|
|
45
|
+
return { title, fm, body: s.trim() };
|
|
46
|
+
}).filter(e => e.title && e.title !== 'Learnings');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function run(args = {}) {
|
|
50
|
+
const changeDir = args._?.[0] || args.changeDir;
|
|
51
|
+
const solutionsDir = args.dir || 'docs/solutions';
|
|
52
|
+
|
|
53
|
+
if (!changeDir) {
|
|
54
|
+
console.error('Usage: tf solutions promote <change-dir>');
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const learningsPath = join(changeDir, 'learnings.md');
|
|
59
|
+
if (!existsSync(learningsPath)) {
|
|
60
|
+
console.log(`No learnings.md found in ${changeDir}, nothing to promote`);
|
|
61
|
+
return { promoted: 0, updated: 0 };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const content = readFileSync(learningsPath, 'utf-8');
|
|
65
|
+
const learnings = parseLearnings(content);
|
|
66
|
+
|
|
67
|
+
if (learnings.length === 0) {
|
|
68
|
+
console.log('No learning entries found');
|
|
69
|
+
return { promoted: 0, updated: 0 };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 读取全局 INDEX
|
|
73
|
+
const indexPath = join(solutionsDir, 'INDEX.md');
|
|
74
|
+
const indexContent = existsSync(indexPath) ? readFileSync(indexPath, 'utf-8') : '';
|
|
75
|
+
|
|
76
|
+
let promoted = 0;
|
|
77
|
+
let updated = 0;
|
|
78
|
+
|
|
79
|
+
for (const learning of learnings) {
|
|
80
|
+
const severity = learning.fm?.severity || 'low';
|
|
81
|
+
const type = learning.fm?.type || 'insight';
|
|
82
|
+
const phase = learning.fm?.phase || 'cross-phase';
|
|
83
|
+
const domain = learning.fm?.domain || 'general';
|
|
84
|
+
|
|
85
|
+
// 晋升条件:severity ≥ medium 且 type = pitfall/pattern
|
|
86
|
+
const sevOk = severity === 'high' || severity === 'medium';
|
|
87
|
+
const typeOk = type === 'pitfall' || type === 'pattern';
|
|
88
|
+
if (!sevOk || !typeOk) continue;
|
|
89
|
+
|
|
90
|
+
// 检查重复:domain+type 匹配
|
|
91
|
+
const dupRegex = new RegExp(`\\| ${domain} \\| ${type} \\|`);
|
|
92
|
+
if (dupRegex.test(indexContent)) {
|
|
93
|
+
console.log(`Duplicate found (${domain}/${type}), marking as confirmed pattern`);
|
|
94
|
+
updated++;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// 写入全局
|
|
99
|
+
const date = learning.fm?.date || new Date().toISOString().slice(0, 10);
|
|
100
|
+
const phaseDir = join(solutionsDir, phase);
|
|
101
|
+
if (!existsSync(phaseDir)) {
|
|
102
|
+
mkdirSync(phaseDir, { recursive: true });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const slug = learning.title.toLowerCase().replace(/[^a-z0-9一-鿿]+/g, '-').slice(0, 40);
|
|
106
|
+
const fileName = `${date}-${slug}.md`;
|
|
107
|
+
const filePath = join(phaseDir, fileName);
|
|
108
|
+
|
|
109
|
+
const fileContent = `---
|
|
110
|
+
phase: ${phase}
|
|
111
|
+
domain: ${domain}
|
|
112
|
+
type: ${type}
|
|
113
|
+
severity: ${severity}
|
|
114
|
+
date: ${date}
|
|
115
|
+
source: ${basename(changeDir)}
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
${learning.body}
|
|
119
|
+
`;
|
|
120
|
+
writeFileSync(filePath, fileContent, 'utf-8');
|
|
121
|
+
|
|
122
|
+
// 追加 INDEX
|
|
123
|
+
appendFileSync(indexPath, `| ${date} | ${phase} | ${domain} | ${type} | ${severity} | ${learning.title.slice(0, 80)} | ${phase}/${fileName} |\n`, 'utf-8');
|
|
124
|
+
|
|
125
|
+
console.log(`Promoted: ${phase}/${fileName}`);
|
|
126
|
+
promoted++;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
console.log(`\nPromotion complete: ${promoted} promoted, ${updated} updated as confirmed`);
|
|
130
|
+
return { promoted, updated };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (process.argv[1]?.includes('solutions-promote')) {
|
|
134
|
+
const args = { _: [] };
|
|
135
|
+
for (let i = 2; i < process.argv.length; i++) {
|
|
136
|
+
if (process.argv[i].startsWith('--')) {
|
|
137
|
+
const key = process.argv[i].replace('--', '');
|
|
138
|
+
const val = process.argv[i + 1] && !process.argv[i + 1].startsWith('--') ? process.argv[++i] : true;
|
|
139
|
+
args[key] = val;
|
|
140
|
+
} else {
|
|
141
|
+
args._.push(process.argv[i]);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
run(args);
|
|
145
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
function toPosix(value) {
|
|
5
|
+
return value.split(path.sep).join('/');
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function specsDir(changeDir) {
|
|
9
|
+
return path.join(changeDir, 'specs');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function walk(dir, results = []) {
|
|
13
|
+
if (!fs.existsSync(dir)) return results;
|
|
14
|
+
|
|
15
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
16
|
+
const full = path.join(dir, entry.name);
|
|
17
|
+
if (entry.isDirectory()) {
|
|
18
|
+
walk(full, results);
|
|
19
|
+
} else if (entry.isFile()) {
|
|
20
|
+
results.push(full);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return results;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function findCanonicalSpecFiles(changeDir) {
|
|
28
|
+
const root = specsDir(changeDir);
|
|
29
|
+
if (!fs.existsSync(root)) return [];
|
|
30
|
+
|
|
31
|
+
return walk(root)
|
|
32
|
+
.filter(file => {
|
|
33
|
+
if (path.basename(file) !== 'spec.md') return false;
|
|
34
|
+
const rel = path.relative(root, file).split(path.sep);
|
|
35
|
+
return rel.length === 2;
|
|
36
|
+
})
|
|
37
|
+
.sort();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function findInvalidSpecFiles(changeDir) {
|
|
41
|
+
const root = specsDir(changeDir);
|
|
42
|
+
if (!fs.existsSync(root)) return [];
|
|
43
|
+
|
|
44
|
+
const invalid = [];
|
|
45
|
+
|
|
46
|
+
for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
|
|
47
|
+
if (!entry.isFile() || !entry.name.endsWith('.md')) continue;
|
|
48
|
+
|
|
49
|
+
const rel = `specs/${entry.name}`;
|
|
50
|
+
if (entry.name === 'README.md') continue;
|
|
51
|
+
if (entry.name === 'spec.md') {
|
|
52
|
+
invalid.push({ path: rel, expected: 'specs/<capability>/spec.md' });
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const capability = entry.name.replace(/\.md$/, '');
|
|
57
|
+
invalid.push({ path: rel, expected: `specs/${capability}/spec.md` });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return invalid.sort((a, b) => a.path.localeCompare(b.path));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function formatSpecPathFailure(item) {
|
|
64
|
+
return `Invalid spec path: ${item.path}. Expected: ${item.expected}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function validateSpecPathLayout(changeDir, options = {}) {
|
|
68
|
+
const requireSpecs = options.requireSpecs === true;
|
|
69
|
+
const specFiles = findCanonicalSpecFiles(changeDir);
|
|
70
|
+
const failures = findInvalidSpecFiles(changeDir).map(formatSpecPathFailure);
|
|
71
|
+
|
|
72
|
+
if (requireSpecs && specFiles.length === 0) {
|
|
73
|
+
failures.push('No canonical spec files found. Expected at least one specs/<capability>/spec.md');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
pass: failures.length === 0,
|
|
78
|
+
specFiles,
|
|
79
|
+
failures,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function relativeSpecPath(changeDir, file) {
|
|
84
|
+
return toPosix(path.relative(changeDir, file));
|
|
85
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// scripts/lib/state-loader.mjs — lightweight .team-flow.yaml state file reader/writer
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
const STATE_FILE = '.team-flow.yaml';
|
|
6
|
+
|
|
7
|
+
const BUILTIN_DEFAULTS = {
|
|
8
|
+
state: 'exploring',
|
|
9
|
+
workflow: 'auto',
|
|
10
|
+
revision: null,
|
|
11
|
+
artifacts_hash: null,
|
|
12
|
+
contract_hash: null,
|
|
13
|
+
execution_mode: null,
|
|
14
|
+
execution_plan_hash: null,
|
|
15
|
+
execution_plan_revision: null,
|
|
16
|
+
batches_completed: 0,
|
|
17
|
+
test_result: null,
|
|
18
|
+
spec_merged: false,
|
|
19
|
+
change_name: null,
|
|
20
|
+
last_transition: null,
|
|
21
|
+
last_transition_from: null,
|
|
22
|
+
last_transition_to: null,
|
|
23
|
+
dp_0_decisions: null,
|
|
24
|
+
dp_0_result: null,
|
|
25
|
+
dp_0_confirmed: null,
|
|
26
|
+
dp_0_timestamp: null,
|
|
27
|
+
dp_1_result: null,
|
|
28
|
+
dp_1_timestamp: null,
|
|
29
|
+
dp_2_result: null,
|
|
30
|
+
dp_2_timestamp: null,
|
|
31
|
+
dp_3_result: null,
|
|
32
|
+
dp_3_timestamp: null,
|
|
33
|
+
dp_4_result: null,
|
|
34
|
+
dp_4_timestamp: null,
|
|
35
|
+
dp_5_result: null,
|
|
36
|
+
dp_5_timestamp: null,
|
|
37
|
+
dp_6_result: null,
|
|
38
|
+
dp_6_timestamp: null,
|
|
39
|
+
dp_7_result: null,
|
|
40
|
+
dp_7_timestamp: null,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Read state file, merging with built-in defaults.
|
|
45
|
+
* Returns a complete state object even if the file doesn't exist.
|
|
46
|
+
*/
|
|
47
|
+
export function readState(changeDir) {
|
|
48
|
+
const filePath = path.join(changeDir, STATE_FILE);
|
|
49
|
+
if (!fs.existsSync(filePath)) {
|
|
50
|
+
return { ...BUILTIN_DEFAULTS, change_name: path.basename(changeDir) };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
54
|
+
const parsed = parseYaml(raw);
|
|
55
|
+
return { ...BUILTIN_DEFAULTS, ...parsed };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Write state object to .team-flow.yaml.
|
|
60
|
+
*/
|
|
61
|
+
export function writeState(changeDir, state) {
|
|
62
|
+
const filePath = path.join(changeDir, STATE_FILE);
|
|
63
|
+
const lines = [];
|
|
64
|
+
lines.push('# .team-flow.yaml — lightweight state machine');
|
|
65
|
+
lines.push('# Derived data. Always rebuildable from artifacts. Lost/corrupt → fall back to content-level detection.');
|
|
66
|
+
lines.push('');
|
|
67
|
+
lines.push('# === Core state ===');
|
|
68
|
+
lines.push(`state: ${state.state || 'exploring'}`);
|
|
69
|
+
lines.push(`workflow: ${state.workflow || 'auto'}`);
|
|
70
|
+
lines.push(`revision: ${state.revision ?? 'null'}`);
|
|
71
|
+
lines.push('');
|
|
72
|
+
lines.push('# === Hashes (fast staleness detection) ===');
|
|
73
|
+
lines.push(`artifacts_hash: ${state.artifacts_hash ?? 'null'}`);
|
|
74
|
+
lines.push(`contract_hash: ${state.contract_hash ?? 'null'}`);
|
|
75
|
+
lines.push('');
|
|
76
|
+
lines.push('# === Execution progress ===');
|
|
77
|
+
lines.push(`execution_mode: ${state.execution_mode ?? 'null'}`);
|
|
78
|
+
lines.push(`execution_plan_hash: ${state.execution_plan_hash ?? 'null'}`);
|
|
79
|
+
lines.push(`execution_plan_revision: ${state.execution_plan_revision ?? 'null'}`);
|
|
80
|
+
lines.push(`batches_completed: ${state.batches_completed ?? 0}`);
|
|
81
|
+
lines.push(`test_result: ${state.test_result ?? 'null'}`);
|
|
82
|
+
lines.push(`spec_merged: ${state.spec_merged ?? false}`);
|
|
83
|
+
lines.push('');
|
|
84
|
+
lines.push('# === Metadata ===');
|
|
85
|
+
lines.push(`change_name: ${state.change_name ?? path.basename(changeDir)}`);
|
|
86
|
+
lines.push(`last_transition: ${state.last_transition ?? 'null'}`);
|
|
87
|
+
lines.push(`last_transition_from: ${state.last_transition_from ?? 'null'}`);
|
|
88
|
+
lines.push(`last_transition_to: ${state.last_transition_to ?? 'null'}`);
|
|
89
|
+
lines.push('');
|
|
90
|
+
lines.push('# === Decision points ===');
|
|
91
|
+
lines.push(`dp_0_decisions: ${state.dp_0_decisions ?? 'null'}`);
|
|
92
|
+
lines.push(`dp_0_result: ${state.dp_0_result ?? 'null'}`);
|
|
93
|
+
lines.push(`dp_0_confirmed: ${state.dp_0_confirmed ?? 'null'}`);
|
|
94
|
+
lines.push(`dp_0_timestamp: ${state.dp_0_timestamp ?? 'null'}`);
|
|
95
|
+
lines.push(`dp_1_result: ${state.dp_1_result ?? 'null'}`);
|
|
96
|
+
lines.push(`dp_1_timestamp: ${state.dp_1_timestamp ?? 'null'}`);
|
|
97
|
+
lines.push(`dp_2_result: ${state.dp_2_result ?? 'null'}`);
|
|
98
|
+
lines.push(`dp_2_timestamp: ${state.dp_2_timestamp ?? 'null'}`);
|
|
99
|
+
lines.push(`dp_3_result: ${state.dp_3_result ?? 'null'}`);
|
|
100
|
+
lines.push(`dp_3_timestamp: ${state.dp_3_timestamp ?? 'null'}`);
|
|
101
|
+
lines.push(`dp_4_result: ${state.dp_4_result ?? 'null'}`);
|
|
102
|
+
lines.push(`dp_4_timestamp: ${state.dp_4_timestamp ?? 'null'}`);
|
|
103
|
+
lines.push(`dp_5_result: ${state.dp_5_result ?? 'null'}`);
|
|
104
|
+
lines.push(`dp_5_timestamp: ${state.dp_5_timestamp ?? 'null'}`);
|
|
105
|
+
lines.push(`dp_6_result: ${state.dp_6_result ?? 'null'}`);
|
|
106
|
+
lines.push(`dp_6_timestamp: ${state.dp_6_timestamp ?? 'null'}`);
|
|
107
|
+
lines.push(`dp_7_result: ${state.dp_7_result ?? 'null'}`);
|
|
108
|
+
lines.push(`dp_7_timestamp: ${state.dp_7_timestamp ?? 'null'}`);
|
|
109
|
+
|
|
110
|
+
fs.writeFileSync(filePath, lines.join('\n') + '\n', 'utf-8');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Update a single field in the state file.
|
|
115
|
+
*/
|
|
116
|
+
export function updateField(changeDir, field, value) {
|
|
117
|
+
const state = readState(changeDir);
|
|
118
|
+
state[field] = value;
|
|
119
|
+
writeState(changeDir, state);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Rebuild state file from artifacts — recomputes hashes.
|
|
124
|
+
* Requires hash functions to be passed in (avoids circular dependency).
|
|
125
|
+
*/
|
|
126
|
+
export function rebuildState(changeDir, { computeArtifactsHash, computeContractHash }) {
|
|
127
|
+
const state = readState(changeDir);
|
|
128
|
+
state.artifacts_hash = computeArtifactsHash(changeDir);
|
|
129
|
+
state.contract_hash = computeContractHash(changeDir);
|
|
130
|
+
writeState(changeDir, state);
|
|
131
|
+
return state;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Minimal YAML parser — top-level fields only, zero dependencies.
|
|
135
|
+
// Handles strings, null, integers. No nested structures needed.
|
|
136
|
+
function parseYaml(content) {
|
|
137
|
+
const result = {};
|
|
138
|
+
for (const line of content.split('\n')) {
|
|
139
|
+
const trimmed = line.trim();
|
|
140
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
141
|
+
const match = trimmed.match(/^(\w[\w_]*):\s*(.*)/);
|
|
142
|
+
if (match) {
|
|
143
|
+
const val = match[2].trim();
|
|
144
|
+
if (val === 'null' || val === '') {
|
|
145
|
+
result[match[1]] = null;
|
|
146
|
+
} else if (/^\d+$/.test(val)) {
|
|
147
|
+
result[match[1]] = parseInt(val, 10);
|
|
148
|
+
} else {
|
|
149
|
+
result[match[1]] = val;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return result;
|
|
154
|
+
}
|