@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.
Files changed (405) hide show
  1. package/.agents/plugins/marketplace.json +20 -0
  2. package/.agents/skills +1 -0
  3. package/.claude/always/phase-guard.md +3 -0
  4. package/.claude-plugin/marketplace.json +20 -0
  5. package/.claude-plugin/plugin.json +31 -0
  6. package/.codex-plugin/plugin.json +45 -0
  7. package/.codexignore +6 -0
  8. package/.cursor-plugin/marketplace.json +17 -0
  9. package/.cursor-plugin/plugin.json +31 -0
  10. package/.github/copilot-instructions.md +15 -0
  11. package/.github/dependabot.yml +10 -0
  12. package/.github/plugin/marketplace.json +42 -0
  13. package/.github/workflows/ci.yml +98 -0
  14. package/.github/workflows/hol-plugin-scanner.yml +17 -0
  15. package/.opencode/INSTALL.md +61 -0
  16. package/.opencode/plugins/team-flow.js +70 -0
  17. package/AGENTS.md +280 -0
  18. package/CHANGELOG.md +635 -0
  19. package/CONTRIBUTING.md +115 -0
  20. package/GEMINI.md +14 -0
  21. package/HANDOFF.md +219 -0
  22. package/INSTALL.md +810 -0
  23. package/LICENSE +21 -0
  24. package/README.md +185 -0
  25. package/SECURITY.md +37 -0
  26. package/agents/bug-investigator.md +180 -0
  27. package/agents/change-split-auditor.md +257 -0
  28. package/agents/code-reviewer.md +171 -0
  29. package/agents/cross-change-consistency-checker.md +215 -0
  30. package/agents/prd-completeness-reviewer.md +197 -0
  31. package/agents/prototype-builder.md +230 -0
  32. package/agents/prototype-env-scout.md +156 -0
  33. package/agents/prototype-reviewer.md +226 -0
  34. package/assets/icon.svg +6 -0
  35. package/dist/index.d.ts +13 -0
  36. package/dist/index.js +5 -0
  37. package/dist/parsing/change-parser.d.ts +12 -0
  38. package/dist/parsing/change-parser.js +54 -0
  39. package/dist/parsing/requirement-blocks.d.ts +31 -0
  40. package/dist/parsing/requirement-blocks.js +192 -0
  41. package/dist/schema/base.d.ts +7 -0
  42. package/dist/schema/base.js +1 -0
  43. package/dist/schema/change.d.ts +25 -0
  44. package/dist/schema/change.js +1 -0
  45. package/dist/schema/index.d.ts +3 -0
  46. package/dist/schema/index.js +1 -0
  47. package/dist/schema/spec.d.ts +11 -0
  48. package/dist/schema/spec.js +1 -0
  49. package/dist/validation/constants.d.ts +42 -0
  50. package/dist/validation/constants.js +42 -0
  51. package/dist/validation/tokenizer.d.ts +17 -0
  52. package/dist/validation/tokenizer.js +173 -0
  53. package/dist/validation/types.d.ts +40 -0
  54. package/dist/validation/types.js +1 -0
  55. package/dist/validation/validator.d.ts +21 -0
  56. package/dist/validation/validator.js +489 -0
  57. package/docs/README_en.md +358 -0
  58. package/docs/artifact-contract.md +96 -0
  59. package/docs/decision-points.md +88 -0
  60. package/docs/e2e-integration-design.md +207 -0
  61. package/docs/examples/README.md +25 -0
  62. package/docs/examples/add-dark-mode/README.md +13 -0
  63. package/docs/examples/add-dark-mode/design.md +49 -0
  64. package/docs/examples/add-dark-mode/execution-contract.md +56 -0
  65. package/docs/examples/add-dark-mode/proposal.md +46 -0
  66. package/docs/examples/add-dark-mode/specs/ui-theme/spec.md +35 -0
  67. package/docs/examples/add-dark-mode/tasks.md +26 -0
  68. package/docs/examples/refactor-auth-boundary/README.md +15 -0
  69. package/docs/examples/refactor-auth-boundary/design.md +41 -0
  70. package/docs/examples/refactor-auth-boundary/execution-contract.md +61 -0
  71. package/docs/examples/refactor-auth-boundary/proposal.md +47 -0
  72. package/docs/examples/refactor-auth-boundary/specs/auth-boundary/spec.md +45 -0
  73. package/docs/examples/refactor-auth-boundary/tasks.md +25 -0
  74. package/docs/plans/2026-07-07-fix-batch-design.md +68 -0
  75. package/docs/plans/2026-07-20-001-refactor-skill-collaboration-protocol-plan.md +128 -0
  76. package/docs/platform-matrix.md +45 -0
  77. package/docs/prototype-design-research.md +206 -0
  78. package/docs/release-checklist.md +96 -0
  79. package/docs/showcase.html +699 -0
  80. package/docs/skill-rename-v0.8.0.md +49 -0
  81. package/docs/state-machine.md +162 -0
  82. package/gemini-extension.json +6 -0
  83. package/hooks/hooks-cursor.json +8 -0
  84. package/hooks/hooks.json +26 -0
  85. package/hooks/pre-tool-use-guard +116 -0
  86. package/hooks/session-start +14 -0
  87. package/llms.txt +51 -0
  88. package/package.json +48 -0
  89. package/plugin.json +27 -0
  90. package/prd/v1/plan.md +443 -0
  91. package/prd/v1/prd.md +580 -0
  92. package/scripts/check-update.mjs +91 -0
  93. package/scripts/check-version-consistency.mjs +345 -0
  94. package/scripts/ensure-branch.mjs +71 -0
  95. package/scripts/get-config +31 -0
  96. package/scripts/guard/checks/artifacts-exist.mjs +36 -0
  97. package/scripts/guard/checks/contract-current.mjs +33 -0
  98. package/scripts/guard/checks/contract-fresh.mjs +17 -0
  99. package/scripts/guard/checks/dp-gate-passed.mjs +35 -0
  100. package/scripts/guard/checks/dp3-approved.mjs +22 -0
  101. package/scripts/guard/checks/execution-plan-ready.mjs +40 -0
  102. package/scripts/guard/checks/execution-reviews-passed.mjs +36 -0
  103. package/scripts/guard/checks/schema-valid.mjs +63 -0
  104. package/scripts/guard/checks/specs-merged.mjs +51 -0
  105. package/scripts/guard/checks/tasks-complete.mjs +31 -0
  106. package/scripts/guard/checks/tests-passing.mjs +31 -0
  107. package/scripts/guard/design-token-guard.mjs +237 -0
  108. package/scripts/guard/guard.mjs +205 -0
  109. package/scripts/infer-workflow.mjs +136 -0
  110. package/scripts/install-amazon-q.mjs +9 -0
  111. package/scripts/install-cline.mjs +9 -0
  112. package/scripts/install-continue.mjs +9 -0
  113. package/scripts/install-cursor.mjs +265 -0
  114. package/scripts/install-git-hooks.mjs +82 -0
  115. package/scripts/install-kiro.mjs +9 -0
  116. package/scripts/install-pi.mjs +9 -0
  117. package/scripts/install-qoder.mjs +9 -0
  118. package/scripts/install-qwen.mjs +9 -0
  119. package/scripts/install-roocode.mjs +9 -0
  120. package/scripts/install-windsurf.mjs +9 -0
  121. package/scripts/install-zcode.mjs +265 -0
  122. package/scripts/lib/cmd-audit.mjs +127 -0
  123. package/scripts/lib/cmd-checkpoint.mjs +118 -0
  124. package/scripts/lib/cmd-config.mjs +88 -0
  125. package/scripts/lib/cmd-doctor.mjs +267 -0
  126. package/scripts/lib/cmd-execution.mjs +206 -0
  127. package/scripts/lib/cmd-handoff.mjs +94 -0
  128. package/scripts/lib/cmd-inject.mjs +310 -0
  129. package/scripts/lib/cmd-install-amazon-q.mjs +11 -0
  130. package/scripts/lib/cmd-install-cline.mjs +11 -0
  131. package/scripts/lib/cmd-install-continue.mjs +11 -0
  132. package/scripts/lib/cmd-install-cursor.mjs +14 -0
  133. package/scripts/lib/cmd-install-kiro.mjs +11 -0
  134. package/scripts/lib/cmd-install-pi.mjs +11 -0
  135. package/scripts/lib/cmd-install-qoder.mjs +11 -0
  136. package/scripts/lib/cmd-install-qwen.mjs +11 -0
  137. package/scripts/lib/cmd-install-roocode.mjs +11 -0
  138. package/scripts/lib/cmd-install-windsurf.mjs +11 -0
  139. package/scripts/lib/cmd-install-workbuddy.mjs +359 -0
  140. package/scripts/lib/cmd-install-zcode.mjs +14 -0
  141. package/scripts/lib/cmd-isolate.mjs +37 -0
  142. package/scripts/lib/cmd-list.mjs +71 -0
  143. package/scripts/lib/cmd-runtime.mjs +85 -0
  144. package/scripts/lib/cmd-solutions.mjs +71 -0
  145. package/scripts/lib/cmd-state.mjs +231 -0
  146. package/scripts/lib/cmd-sync.mjs +102 -0
  147. package/scripts/lib/cmd-validate.mjs +105 -0
  148. package/scripts/lib/cmd-version.mjs +148 -0
  149. package/scripts/lib/config-loader.mjs +107 -0
  150. package/scripts/lib/execution-plan.mjs +492 -0
  151. package/scripts/lib/execution-recommendation.mjs +221 -0
  152. package/scripts/lib/hash.mjs +74 -0
  153. package/scripts/lib/install.mjs +267 -0
  154. package/scripts/lib/platform-runtime-inventory.mjs +27 -0
  155. package/scripts/lib/platforms.mjs +165 -0
  156. package/scripts/lib/prototype-sync.mjs +192 -0
  157. package/scripts/lib/sdd-overlay.mjs +262 -0
  158. package/scripts/lib/shell-quote.mjs +4 -0
  159. package/scripts/lib/solutions-capture.mjs +102 -0
  160. package/scripts/lib/solutions-index-gen.mjs +112 -0
  161. package/scripts/lib/solutions-inject.mjs +77 -0
  162. package/scripts/lib/solutions-promote.mjs +145 -0
  163. package/scripts/lib/spec-paths.mjs +85 -0
  164. package/scripts/lib/state-loader.mjs +154 -0
  165. package/scripts/lint/lint-skills.mjs +149 -0
  166. package/scripts/lint/rules/behavior-consistency.mjs +56 -0
  167. package/scripts/lint/rules/dp-trigger-points.mjs +60 -0
  168. package/scripts/lint/rules/exception-handling.mjs +59 -0
  169. package/scripts/lint/rules/no-contradictory-instructions.mjs +67 -0
  170. package/scripts/lint/rules/no-redundant-checks.mjs +50 -0
  171. package/scripts/lint/rules/token-rules.mjs +122 -0
  172. package/scripts/review-package +44 -0
  173. package/scripts/task-brief +40 -0
  174. package/scripts/team-flow.mjs +164 -0
  175. package/scripts/token-baseline.mjs +158 -0
  176. package/scripts/validate-artifacts +96 -0
  177. package/scripts/verify-marketplace-release.mjs +59 -0
  178. package/skills/architecture-design/SKILL.md +140 -0
  179. package/skills/architecture-design/chapters/ch01-4a-domains.md +27 -0
  180. package/skills/architecture-design/chapters/ch02-change-cascade.md +27 -0
  181. package/skills/architecture-design/chapters/ch03-architecture-outputs.md +25 -0
  182. package/skills/architecture-design/chapters/ch04-entity-to-aggregate.md +27 -0
  183. package/skills/architecture-design/chapters/ch05-cqrs.md +32 -0
  184. package/skills/architecture-design/chapters/ch06-integration.md +54 -0
  185. package/skills/architecture-design/cheatsheet.md +51 -0
  186. package/skills/architecture-design/glossary.md +23 -0
  187. package/skills/architecture-design/patterns.md +32 -0
  188. package/skills/bug-investigator/SKILL.md +77 -0
  189. package/skills/build-executor/SKILL.md +163 -0
  190. package/skills/build-executor/implementer-prompt.md +162 -0
  191. package/skills/build-executor/references/execution-modes.md +86 -0
  192. package/skills/build-executor/task-reviewer-prompt.md +195 -0
  193. package/skills/ce-brainstorm/SKILL.md +415 -0
  194. package/skills/ce-brainstorm/references/agents/slack-researcher.md +127 -0
  195. package/skills/ce-brainstorm/references/blindspot-pass.md +70 -0
  196. package/skills/ce-brainstorm/references/brainstorm-sections.md +285 -0
  197. package/skills/ce-brainstorm/references/handoff.md +168 -0
  198. package/skills/ce-brainstorm/references/html-rendering.md +634 -0
  199. package/skills/ce-brainstorm/references/markdown-rendering.md +241 -0
  200. package/skills/ce-brainstorm/references/model-tiers.md +9 -0
  201. package/skills/ce-brainstorm/references/prd-mapping.md +71 -0
  202. package/skills/ce-brainstorm/references/product-pressure-test.md +42 -0
  203. package/skills/ce-brainstorm/references/reasoning-elevation.md +63 -0
  204. package/skills/ce-brainstorm/references/settled-decisions.md +45 -0
  205. package/skills/ce-brainstorm/references/synthesis-summary.md +282 -0
  206. package/skills/ce-brainstorm/references/universal-brainstorming.md +73 -0
  207. package/skills/ce-brainstorm/references/verdict-routing.md +26 -0
  208. package/skills/ce-brainstorm/references/visual-probes.md +158 -0
  209. package/skills/ce-brainstorm/scripts/visual-probe-server.js +418 -0
  210. package/skills/ce-compound/SKILL.md +114 -0
  211. package/skills/ce-compound/assets/resolution-template.md +94 -0
  212. package/skills/ce-compound/references/agents/best-practices-researcher.md +115 -0
  213. package/skills/ce-compound/references/agents/data-integrity-guardian.md +68 -0
  214. package/skills/ce-compound/references/agents/framework-docs-researcher.md +93 -0
  215. package/skills/ce-compound/references/agents/pattern-recognition-specialist.md +55 -0
  216. package/skills/ce-compound/references/agents/performance-oracle.md +108 -0
  217. package/skills/ce-compound/references/agents/security-sentinel.md +91 -0
  218. package/skills/ce-compound/references/agents/session-historian.md +83 -0
  219. package/skills/ce-compound/references/concepts-vocabulary.md +78 -0
  220. package/skills/ce-compound/references/full-mode-workflow.md +413 -0
  221. package/skills/ce-compound/references/grounding-validation.md +84 -0
  222. package/skills/ce-compound/references/lightweight-mode.md +53 -0
  223. package/skills/ce-compound/references/output-standards.md +182 -0
  224. package/skills/ce-compound/references/promotion-rules.md +38 -0
  225. package/skills/ce-compound/references/schema.yaml +231 -0
  226. package/skills/ce-compound/references/three-tier-index.md +55 -0
  227. package/skills/ce-compound/references/v0.5-upgrade.md +32 -0
  228. package/skills/ce-compound/references/write-flow.md +36 -0
  229. package/skills/ce-compound/references/yaml-schema.md +118 -0
  230. package/skills/ce-compound/scripts/session-history/discover-sessions.sh +130 -0
  231. package/skills/ce-compound/scripts/session-history/extract-errors.py +254 -0
  232. package/skills/ce-compound/scripts/session-history/extract-metadata.py +456 -0
  233. package/skills/ce-compound/scripts/session-history/extract-skeleton.py +575 -0
  234. package/skills/ce-compound/scripts/validate-doc-claims.py +346 -0
  235. package/skills/ce-compound/scripts/validate-frontmatter.py +137 -0
  236. package/skills/ce-ideate/SKILL.md +402 -0
  237. package/skills/ce-ideate/references/agents/issue-intelligence-analyst.md +200 -0
  238. package/skills/ce-ideate/references/agents/learnings-researcher.md +247 -0
  239. package/skills/ce-ideate/references/agents/slack-researcher.md +127 -0
  240. package/skills/ce-ideate/references/agents/web-researcher.md +121 -0
  241. package/skills/ce-ideate/references/divergent-ideation.md +89 -0
  242. package/skills/ce-ideate/references/html-rendering.md +634 -0
  243. package/skills/ce-ideate/references/ideation-sections.md +191 -0
  244. package/skills/ce-ideate/references/markdown-rendering.md +238 -0
  245. package/skills/ce-ideate/references/post-ideation-workflow.md +167 -0
  246. package/skills/ce-ideate/references/universal-ideation.md +107 -0
  247. package/skills/ce-ideate/references/web-research-cache.md +56 -0
  248. package/skills/ce-plan/SKILL.md +137 -0
  249. package/skills/ce-plan/references/agents/agent-native-planning-strategist.md +62 -0
  250. package/skills/ce-plan/references/agents/architecture-strategist.md +46 -0
  251. package/skills/ce-plan/references/agents/best-practices-researcher.md +114 -0
  252. package/skills/ce-plan/references/agents/data-integrity-guardian.md +68 -0
  253. package/skills/ce-plan/references/agents/data-migration-reviewer.md +103 -0
  254. package/skills/ce-plan/references/agents/deployment-verification-agent.md +157 -0
  255. package/skills/ce-plan/references/agents/framework-docs-researcher.md +93 -0
  256. package/skills/ce-plan/references/agents/git-history-analyzer.md +40 -0
  257. package/skills/ce-plan/references/agents/learnings-researcher.md +247 -0
  258. package/skills/ce-plan/references/agents/pattern-recognition-specialist.md +55 -0
  259. package/skills/ce-plan/references/agents/performance-oracle.md +108 -0
  260. package/skills/ce-plan/references/agents/repo-research-analyst.md +258 -0
  261. package/skills/ce-plan/references/agents/security-sentinel.md +91 -0
  262. package/skills/ce-plan/references/agents/slack-researcher.md +127 -0
  263. package/skills/ce-plan/references/agents/spec-flow-analyzer.md +80 -0
  264. package/skills/ce-plan/references/agents/web-researcher.md +121 -0
  265. package/skills/ce-plan/references/approach-altitude.md +55 -0
  266. package/skills/ce-plan/references/change-splitting.md +65 -0
  267. package/skills/ce-plan/references/completion-contract.md +33 -0
  268. package/skills/ce-plan/references/core-principles.md +40 -0
  269. package/skills/ce-plan/references/deepening-workflow.md +263 -0
  270. package/skills/ce-plan/references/dependency-graph.md +36 -0
  271. package/skills/ce-plan/references/html-rendering.md +634 -0
  272. package/skills/ce-plan/references/intake-routing.md +189 -0
  273. package/skills/ce-plan/references/markdown-rendering.md +238 -0
  274. package/skills/ce-plan/references/plan-handoff.md +150 -0
  275. package/skills/ce-plan/references/plan-sections.md +427 -0
  276. package/skills/ce-plan/references/plan-structure.md +120 -0
  277. package/skills/ce-plan/references/planning-modes.md +69 -0
  278. package/skills/ce-plan/references/reasoning-elevation.md +63 -0
  279. package/skills/ce-plan/references/research-workflow.md +165 -0
  280. package/skills/ce-plan/references/review-and-write.md +127 -0
  281. package/skills/ce-plan/references/settled-decisions.md +45 -0
  282. package/skills/ce-plan/references/synthesis-summary.md +418 -0
  283. package/skills/ce-plan/references/universal-planning.md +168 -0
  284. package/skills/ce-proof/SKILL.md +346 -0
  285. package/skills/ce-strategy/SKILL.md +97 -0
  286. package/skills/ce-strategy/references/interview.md +143 -0
  287. package/skills/ce-strategy/references/strategy-template.md +89 -0
  288. package/skills/code-reviewer/SKILL.md +84 -0
  289. package/skills/code-reviewer/code-reviewer-prompt.md +195 -0
  290. package/skills/contract-builder/SKILL.md +72 -0
  291. package/skills/design-system/SKILL.md +75 -0
  292. package/skills/design-system/references/agents/design-system-architect.md +138 -0
  293. package/skills/design-system/references/creation-flow.md +170 -0
  294. package/skills/design-system/references/preview-template.html +319 -0
  295. package/skills/design-system/references/token-derivation.md +115 -0
  296. package/skills/design-system/references/variant-schema.md +60 -0
  297. package/skills/e2e/SKILL.md +56 -0
  298. package/skills/e2e/references/test-verifier.md +40 -0
  299. package/skills/need-explorer/SKILL.md +79 -0
  300. package/skills/prototype/SKILL.md +98 -0
  301. package/skills/prototype/references/agents/design-system-architect.md +122 -0
  302. package/skills/prototype/references/checklist.md +82 -0
  303. package/skills/prototype/references/craft/accessibility-baseline.md +71 -0
  304. package/skills/prototype/references/craft/anti-ai-slop.md +87 -0
  305. package/skills/prototype/references/craft/laws-of-ux.md +60 -0
  306. package/skills/prototype/references/craft/state-coverage.md +81 -0
  307. package/skills/prototype/references/craft/typography-hierarchy.md +50 -0
  308. package/skills/prototype/references/layouts.md +223 -0
  309. package/skills/prototype/references/orchestration-flow.md +101 -0
  310. package/skills/prototype/references/prototype-scaffold/assets/design-tokens.css +19 -0
  311. package/skills/prototype/references/prototype-scaffold/components/button.html +3 -0
  312. package/skills/prototype/references/prototype-scaffold/design-system.md +7 -0
  313. package/skills/prototype/references/prototype-scaffold/flow.md +11 -0
  314. package/skills/prototype/references/prototype-scaffold/index.html +20 -0
  315. package/skills/prototype/references/prototype-scaffold/pages/index.html +11 -0
  316. package/skills/prototype/references/template.html +396 -0
  317. package/skills/release-archivist/SKILL.md +166 -0
  318. package/skills/release-archivist/references/closing-procedures.md +81 -0
  319. package/skills/session-handoff/SKILL.md +110 -0
  320. package/skills/session-handoff/references/context-extraction.md +70 -0
  321. package/skills/session-handoff/references/handoff-template.md +90 -0
  322. package/skills/session-handoff/references/skill-recommendation.md +43 -0
  323. package/skills/spec-merger/SKILL.md +66 -0
  324. package/skills/spec-writer/SKILL.md +149 -0
  325. package/skills/workflow-bootstrap/SKILL.md +148 -0
  326. package/skills/workflow-bootstrap/references/agents/codebase-recon-analyst.md +121 -0
  327. package/skills/workflow-bootstrap/references/b1-reconnaissance.md +80 -0
  328. package/skills/workflow-bootstrap/scripts/recon-probe.sh +328 -0
  329. package/skills/workflow-feedback/SKILL.md +120 -0
  330. package/skills/workflow-feedback/references/category-guide.md +121 -0
  331. package/skills/workflow-feedback/references/feedback-template.md +73 -0
  332. package/skills/workflow-feedback/references/review-mode.md +92 -0
  333. package/skills/workflow-orchestrator/SKILL.md +94 -0
  334. package/skills/workflow-orchestrator/references/feedback-loops.md +56 -0
  335. package/skills/workflow-orchestrator/references/s1-path-router.md +81 -0
  336. package/skills/workflow-orchestrator/references/s2-prd-prototype-loop.md +96 -0
  337. package/skills/workflow-orchestrator/references/s3-plan-pipeline.md +50 -0
  338. package/skills/workflow-orchestrator/references/s4-split-validate.md +108 -0
  339. package/skills/workflow-orchestrator/references/s5-monitoring.md +61 -0
  340. package/skills/workflow-orchestrator/references/state-model.md +185 -0
  341. package/skills/workflow-start/SKILL.md +147 -0
  342. package/skills/workflow-start/references/routing-rules.md +155 -0
  343. package/src/index.ts +16 -0
  344. package/src/parsing/change-parser.ts +79 -0
  345. package/src/parsing/requirement-blocks.ts +244 -0
  346. package/src/schema/base.ts +8 -0
  347. package/src/schema/change.ts +29 -0
  348. package/src/schema/index.ts +3 -0
  349. package/src/schema/spec.ts +12 -0
  350. package/src/validation/constants.ts +53 -0
  351. package/src/validation/tokenizer.ts +180 -0
  352. package/src/validation/types.ts +47 -0
  353. package/src/validation/validator.ts +552 -0
  354. package/templates/abandonment-summary.md +29 -0
  355. package/templates/design.md +37 -0
  356. package/templates/execution-contract.md +99 -0
  357. package/templates/plan.md +86 -0
  358. package/templates/prd.md +468 -0
  359. package/templates/proposal.md +37 -0
  360. package/templates/spec.md +31 -0
  361. package/templates/tasks.md +47 -0
  362. package/tests/e2e.test.mjs +266 -0
  363. package/tests/lib/cmd-audit.test.mjs +202 -0
  364. package/tests/lib/cmd-checkpoint.test.mjs +98 -0
  365. package/tests/lib/cmd-config.test.mjs +101 -0
  366. package/tests/lib/cmd-doctor.test.mjs +450 -0
  367. package/tests/lib/cmd-execution.test.mjs +647 -0
  368. package/tests/lib/cmd-handoff.test.mjs +137 -0
  369. package/tests/lib/cmd-inject.test.mjs +218 -0
  370. package/tests/lib/cmd-install-workbuddy.test.mjs +113 -0
  371. package/tests/lib/cmd-install-zcode.test.mjs +42 -0
  372. package/tests/lib/cmd-isolate.test.mjs +68 -0
  373. package/tests/lib/cmd-list.test.mjs +127 -0
  374. package/tests/lib/cmd-runtime.test.mjs +83 -0
  375. package/tests/lib/cmd-state-missing.test.mjs +65 -0
  376. package/tests/lib/cmd-state.test.mjs +365 -0
  377. package/tests/lib/cmd-sync-paths.test.mjs +84 -0
  378. package/tests/lib/cmd-validate-paths.test.mjs +90 -0
  379. package/tests/lib/config-loader.test.mjs +175 -0
  380. package/tests/lib/ensure-branch.test.mjs +59 -0
  381. package/tests/lib/execution-control-plane.test.mjs +168 -0
  382. package/tests/lib/execution-plan.test.mjs +474 -0
  383. package/tests/lib/guard-specs-merged.test.mjs +108 -0
  384. package/tests/lib/guard-tests-passing.test.mjs +112 -0
  385. package/tests/lib/guard-transitions.test.mjs +262 -0
  386. package/tests/lib/guard.test.mjs +611 -0
  387. package/tests/lib/hash.test.mjs +138 -0
  388. package/tests/lib/infer-workflow.test.mjs +161 -0
  389. package/tests/lib/install-git-hooks.test.mjs +20 -0
  390. package/tests/lib/marketplace-release-docs.test.mjs +31 -0
  391. package/tests/lib/minimality-discipline.test.mjs +37 -0
  392. package/tests/lib/model-profiles-docs.test.mjs +33 -0
  393. package/tests/lib/node20-compatibility.test.mjs +73 -0
  394. package/tests/lib/node20-test-entry.test.mjs +17 -0
  395. package/tests/lib/platform-runtime-distribution.test.mjs +150 -0
  396. package/tests/lib/raw-mode-smoke.test.mjs +42 -0
  397. package/tests/lib/sdd-overlay.test.mjs +118 -0
  398. package/tests/lib/spec-paths.test.mjs +80 -0
  399. package/tests/lib/state-loader.test.mjs +251 -0
  400. package/tests/lib/token-baseline.test.mjs +72 -0
  401. package/tests/lib/token-rules.test.mjs +94 -0
  402. package/tests/lib/verify-marketplace-release.test.mjs +128 -0
  403. package/tests/tsconfig.json +12 -0
  404. package/token-baseline.json +95 -0
  405. package/tsconfig.json +14 -0
@@ -0,0 +1,647 @@
1
+ import { afterEach, beforeEach, describe, it } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { execFileSync } from 'node:child_process';
4
+ import { existsSync, mkdtempSync, mkdirSync, readFileSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
5
+ import { join } from 'node:path';
6
+ import { tmpdir } from 'node:os';
7
+
8
+ const CLI = join(process.cwd(), 'scripts/team-flow.mjs');
9
+ let changeDir;
10
+ let gitRefs;
11
+
12
+ function runSsf(args, cwd = process.cwd(), { confirmPlan = true, acknowledgePlan = true, prepareRecommendation = true } = {}) {
13
+ const isPlan = args[0] === 'execution' && ['plan', 'revise'].includes(args[1]);
14
+ let effectiveArgs = args;
15
+ if (confirmPlan && isPlan && !effectiveArgs.includes('--confirm')) effectiveArgs = [...effectiveArgs, '--confirm'];
16
+ if (confirmPlan && acknowledgePlan && isPlan && requiresAcknowledgement(effectiveArgs) && !effectiveArgs.includes('--acknowledge-recommendation')) {
17
+ effectiveArgs = [...effectiveArgs, '--acknowledge-recommendation'];
18
+ }
19
+ if (prepareRecommendation && isPlan) {
20
+ const changePath = effectiveArgs[2];
21
+ const waves = effectiveArgs.flatMap((value, index) => value === '--wave' ? ['--wave', effectiveArgs[index + 1]] : []).filter(Boolean);
22
+ try {
23
+ execFileSync(process.execPath, [CLI, 'execution', 'recommend', changePath, ...waves], {
24
+ cwd,
25
+ encoding: 'utf8',
26
+ stdio: ['ignore', 'ignore', 'pipe'],
27
+ });
28
+ } catch {
29
+ // Let the requested command report malformed arguments through the usual test helper.
30
+ }
31
+ }
32
+ try {
33
+ const stdout = execFileSync(process.execPath, [CLI, ...effectiveArgs], {
34
+ cwd,
35
+ encoding: 'utf8',
36
+ stdio: ['ignore', 'pipe', 'pipe'],
37
+ });
38
+ return { exitCode: 0, stdout, stderr: '', json: tryJson(stdout) };
39
+ } catch (error) {
40
+ return {
41
+ exitCode: error.status ?? 1,
42
+ stdout: error.stdout?.toString() ?? '',
43
+ stderr: error.stderr?.toString() ?? '',
44
+ json: tryJson(error.stdout?.toString() ?? ''),
45
+ };
46
+ }
47
+ }
48
+
49
+ function requiresAcknowledgement(args) {
50
+ const mode = args[args.indexOf('--mode') + 1];
51
+ const waves = args.flatMap((value, index) => value === '--wave' ? [args[index + 1]] : []).filter(Boolean);
52
+ const hasParallelWave = waves.some(wave => wave.split(':')[1] === 'parallel');
53
+ const plannedTaskCount = waves.reduce((count, wave) => count + (wave.split(':')[2]?.split(',').filter(Boolean).length || 0), 0);
54
+ const isSddRecommendation = hasParallelWave || waves.length > 1 || plannedTaskCount > 3;
55
+ const recommendedMode = isSddRecommendation ? 'sdd' : plannedTaskCount === 1 ? 'inline' : 'batch-inline';
56
+ return mode !== recommendedMode;
57
+ }
58
+
59
+ function runGit(directory, args) {
60
+ return execFileSync('git', args, { cwd: directory, encoding: 'utf8' }).trim();
61
+ }
62
+
63
+ function tryJson(text) {
64
+ try { return JSON.parse(text); } catch { return null; }
65
+ }
66
+
67
+ function writeChangeDirectory(directory, workflow = 'full', revision = null) {
68
+ writeFileSync(join(directory, 'proposal.md'), '## Why\nEnough context to create a controlled execution plan.\n## What Changes\n- Guard execution.\n');
69
+ writeFileSync(join(directory, 'design.md'), '# Design\n');
70
+ writeFileSync(join(directory, 'tasks.md'), '# Tasks\n\n- [ ] 1.1 First task\n- [ ] 1.2 Second task\n');
71
+ writeFileSync(join(directory, 'execution-contract.md'), '# Execution Contract\n');
72
+ mkdirSync(join(directory, 'specs', 'execution'), { recursive: true });
73
+ writeFileSync(join(directory, 'specs', 'execution', 'spec.md'), '## ADDED Requirements\n### Requirement: Guarded execution\nThe system SHALL guard execution.\n#### Scenario: Create plan\n- **WHEN** a plan is created\n- **THEN** it is persisted.\n');
74
+ writeFileSync(join(directory, '.team-flow.yaml'), [
75
+ 'state: approved-for-build',
76
+ `workflow: ${workflow}`,
77
+ revision === null ? null : `revision: ${revision}`,
78
+ '',
79
+ ].filter(line => line !== null).join('\n'));
80
+ }
81
+
82
+ function writeReviewReport(name, content = 'Review completed without blocking findings.\n') {
83
+ const reportsDir = join(changeDir, '.superpowers', 'sdd', 'reviews');
84
+ mkdirSync(reportsDir, { recursive: true });
85
+ const reportPath = join(reportsDir, name);
86
+ writeFileSync(reportPath, content);
87
+ return reportPath;
88
+ }
89
+
90
+ function initializeGitRepository(directory) {
91
+ runGit(directory, ['init', '--quiet']);
92
+ runGit(directory, ['config', 'user.email', 'tests@example.invalid']);
93
+ runGit(directory, ['config', 'user.name', 'Execution Test']);
94
+ runGit(directory, ['add', '--all']);
95
+ runGit(directory, ['commit', '--quiet', '--message', 'initial execution change']);
96
+ const base = runGit(directory, ['rev-parse', 'HEAD']);
97
+
98
+ writeFileSync(join(directory, 'git-range-marker.txt'), 'second commit\n');
99
+ runGit(directory, ['add', 'git-range-marker.txt']);
100
+ runGit(directory, ['commit', '--quiet', '--message', 'second execution change']);
101
+ const head = runGit(directory, ['rev-parse', 'HEAD']);
102
+ const divergent = runGit(directory, ['commit-tree', `${head}^{tree}`, '-m', 'independent execution change']);
103
+ return { base, head, divergent };
104
+ }
105
+
106
+ beforeEach(() => {
107
+ changeDir = mkdtempSync(join(tmpdir(), 'tf-execution-cmd-'));
108
+ writeChangeDirectory(changeDir);
109
+ gitRefs = initializeGitRepository(changeDir);
110
+ });
111
+
112
+ afterEach(() => {
113
+ rmSync(changeDir, { recursive: true, force: true });
114
+ });
115
+
116
+ describe('tf execution', () => {
117
+ it('records DP-4 and state summary after a user-confirmed recommended SDD plan', () => {
118
+ const result = runSsf(['execution', 'plan', changeDir, '--mode', 'sdd',
119
+ '--reason', 'full workflow default', '--wave', 'wave-1:parallel:1.1,1.2', '--json']);
120
+
121
+ assert.equal(result.exitCode, 0, result.stderr);
122
+ assert.equal(result.json.plan.mode, 'sdd');
123
+ assert.equal(result.json.plan.revision, 1);
124
+ assert.equal(runSsf(['state', 'get', changeDir, 'execution_mode', '--json']).json.value, 'sdd');
125
+ assert.match(runSsf(['state', 'get', changeDir, 'execution_plan_hash', '--json']).json.value, /^sha256:/);
126
+ assert.equal(runSsf(['state', 'get', changeDir, 'execution_plan_revision', '--json']).json.value, 1);
127
+ assert.match(runSsf(['state', 'get', changeDir, 'dp_4_result', '--json']).json.value, /plan revision 1/);
128
+ });
129
+
130
+ it('lists applicable execution modes and recommends one from the change evidence', () => {
131
+ const result = runSsf(['execution', 'recommend', changeDir, '--json']);
132
+
133
+ assert.equal(result.exitCode, 0, result.stderr);
134
+ assert.deepEqual(result.json.recommendation.available_modes, ['inline', 'batch-inline', 'sdd']);
135
+ assert.equal(result.json.recommendation.recommendation.mode, 'batch-inline');
136
+ assert.equal(result.json.recommendation.facts.documented_task_count, 2);
137
+ });
138
+
139
+ it('requires a current persisted recommendation before a plan can be confirmed', () => {
140
+ const result = runSsf(['execution', 'plan', changeDir, '--mode', 'sdd', '--confirm',
141
+ '--reason', 'independent implementation', '--wave', 'wave-1:parallel:1.1,1.2', '--json'], process.cwd(), {
142
+ prepareRecommendation: false,
143
+ });
144
+
145
+ assert.notEqual(result.exitCode, 0);
146
+ assert.match(result.stderr, /recommend/i);
147
+ });
148
+
149
+ it('rejects a mode that is not available for the current workflow', () => {
150
+ const workflow = runSsf(['state', 'set', changeDir, 'workflow', 'tweak']);
151
+ assert.equal(workflow.exitCode, 0, workflow.stderr);
152
+ const recommended = runSsf(['execution', 'recommend', changeDir,
153
+ '--wave', 'wave-1:serial:1.1']);
154
+ assert.equal(recommended.exitCode, 0, recommended.stderr);
155
+
156
+ const result = runSsf(['execution', 'plan', changeDir, '--mode', 'sdd', '--confirm',
157
+ '--acknowledge-recommendation', '--reason', 'operator wants delegated review',
158
+ '--wave', 'wave-1:serial:1.1'], process.cwd(), { prepareRecommendation: false });
159
+
160
+ assert.notEqual(result.exitCode, 0);
161
+ assert.match(result.stderr, /available|mode/i);
162
+ });
163
+
164
+ it('rejects a plan when the saved recommendation was for different waves', () => {
165
+ const recommended = runSsf(['execution', 'recommend', changeDir,
166
+ '--wave', 'wave-1:serial:1.1,1.2']);
167
+ assert.equal(recommended.exitCode, 0, recommended.stderr);
168
+
169
+ const result = runSsf(['execution', 'plan', changeDir, '--mode', 'sdd', '--confirm',
170
+ '--reason', 'independent implementation', '--wave', 'wave-1:parallel:1.1,1.2', '--json'], process.cwd(), {
171
+ prepareRecommendation: false,
172
+ });
173
+
174
+ assert.notEqual(result.exitCode, 0);
175
+ assert.match(result.stderr, /recommend/i);
176
+ });
177
+
178
+ it('requires a user confirmation before recording any execution plan', () => {
179
+ const result = runSsf(['execution', 'plan', changeDir, '--mode', 'sdd',
180
+ '--reason', 'independent implementation', '--wave', 'wave-1:parallel:1.1,1.2', '--json'], process.cwd(), { confirmPlan: false });
181
+
182
+ assert.notEqual(result.exitCode, 0);
183
+ assert.match(result.stderr, /confirm/i);
184
+ });
185
+
186
+ it('records an acknowledged non-recommended selection instead of an override', () => {
187
+ const result = runSsf(['execution', 'plan', changeDir, '--mode', 'inline', '--confirm',
188
+ '--acknowledge-recommendation', '--reason', 'operator will keep this focused',
189
+ '--wave', 'wave-1:serial:1.1,1.2', '--json']);
190
+
191
+ assert.equal(result.exitCode, 0, result.stderr);
192
+ assert.equal(result.json.plan.mode, 'inline');
193
+ assert.equal(result.json.plan.source, 'user-confirmed');
194
+ assert.equal(result.json.plan.selection.followed_recommendation, false);
195
+ assert.equal(result.json.plan.selection.acknowledged_non_recommendation, true);
196
+ });
197
+
198
+ it('requires acknowledgement for a non-recommended selection', () => {
199
+ const result = runSsf(['execution', 'plan', changeDir, '--mode', 'batch-inline',
200
+ '--reason', 'operator wants a batch', '--wave', 'wave-1:serial:1.1', '--json'], process.cwd(), { acknowledgePlan: false });
201
+
202
+ assert.notEqual(result.exitCode, 0);
203
+ assert.match(result.stderr, /acknowledge/i);
204
+ });
205
+
206
+ it('rejects multiline and control-character reasons before mutating the plan or state', () => {
207
+ const statePath = join(changeDir, '.team-flow.yaml');
208
+ const planPath = join(changeDir, '.superpowers', 'sdd', 'execution-plan.json');
209
+ const originalState = readFileSync(statePath, 'utf8');
210
+
211
+ for (const reason of ['approved\nexecution_mode: inline', 'approved\u0001inline']) {
212
+ const result = runSsf(['execution', 'plan', changeDir, '--mode', 'sdd', '--reason', reason,
213
+ '--wave', 'wave-1:parallel:1.1,1.2', '--json']);
214
+
215
+ assert.notEqual(result.exitCode, 0);
216
+ assert.match(result.stderr, /reason.*control|reason.*line/i);
217
+ assert.equal(readFileSync(statePath, 'utf8'), originalState);
218
+ assert.equal(existsSync(planPath), false);
219
+ }
220
+ });
221
+
222
+ it('shows the persisted execution plan', () => {
223
+ runSsf(['execution', 'plan', changeDir, '--mode', 'sdd', '--reason', 'full workflow default',
224
+ '--wave', 'wave-1:parallel:1.1,1.2']);
225
+
226
+ const result = runSsf(['execution', 'show', changeDir, '--json']);
227
+
228
+ assert.equal(result.exitCode, 0, result.stderr);
229
+ assert.equal(result.json.plan.mode, 'sdd');
230
+ assert.equal(result.json.valid, true);
231
+ assert.equal(result.json.current, true);
232
+ assert.deepEqual(result.json.waves, [{
233
+ id: 'wave-1',
234
+ strategy: 'parallel',
235
+ tasks: ['1.1', '1.2'],
236
+ depends_on: [],
237
+ eligible: true,
238
+ retryable: false,
239
+ receipt: null,
240
+ blockers: [],
241
+ }]);
242
+ });
243
+
244
+ it('keeps an overlay-relative review report current across working directories', () => {
245
+ const planned = runSsf(['execution', 'plan', changeDir, '--mode', 'sdd', '--reason', 'full workflow default',
246
+ '--wave', 'wave-1:serial:1.1']);
247
+ assert.equal(planned.exitCode, 0, planned.stderr);
248
+ writeReviewReport('wave-1.md');
249
+ const reviewCwd = mkdtempSync(join(tmpdir(), 'tf-review-cwd-'));
250
+ const showCwd = mkdtempSync(join(tmpdir(), 'tf-show-cwd-'));
251
+
252
+ try {
253
+ const reviewed = runSsf(['execution', 'review', changeDir, '--wave', 'wave-1',
254
+ '--base', gitRefs.base, '--head', gitRefs.head,
255
+ '--report', '.superpowers/sdd/reviews/wave-1.md', '--verdict', 'pass'], reviewCwd);
256
+ assert.equal(reviewed.exitCode, 0, reviewed.stderr);
257
+
258
+ const shown = runSsf(['execution', 'show', changeDir, '--json'], showCwd);
259
+ assert.equal(shown.exitCode, 0, shown.stderr);
260
+ assert.equal(shown.json.current, true);
261
+ assert.equal(shown.json.waves[0].receipt.status, 'pass');
262
+ } finally {
263
+ rmSync(reviewCwd, { recursive: true, force: true });
264
+ rmSync(showCwd, { recursive: true, force: true });
265
+ }
266
+ });
267
+
268
+ it('rejects review reports outside the change overlay', () => {
269
+ const planned = runSsf(['execution', 'plan', changeDir, '--mode', 'sdd', '--reason', 'full workflow default',
270
+ '--wave', 'wave-1:serial:1.1']);
271
+ assert.equal(planned.exitCode, 0, planned.stderr);
272
+ const outsideReport = join(changeDir, 'reports', 'wave-1.md');
273
+ mkdirSync(join(changeDir, 'reports'), { recursive: true });
274
+ writeFileSync(outsideReport, 'Review completed without blocking findings.\n');
275
+
276
+ const reviewed = runSsf(['execution', 'review', changeDir, '--wave', 'wave-1',
277
+ '--base', gitRefs.base, '--head', gitRefs.head, '--report', outsideReport, '--verdict', 'pass']);
278
+
279
+ assert.notEqual(reviewed.exitCode, 0);
280
+ assert.match(reviewed.stderr, /overlay|review/i);
281
+ });
282
+
283
+ it('rejects a report reached through a nested review-directory symlink', () => {
284
+ const planned = runSsf(['execution', 'plan', changeDir, '--mode', 'sdd', '--reason', 'full workflow default',
285
+ '--wave', 'wave-1:serial:1.1']);
286
+ assert.equal(planned.exitCode, 0, planned.stderr);
287
+ const outsideDir = join(changeDir, 'reports');
288
+ mkdirSync(outsideDir, { recursive: true });
289
+ writeFileSync(join(outsideDir, 'escaped.md'), 'Review completed without blocking findings.\n');
290
+ const reviewsDir = join(changeDir, '.superpowers', 'sdd', 'reviews');
291
+ mkdirSync(reviewsDir, { recursive: true });
292
+ symlinkSync(outsideDir, join(reviewsDir, 'linked'), 'dir');
293
+
294
+ const reviewed = runSsf(['execution', 'review', changeDir, '--wave', 'wave-1',
295
+ '--base', gitRefs.base, '--head', gitRefs.head,
296
+ '--report', '.superpowers/sdd/reviews/linked/escaped.md', '--verdict', 'pass']);
297
+
298
+ assert.notEqual(reviewed.exitCode, 0);
299
+ assert.match(reviewed.stderr, /overlay|review/i);
300
+ });
301
+
302
+ it('rejects a report when the reviews overlay root is a symlink', () => {
303
+ const planned = runSsf(['execution', 'plan', changeDir, '--mode', 'sdd', '--reason', 'full workflow default',
304
+ '--wave', 'wave-1:serial:1.1']);
305
+ assert.equal(planned.exitCode, 0, planned.stderr);
306
+ const outsideReviewsDir = mkdtempSync(join(tmpdir(), 'tf-external-reviews-'));
307
+ const reviewsDir = join(changeDir, '.superpowers', 'sdd', 'reviews');
308
+
309
+ try {
310
+ writeFileSync(join(outsideReviewsDir, 'wave-1.md'), 'Review completed without blocking findings.\n');
311
+ symlinkSync(outsideReviewsDir, reviewsDir, 'dir');
312
+
313
+ const reviewed = runSsf(['execution', 'review', changeDir, '--wave', 'wave-1',
314
+ '--base', gitRefs.base, '--head', gitRefs.head,
315
+ '--report', '.superpowers/sdd/reviews/wave-1.md', '--verdict', 'pass']);
316
+
317
+ assert.notEqual(reviewed.exitCode, 0);
318
+ assert.match(reviewed.stderr, /overlay|review|symbolic/i);
319
+ } finally {
320
+ rmSync(outsideReviewsDir, { recursive: true, force: true });
321
+ }
322
+ });
323
+
324
+ it('rejects a receipt range containing a nonexistent Git commit', () => {
325
+ const planned = runSsf(['execution', 'plan', changeDir, '--mode', 'sdd', '--reason', 'full workflow default',
326
+ '--wave', 'wave-1:serial:1.1']);
327
+ assert.equal(planned.exitCode, 0, planned.stderr);
328
+ const forgedCommit = '0000000000000000000000000000000000000001';
329
+
330
+ const reviewed = runSsf(['execution', 'review', changeDir, '--wave', 'wave-1',
331
+ '--base', forgedCommit, '--head', gitRefs.head, '--report', writeReviewReport('wave-1.md'), '--verdict', 'pass']);
332
+
333
+ assert.notEqual(reviewed.exitCode, 0);
334
+ assert.match(reviewed.stderr, /base|commit|Git/i);
335
+ });
336
+
337
+ it('rejects a receipt range whose base is not an ancestor of head', () => {
338
+ const planned = runSsf(['execution', 'plan', changeDir, '--mode', 'sdd', '--reason', 'full workflow default',
339
+ '--wave', 'wave-1:serial:1.1']);
340
+ assert.equal(planned.exitCode, 0, planned.stderr);
341
+
342
+ const reviewed = runSsf(['execution', 'review', changeDir, '--wave', 'wave-1',
343
+ '--base', gitRefs.head, '--head', gitRefs.divergent,
344
+ '--report', writeReviewReport('wave-1.md'), '--verdict', 'pass']);
345
+
346
+ assert.notEqual(reviewed.exitCode, 0);
347
+ assert.match(reviewed.stderr, /ancestor|range|base/i);
348
+ });
349
+
350
+ it('treats a persisted pass receipt with a forged Git base as unusable', () => {
351
+ const planned = runSsf(['execution', 'plan', changeDir, '--mode', 'sdd', '--reason', 'full workflow default',
352
+ '--wave', 'wave-1:serial:1.1', '--wave', 'wave-2:serial:1.2:wave-1']);
353
+ assert.equal(planned.exitCode, 0, planned.stderr);
354
+ const reviewed = runSsf(['execution', 'review', changeDir, '--wave', 'wave-1',
355
+ '--base', gitRefs.base, '--head', gitRefs.head, '--report', writeReviewReport('wave-1.md'), '--verdict', 'pass']);
356
+ assert.equal(reviewed.exitCode, 0, reviewed.stderr);
357
+
358
+ const receiptPath = join(changeDir, '.superpowers', 'sdd', 'reviews', Buffer.from('wave-1', 'utf8').toString('base64url') + '.json');
359
+ const receipt = JSON.parse(readFileSync(receiptPath, 'utf8'));
360
+ receipt.base = '0000000000000000000000000000000000000001';
361
+ writeFileSync(receiptPath, `${JSON.stringify(receipt, null, 2)}\n`);
362
+
363
+ const shown = runSsf(['execution', 'show', changeDir, '--json']);
364
+ assert.equal(shown.exitCode, 0, shown.stderr);
365
+ assert.equal(shown.json.waves[0].receipt, null);
366
+ assert.deepEqual(shown.json.waves[1].blockers, ['wave-1']);
367
+ assert.equal(shown.json.waves[1].eligible, false);
368
+ });
369
+
370
+ it('treats a persisted pass receipt with a non-ancestral Git range as unusable', () => {
371
+ const planned = runSsf(['execution', 'plan', changeDir, '--mode', 'sdd', '--reason', 'full workflow default',
372
+ '--wave', 'wave-1:serial:1.1', '--wave', 'wave-2:serial:1.2:wave-1']);
373
+ assert.equal(planned.exitCode, 0, planned.stderr);
374
+ const reviewed = runSsf(['execution', 'review', changeDir, '--wave', 'wave-1',
375
+ '--base', gitRefs.base, '--head', gitRefs.head, '--report', writeReviewReport('wave-1.md'), '--verdict', 'pass']);
376
+ assert.equal(reviewed.exitCode, 0, reviewed.stderr);
377
+
378
+ const receiptPath = join(changeDir, '.superpowers', 'sdd', 'reviews', Buffer.from('wave-1', 'utf8').toString('base64url') + '.json');
379
+ const receipt = JSON.parse(readFileSync(receiptPath, 'utf8'));
380
+ receipt.base = gitRefs.head;
381
+ receipt.head = gitRefs.divergent;
382
+ writeFileSync(receiptPath, `${JSON.stringify(receipt, null, 2)}\n`);
383
+
384
+ const shown = runSsf(['execution', 'show', changeDir, '--json']);
385
+ assert.equal(shown.exitCode, 0, shown.stderr);
386
+ assert.equal(shown.json.waves[0].receipt, null);
387
+ assert.deepEqual(shown.json.waves[1].blockers, ['wave-1']);
388
+ assert.equal(shown.json.waves[1].eligible, false);
389
+ });
390
+
391
+ it('does not show a pass receipt after its report evidence is deleted', () => {
392
+ runSsf(['execution', 'plan', changeDir, '--mode', 'sdd', '--reason', 'full workflow default',
393
+ '--wave', 'wave-1:serial:1.1']);
394
+ const reportPath = writeReviewReport('wave-1.md');
395
+ const reviewed = runSsf(['execution', 'review', changeDir, '--wave', 'wave-1',
396
+ '--base', gitRefs.base, '--head', gitRefs.head, '--report', reportPath, '--verdict', 'pass']);
397
+ assert.equal(reviewed.exitCode, 0, reviewed.stderr);
398
+
399
+ rmSync(reportPath);
400
+
401
+ const shown = runSsf(['execution', 'show', changeDir, '--json']);
402
+ assert.equal(shown.exitCode, 0, shown.stderr);
403
+ assert.equal(shown.json.waves[0].receipt, null);
404
+ assert.equal(shown.json.waves[0].eligible, true);
405
+ });
406
+
407
+ it('encodes wave dependencies and refuses review of a wave before its dependencies pass', () => {
408
+ const planned = runSsf(['execution', 'plan', changeDir, '--mode', 'sdd',
409
+ '--reason', 'full workflow default',
410
+ '--wave', 'wave-1:parallel:1.1,1.2',
411
+ '--wave', 'wave-2:serial:2.1:wave-1', '--json']);
412
+ assert.equal(planned.exitCode, 0, planned.stderr);
413
+ assert.deepEqual(planned.json.plan.waves[1].depends_on, ['wave-1']);
414
+
415
+ const shown = runSsf(['execution', 'show', changeDir, '--json']);
416
+ assert.equal(shown.exitCode, 0, shown.stderr);
417
+ assert.equal(shown.json.waves[0].eligible, true);
418
+ assert.equal(shown.json.waves[1].eligible, false);
419
+ assert.deepEqual(shown.json.waves[1].blockers, ['wave-1']);
420
+
421
+ const premature = runSsf(['execution', 'review', changeDir, '--wave', 'wave-2',
422
+ '--base', gitRefs.base, '--head', gitRefs.head, '--report', 'reports/wave-2.md', '--verdict', 'pass']);
423
+ assert.notEqual(premature.exitCode, 0);
424
+ assert.match(premature.stderr, /wave-1.*pass|dependencies/i);
425
+ });
426
+
427
+ it('rejects a plan when state mode differs from the frozen plan mode', () => {
428
+ runSsf(['execution', 'plan', changeDir, '--mode', 'sdd', '--reason', 'full workflow default',
429
+ '--wave', 'wave-1:parallel:1.1,1.2']);
430
+ const statePath = join(changeDir, '.team-flow.yaml');
431
+ writeFileSync(statePath, readFileSync(statePath, 'utf8').replace('execution_mode: sdd', 'execution_mode: inline'));
432
+
433
+ const result = runSsf(['execution', 'show', changeDir, '--json']);
434
+
435
+ assert.notEqual(result.exitCode, 0);
436
+ assert.equal(result.json.valid, false);
437
+ assert.ok(result.json.failures.includes('execution plan mode does not match state'));
438
+ });
439
+
440
+ it('increments revision when a batch-inline plan is revised to SDD', () => {
441
+ const initial = runSsf(['execution', 'plan', changeDir, '--mode', 'batch-inline', '--confirm', '--acknowledge-recommendation',
442
+ '--reason', 'operator requested a batch', '--wave', 'wave-1:serial:1.1']);
443
+ assert.equal(initial.exitCode, 0, initial.stderr);
444
+
445
+ const revised = runSsf(['execution', 'revise', changeDir, '--mode', 'sdd',
446
+ '--reason', 'risk requires independent review', '--wave', 'wave-1:parallel:1.1,1.2', '--json']);
447
+
448
+ assert.equal(revised.exitCode, 0, revised.stderr);
449
+ assert.equal(revised.json.plan.revision, 2);
450
+ assert.equal(runSsf(['state', 'get', changeDir, 'execution_plan_revision', '--json']).json.value, 2);
451
+ });
452
+
453
+ it('requires confirmation and acknowledgement when a revision differs from its recommendation', () => {
454
+ const initial = runSsf(['execution', 'plan', changeDir, '--mode', 'sdd',
455
+ '--reason', 'parallel work needs review', '--wave', 'wave-1:parallel:1.1,1.2']);
456
+ assert.equal(initial.exitCode, 0, initial.stderr);
457
+
458
+ const recommended = runSsf(['execution', 'recommend', changeDir,
459
+ '--wave', 'wave-1:serial:1.1']);
460
+ assert.equal(recommended.exitCode, 0, recommended.stderr);
461
+
462
+ const missingConfirm = runSsf(['execution', 'revise', changeDir, '--mode', 'sdd',
463
+ '--reason', 'retain SDD for the revised work', '--wave', 'wave-1:serial:1.1'], process.cwd(), {
464
+ confirmPlan: false,
465
+ prepareRecommendation: false,
466
+ });
467
+ assert.notEqual(missingConfirm.exitCode, 0);
468
+ assert.match(missingConfirm.stderr, /confirm/i);
469
+
470
+ const missingAcknowledgement = runSsf(['execution', 'revise', changeDir, '--mode', 'sdd', '--confirm',
471
+ '--reason', 'retain SDD for the revised work', '--wave', 'wave-1:serial:1.1'], process.cwd(), {
472
+ acknowledgePlan: false,
473
+ prepareRecommendation: false,
474
+ });
475
+ assert.notEqual(missingAcknowledgement.exitCode, 0);
476
+ assert.match(missingAcknowledgement.stderr, /acknowledge/i);
477
+
478
+ const revised = runSsf(['execution', 'revise', changeDir, '--mode', 'sdd', '--confirm',
479
+ '--acknowledge-recommendation', '--reason', 'retain SDD for the revised work',
480
+ '--wave', 'wave-1:serial:1.1', '--json'], process.cwd(), { prepareRecommendation: false });
481
+ assert.equal(revised.exitCode, 0, revised.stderr);
482
+ assert.equal(revised.json.plan.selection.confirmed, true);
483
+ assert.equal(revised.json.plan.selection.followed_recommendation, false);
484
+ });
485
+
486
+ it('requires a fresh recommendation after the prior plan before recording a revision', () => {
487
+ const initial = runSsf(['execution', 'plan', changeDir, '--mode', 'sdd',
488
+ '--reason', 'parallel work needs review', '--wave', 'wave-1:parallel:1.1,1.2']);
489
+ assert.equal(initial.exitCode, 0, initial.stderr);
490
+
491
+ const result = runSsf(['execution', 'revise', changeDir, '--mode', 'sdd', '--confirm',
492
+ '--reason', 'reconfirm the same work as a new revision', '--wave', 'wave-1:parallel:1.1,1.2'], process.cwd(), {
493
+ prepareRecommendation: false,
494
+ });
495
+
496
+ assert.notEqual(result.exitCode, 0);
497
+ assert.match(result.stderr, /recommend/i);
498
+ });
499
+
500
+ it('invalidates receipts from the replaced plan revision', () => {
501
+ const initial = runSsf(['execution', 'plan', changeDir, '--mode', 'batch-inline', '--confirm', '--acknowledge-recommendation',
502
+ '--reason', 'operator requested a batch', '--wave', 'wave-1:serial:1.1']);
503
+ assert.equal(initial.exitCode, 0, initial.stderr);
504
+ const reportPath = writeReviewReport('wave-1.md');
505
+ const reviewed = runSsf(['execution', 'review', changeDir, '--wave', 'wave-1',
506
+ '--base', gitRefs.base, '--head', gitRefs.head, '--report', reportPath, '--verdict', 'pass']);
507
+ assert.equal(reviewed.exitCode, 0, reviewed.stderr);
508
+
509
+ const revised = runSsf(['execution', 'revise', changeDir, '--mode', 'sdd',
510
+ '--reason', 'risk requires independent review', '--wave', 'wave-1:parallel:1.1,1.2', '--json']);
511
+ assert.equal(revised.exitCode, 0, revised.stderr);
512
+
513
+ const shown = runSsf(['execution', 'show', changeDir, '--json']);
514
+ assert.equal(shown.exitCode, 0, shown.stderr);
515
+ assert.equal(shown.json.current, true);
516
+ assert.equal(shown.json.waves[0].receipt, null);
517
+ assert.equal(shown.json.waves[0].eligible, true);
518
+ });
519
+
520
+ it('replans a current SDD plan with a new revision, renewed DP-4 state, and cleared receipts', () => {
521
+ const initial = runSsf(['execution', 'plan', changeDir, '--mode', 'sdd',
522
+ '--reason', 'full workflow default', '--wave', 'wave-1:serial:1.1']);
523
+ assert.equal(initial.exitCode, 0, initial.stderr);
524
+ const reviewed = runSsf(['execution', 'review', changeDir, '--wave', 'wave-1',
525
+ '--base', gitRefs.base, '--head', gitRefs.head, '--report', writeReviewReport('wave-1.md'), '--verdict', 'pass']);
526
+ assert.equal(reviewed.exitCode, 0, reviewed.stderr);
527
+
528
+ const replanned = runSsf(['execution', 'revise', changeDir, '--mode', 'sdd',
529
+ '--reason', 'split independent work into a recovery wave',
530
+ '--wave', 'wave-1:parallel:1.1,1.2', '--json']);
531
+
532
+ assert.equal(replanned.exitCode, 0, replanned.stderr);
533
+ assert.equal(replanned.json.plan.revision, 2);
534
+ assert.equal(runSsf(['state', 'get', changeDir, 'execution_plan_revision', '--json']).json.value, 2);
535
+ assert.match(runSsf(['state', 'get', changeDir, 'dp_4_result', '--json']).json.value, /plan revision 2/);
536
+ const shown = runSsf(['execution', 'show', changeDir, '--json']);
537
+ assert.equal(shown.exitCode, 0, shown.stderr);
538
+ assert.equal(shown.json.current, true);
539
+ assert.equal(shown.json.waves[0].receipt, null);
540
+ });
541
+
542
+ it('recovers a stale SDD plan by revising it to current artifacts and clearing old receipts', () => {
543
+ const initial = runSsf(['execution', 'plan', changeDir, '--mode', 'sdd',
544
+ '--reason', 'full workflow default', '--wave', 'wave-1:serial:1.1']);
545
+ assert.equal(initial.exitCode, 0, initial.stderr);
546
+ const reviewed = runSsf(['execution', 'review', changeDir, '--wave', 'wave-1',
547
+ '--base', gitRefs.base, '--head', gitRefs.head, '--report', writeReviewReport('stale-wave-1.md'), '--verdict', 'pass']);
548
+ assert.equal(reviewed.exitCode, 0, reviewed.stderr);
549
+ writeFileSync(join(changeDir, 'tasks.md'), '# Tasks\n\n- [ ] 1.1 Updated task\n- [ ] 1.2 Recovery task\n');
550
+
551
+ const replanned = runSsf(['execution', 'revise', changeDir, '--mode', 'sdd',
552
+ '--reason', 'refresh the plan after task scope changed',
553
+ '--wave', 'wave-1:parallel:1.1,1.2', '--json']);
554
+
555
+ assert.equal(replanned.exitCode, 0, replanned.stderr);
556
+ assert.equal(replanned.json.plan.revision, 2);
557
+ const shown = runSsf(['execution', 'show', changeDir, '--json']);
558
+ assert.equal(shown.exitCode, 0, shown.stderr);
559
+ assert.equal(shown.json.current, true);
560
+ assert.equal(shown.json.waves[0].receipt, null);
561
+ assert.match(runSsf(['state', 'get', changeDir, 'dp_4_result', '--json']).json.value, /plan revision 2/);
562
+ });
563
+
564
+ it('makes a failed current wave retryable while blocking dependents until its replacement pass receipt', () => {
565
+ const planned = runSsf(['execution', 'plan', changeDir, '--mode', 'sdd',
566
+ '--reason', 'repair reviews before dependent work',
567
+ '--wave', 'wave-1:serial:1.1',
568
+ '--wave', 'wave-2:serial:1.2:wave-1']);
569
+ assert.equal(planned.exitCode, 0, planned.stderr);
570
+
571
+ const failed = runSsf(['execution', 'review', changeDir, '--wave', 'wave-1',
572
+ '--base', gitRefs.base, '--head', gitRefs.head, '--report', writeReviewReport('wave-1-fail.md'), '--verdict', 'fail']);
573
+ assert.equal(failed.exitCode, 0, failed.stderr);
574
+
575
+ let shown = runSsf(['execution', 'show', changeDir, '--json']);
576
+ assert.equal(shown.exitCode, 0, shown.stderr);
577
+ assert.equal(shown.json.waves[0].receipt.status, 'fail');
578
+ assert.equal(shown.json.waves[0].retryable, true);
579
+ assert.equal(shown.json.waves[0].eligible, true);
580
+ assert.equal(shown.json.waves[1].eligible, false);
581
+ assert.deepEqual(shown.json.waves[1].blockers, ['wave-1']);
582
+
583
+ const replacement = runSsf(['execution', 'review', changeDir, '--wave', 'wave-1',
584
+ '--base', gitRefs.base, '--head', gitRefs.head, '--report', writeReviewReport('wave-1-pass.md'), '--verdict', 'pass']);
585
+ assert.equal(replacement.exitCode, 0, replacement.stderr);
586
+
587
+ shown = runSsf(['execution', 'show', changeDir, '--json']);
588
+ assert.equal(shown.exitCode, 0, shown.stderr);
589
+ assert.equal(shown.json.waves[0].receipt.status, 'pass');
590
+ assert.equal(shown.json.waves[0].retryable, false);
591
+ assert.equal(shown.json.waves[0].eligible, false);
592
+ assert.equal(shown.json.waves[1].eligible, true);
593
+ });
594
+
595
+ it('keeps the Task 1 state revision aligned through plan, show, revise, and show', () => {
596
+ writeChangeDirectory(changeDir, 'full', 2);
597
+ const initial = runSsf(['execution', 'plan', changeDir, '--mode', 'batch-inline', '--confirm', '--acknowledge-recommendation',
598
+ '--reason', 'operator requested a batch', '--wave', 'wave-1:serial:1.1', '--json']);
599
+ assert.equal(initial.exitCode, 0, initial.stderr);
600
+ assert.equal(initial.json.plan.revision, 2);
601
+
602
+ const firstShow = runSsf(['execution', 'show', changeDir, '--json']);
603
+ assert.equal(firstShow.exitCode, 0, firstShow.stderr);
604
+ assert.equal(firstShow.json.valid, true);
605
+ assert.equal(runSsf(['state', 'get', changeDir, 'revision', '--json']).json.value, 2);
606
+
607
+ const revised = runSsf(['execution', 'revise', changeDir, '--mode', 'sdd',
608
+ '--reason', 'risk requires independent review', '--wave', 'wave-1:parallel:1.1,1.2', '--json']);
609
+ assert.equal(revised.exitCode, 0, revised.stderr);
610
+ assert.equal(revised.json.plan.revision, 3);
611
+
612
+ const secondShow = runSsf(['execution', 'show', changeDir, '--json']);
613
+ assert.equal(secondShow.exitCode, 0, secondShow.stderr);
614
+ assert.equal(secondShow.json.valid, true);
615
+ assert.equal(runSsf(['state', 'get', changeDir, 'revision', '--json']).json.value, 3);
616
+ });
617
+
618
+ it('rejects an invalid review verdict without writing a receipt', () => {
619
+ runSsf(['execution', 'plan', changeDir, '--mode', 'sdd', '--reason', 'full workflow default',
620
+ '--wave', 'wave-1:parallel:1.1,1.2']);
621
+
622
+ const result = runSsf(['execution', 'review', changeDir, '--wave', 'wave-1',
623
+ '--base', gitRefs.base, '--head', gitRefs.head, '--report', 'reports/wave-1.md', '--verdict', 'maybe', '--json']);
624
+
625
+ assert.notEqual(result.exitCode, 0);
626
+ assert.match(result.stderr, /pass.*fail|verdict/i);
627
+ });
628
+
629
+ it('rejects a review without exactly one wave selector', () => {
630
+ const result = runSsf(['execution', 'review', changeDir, '--base', gitRefs.base,
631
+ '--head', gitRefs.head, '--report', 'reports/wave-1.md', '--verdict', 'pass']);
632
+
633
+ assert.notEqual(result.exitCode, 0);
634
+ assert.match(result.stderr, /--wave is required/);
635
+ });
636
+
637
+ it('rejects malformed waves and SDD plan downgrades', () => {
638
+ const malformed = runSsf(['execution', 'plan', changeDir, '--mode', 'sdd', '--reason', 'bad wave', '--wave', 'missing-parts']);
639
+ assert.notEqual(malformed.exitCode, 0);
640
+ assert.match(malformed.stderr, /wave/i);
641
+
642
+ runSsf(['execution', 'plan', changeDir, '--mode', 'sdd', '--reason', 'full workflow default', '--wave', 'wave-1:serial:1.1']);
643
+ const invalidRevision = runSsf(['execution', 'revise', changeDir, '--mode', 'inline', '--reason', 'downgrade', '--wave', 'wave-1:serial:1.1']);
644
+ assert.notEqual(invalidRevision.exitCode, 0);
645
+ assert.match(invalidRevision.stderr, /sdd|downgrade|upgrade/i);
646
+ });
647
+ });