@einja/dev-cli 0.1.9 → 0.1.11

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 (251) hide show
  1. package/README.md +30 -2
  2. package/dist/cli.js +3 -6
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/init.d.ts.map +1 -1
  5. package/dist/commands/init.js +11 -6
  6. package/dist/commands/init.js.map +1 -1
  7. package/dist/commands/list.js +1 -1
  8. package/dist/commands/list.js.map +1 -1
  9. package/dist/commands/sync.d.ts.map +1 -1
  10. package/dist/commands/sync.js +69 -7
  11. package/dist/commands/sync.js.map +1 -1
  12. package/dist/commands/sync.test.js +11 -25
  13. package/dist/commands/sync.test.js.map +1 -1
  14. package/dist/commands/task-loop/index.d.ts.map +1 -1
  15. package/dist/commands/task-loop/index.js +5 -2
  16. package/dist/commands/task-loop/index.js.map +1 -1
  17. package/dist/commands/task-loop/lib/__mocks__/child-process.mock.d.ts +227 -0
  18. package/dist/commands/task-loop/lib/__mocks__/child-process.mock.d.ts.map +1 -0
  19. package/dist/commands/task-loop/lib/__mocks__/child-process.mock.js +351 -0
  20. package/dist/commands/task-loop/lib/__mocks__/child-process.mock.js.map +1 -0
  21. package/dist/commands/task-loop/lib/__mocks__/sample-issues.d.ts +46 -0
  22. package/dist/commands/task-loop/lib/__mocks__/sample-issues.d.ts.map +1 -0
  23. package/dist/commands/task-loop/lib/__mocks__/sample-issues.js +224 -0
  24. package/dist/commands/task-loop/lib/__mocks__/sample-issues.js.map +1 -0
  25. package/dist/commands/task-loop/lib/branch-manager.d.ts.map +1 -1
  26. package/dist/commands/task-loop/lib/branch-manager.js +14 -8
  27. package/dist/commands/task-loop/lib/branch-manager.js.map +1 -1
  28. package/dist/commands/task-loop/lib/branch-manager.test.d.ts +2 -0
  29. package/dist/commands/task-loop/lib/branch-manager.test.d.ts.map +1 -0
  30. package/dist/commands/task-loop/lib/branch-manager.test.js +539 -0
  31. package/dist/commands/task-loop/lib/branch-manager.test.js.map +1 -0
  32. package/dist/commands/task-loop/lib/conflict-handler.js +1 -1
  33. package/dist/commands/task-loop/lib/conflict-handler.js.map +1 -1
  34. package/dist/commands/task-loop/lib/dependency-resolver.test.d.ts +2 -0
  35. package/dist/commands/task-loop/lib/dependency-resolver.test.d.ts.map +1 -0
  36. package/dist/commands/task-loop/lib/dependency-resolver.test.js +1129 -0
  37. package/dist/commands/task-loop/lib/dependency-resolver.test.js.map +1 -0
  38. package/dist/commands/task-loop/lib/gh-setup.d.ts.map +1 -1
  39. package/dist/commands/task-loop/lib/gh-setup.js.map +1 -1
  40. package/dist/commands/task-loop/lib/github-client.d.ts.map +1 -1
  41. package/dist/commands/task-loop/lib/github-client.js +3 -3
  42. package/dist/commands/task-loop/lib/github-client.js.map +1 -1
  43. package/dist/commands/task-loop/lib/github-client.test.d.ts +2 -0
  44. package/dist/commands/task-loop/lib/github-client.test.d.ts.map +1 -0
  45. package/dist/commands/task-loop/lib/github-client.test.js +377 -0
  46. package/dist/commands/task-loop/lib/github-client.test.js.map +1 -0
  47. package/dist/commands/task-loop/lib/issue-parser.js +4 -4
  48. package/dist/commands/task-loop/lib/issue-parser.js.map +1 -1
  49. package/dist/commands/task-loop/lib/issue-parser.test.d.ts +2 -0
  50. package/dist/commands/task-loop/lib/issue-parser.test.d.ts.map +1 -0
  51. package/dist/commands/task-loop/lib/issue-parser.test.js +854 -0
  52. package/dist/commands/task-loop/lib/issue-parser.test.js.map +1 -0
  53. package/dist/commands/task-loop/lib/pull-request-manager.d.ts +35 -0
  54. package/dist/commands/task-loop/lib/pull-request-manager.d.ts.map +1 -0
  55. package/dist/commands/task-loop/lib/pull-request-manager.js +150 -0
  56. package/dist/commands/task-loop/lib/pull-request-manager.js.map +1 -0
  57. package/dist/commands/task-loop/lib/task-number-utils.d.ts +10 -4
  58. package/dist/commands/task-loop/lib/task-number-utils.d.ts.map +1 -1
  59. package/dist/commands/task-loop/lib/task-number-utils.js +19 -10
  60. package/dist/commands/task-loop/lib/task-number-utils.js.map +1 -1
  61. package/dist/commands/task-loop/lib/task-number-utils.test.d.ts +2 -0
  62. package/dist/commands/task-loop/lib/task-number-utils.test.d.ts.map +1 -0
  63. package/dist/commands/task-loop/lib/task-number-utils.test.js +379 -0
  64. package/dist/commands/task-loop/lib/task-number-utils.test.js.map +1 -0
  65. package/dist/commands/task-loop/lib/task-state-manager.d.ts.map +1 -1
  66. package/dist/commands/task-loop/lib/task-state-manager.js +1 -1
  67. package/dist/commands/task-loop/lib/task-state-manager.js.map +1 -1
  68. package/dist/commands/task-loop/lib/task-state-manager.test.d.ts +2 -0
  69. package/dist/commands/task-loop/lib/task-state-manager.test.d.ts.map +1 -0
  70. package/dist/commands/task-loop/lib/task-state-manager.test.js +541 -0
  71. package/dist/commands/task-loop/lib/task-state-manager.test.js.map +1 -0
  72. package/dist/lib/file-system.js +1 -1
  73. package/dist/lib/file-system.js.map +1 -1
  74. package/dist/lib/mcp-config.d.ts.map +1 -1
  75. package/dist/lib/mcp-config.js +8 -4
  76. package/dist/lib/mcp-config.js.map +1 -1
  77. package/dist/lib/mcp-config.test.js +2 -2
  78. package/dist/lib/mcp-config.test.js.map +1 -1
  79. package/dist/lib/merger.d.ts.map +1 -1
  80. package/dist/lib/merger.js.map +1 -1
  81. package/dist/lib/preset-update/cli-repo-detector.test.js.map +1 -1
  82. package/dist/lib/preset-update/file-copier.d.ts +2 -0
  83. package/dist/lib/preset-update/file-copier.d.ts.map +1 -1
  84. package/dist/lib/preset-update/file-copier.js +12 -8
  85. package/dist/lib/preset-update/file-copier.js.map +1 -1
  86. package/dist/lib/preset-update/file-copier.test.js +36 -5
  87. package/dist/lib/preset-update/file-copier.test.js.map +1 -1
  88. package/dist/lib/preset-update/preset-finder.d.ts +1 -1
  89. package/dist/lib/preset-update/preset-finder.d.ts.map +1 -1
  90. package/dist/lib/preset-update/preset-finder.js +1 -1
  91. package/dist/lib/preset-update/preset-finder.js.map +1 -1
  92. package/dist/lib/preset-update/preset-finder.test.js +11 -11
  93. package/dist/lib/preset-update/preset-finder.test.js.map +1 -1
  94. package/dist/lib/preset.js +3 -3
  95. package/dist/lib/preset.js.map +1 -1
  96. package/dist/lib/sync/backup-manager.d.ts.map +1 -1
  97. package/dist/lib/sync/backup-manager.js +1 -1
  98. package/dist/lib/sync/backup-manager.js.map +1 -1
  99. package/dist/lib/sync/backup-manager.test.js +2 -2
  100. package/dist/lib/sync/backup-manager.test.js.map +1 -1
  101. package/dist/lib/sync/batch-processor.d.ts.map +1 -1
  102. package/dist/lib/sync/batch-processor.js.map +1 -1
  103. package/dist/lib/sync/batch-processor.test.js.map +1 -1
  104. package/dist/lib/sync/category-validator.d.ts.map +1 -1
  105. package/dist/lib/sync/category-validator.js.map +1 -1
  106. package/dist/lib/sync/category-validator.test.js +2 -11
  107. package/dist/lib/sync/category-validator.test.js.map +1 -1
  108. package/dist/lib/sync/conflict-reporter.d.ts.map +1 -1
  109. package/dist/lib/sync/conflict-reporter.js +1 -2
  110. package/dist/lib/sync/conflict-reporter.js.map +1 -1
  111. package/dist/lib/sync/conflict-reporter.test.js +2 -7
  112. package/dist/lib/sync/conflict-reporter.test.js.map +1 -1
  113. package/dist/lib/sync/diff-engine.d.ts.map +1 -1
  114. package/dist/lib/sync/diff-engine.js +2 -4
  115. package/dist/lib/sync/diff-engine.js.map +1 -1
  116. package/dist/lib/sync/diff-engine.test.js.map +1 -1
  117. package/dist/lib/sync/file-filter.d.ts.map +1 -1
  118. package/dist/lib/sync/file-filter.js +26 -3
  119. package/dist/lib/sync/file-filter.js.map +1 -1
  120. package/dist/lib/sync/file-filter.test.js +26 -2
  121. package/dist/lib/sync/file-filter.test.js.map +1 -1
  122. package/dist/lib/sync/hash-cache.d.ts.map +1 -1
  123. package/dist/lib/sync/hash-cache.js.map +1 -1
  124. package/dist/lib/sync/hash-cache.test.js +2 -2
  125. package/dist/lib/sync/hash-cache.test.js.map +1 -1
  126. package/dist/lib/sync/integration.test.js +289 -2
  127. package/dist/lib/sync/integration.test.js.map +1 -1
  128. package/dist/lib/sync/marker-processor.d.ts +34 -10
  129. package/dist/lib/sync/marker-processor.d.ts.map +1 -1
  130. package/dist/lib/sync/marker-processor.js +142 -41
  131. package/dist/lib/sync/marker-processor.js.map +1 -1
  132. package/dist/lib/sync/marker-processor.test.js +134 -1
  133. package/dist/lib/sync/marker-processor.test.js.map +1 -1
  134. package/dist/lib/sync/metadata-manager.d.ts.map +1 -1
  135. package/dist/lib/sync/metadata-manager.js.map +1 -1
  136. package/dist/lib/sync/metadata-manager.test.js +4 -6
  137. package/dist/lib/sync/metadata-manager.test.js.map +1 -1
  138. package/dist/lib/sync/performance.test.js +2 -2
  139. package/dist/lib/sync/performance.test.js.map +1 -1
  140. package/dist/lib/sync/seed-synchronizer.d.ts +27 -0
  141. package/dist/lib/sync/seed-synchronizer.d.ts.map +1 -0
  142. package/dist/lib/sync/seed-synchronizer.js +72 -0
  143. package/dist/lib/sync/seed-synchronizer.js.map +1 -0
  144. package/dist/lib/sync/seed-synchronizer.test.d.ts +2 -0
  145. package/dist/lib/sync/seed-synchronizer.test.d.ts.map +1 -0
  146. package/dist/lib/sync/seed-synchronizer.test.js +147 -0
  147. package/dist/lib/sync/seed-synchronizer.test.js.map +1 -0
  148. package/dist/types/index.d.ts.map +1 -1
  149. package/dist/types/preset-update.d.ts +1 -1
  150. package/dist/types/sync.d.ts +4 -2
  151. package/dist/types/sync.d.ts.map +1 -1
  152. package/dist/types/sync.js.map +1 -1
  153. package/package.json +1 -2
  154. package/presets/default/.claude/agents/einja/backend-architect.md +1131 -0
  155. package/presets/{minimal/.claude/agents/einja/frontend → default/.claude/agents/einja}/design-engineer.md +1 -1
  156. package/presets/{minimal/.claude/agents/einja/frontend → default/.claude/agents/einja}/frontend-architect.md +1 -1
  157. package/presets/{minimal/.claude/agents/einja/frontend → default/.claude/agents/einja}/frontend-coder.md +1 -37
  158. package/presets/{minimal → default}/.claude/agents/einja/task/task-committer.md +12 -6
  159. package/presets/{minimal → default}/.claude/agents/einja/task/task-executer.md +9 -9
  160. package/presets/{minimal → default}/.claude/commands/einja/frontend-implement.md +1 -1
  161. package/presets/{minimal → default}/.claude/commands/einja/update-docs-by-task-specs.md +6 -6
  162. package/presets/{minimal/.claude/skills/einja/api-development → default/.claude/skills/einja-api-development}/SKILL.md +5 -5
  163. package/presets/{minimal/.claude/skills/einja/backend-architecture → default/.claude/skills/einja-backend-architecture}/SKILL.md +5 -5
  164. package/presets/{minimal/.claude/skills/einja/coding-standards → default/.claude/skills/einja-coding-standards}/SKILL.md +6 -6
  165. package/presets/{minimal/.claude/skills/einja/component-design → default/.claude/skills/einja-component-design}/SKILL.md +6 -6
  166. package/presets/{minimal/.claude/skills/einja/frontend-development → default/.claude/skills/einja-frontend-development}/SKILL.md +5 -5
  167. package/presets/{minimal/.claude/skills/einja/output-format → default/.claude/skills/einja-output-format}/SKILL.md +54 -5
  168. package/presets/{minimal → default}/preset.yaml +1 -1
  169. package/presets/{minimal → default}/symlinks.json +10 -10
  170. package/scaffolds/cli/preset.yaml +110 -0
  171. package/scaffolds/example/README.md +35 -0
  172. package/scaffolds/example/specs/issues/issue999-example-task/design.md +879 -0
  173. package/scaffolds/example/specs/issues/issue999-example-task/qa-tests/README.md +150 -0
  174. package/scaffolds/example/specs/issues/issue999-example-task/qa-tests/phase1/1-1.md +268 -0
  175. package/scaffolds/example/specs/issues/issue999-example-task/qa-tests/phase1/1-2.md +179 -0
  176. package/scaffolds/example/specs/issues/issue999-example-task/qa-tests/phase1/1-3.md +392 -0
  177. package/scaffolds/example/specs/issues/issue999-example-task/qa-tests/phase1/evidence/.gitkeep +0 -0
  178. package/scaffolds/example/specs/issues/issue999-example-task/qa-tests/phase2/2-1.md +459 -0
  179. package/scaffolds/example/specs/issues/issue999-example-task/qa-tests/phase2/evidence/.gitkeep +0 -0
  180. package/scaffolds/example/specs/issues/issue999-example-task/qa-tests/scenarios.md +125 -0
  181. package/scaffolds/example/specs/issues/issue999-example-task/requirements.md +494 -0
  182. package/scaffolds/example/specs/issues/issue999-example-task/tasks.md +212 -0
  183. package/scaffolds/instructions/deployment-setup.md +458 -0
  184. package/scaffolds/instructions/environment-setup.md +509 -0
  185. package/scaffolds/instructions/local-server-environment-and-worktree.md +539 -0
  186. package/scaffolds/instructions/task-execute.md +649 -0
  187. package/scaffolds/instructions/task-vibe-kanban-loop.md +495 -0
  188. package/scaffolds/memory/archive/.gitkeep +0 -0
  189. package/scaffolds/memory/decisions.md +35 -0
  190. package/scaffolds/memory/patterns.md +37 -0
  191. package/scaffolds/steering/README.md +42 -0
  192. package/scaffolds/steering/acceptance-criteria-and-qa-guide.md +11 -0
  193. package/scaffolds/steering/architecture.md +11 -0
  194. package/scaffolds/steering/branch-strategy.md +11 -0
  195. package/scaffolds/steering/commit-rules.md +12 -1
  196. package/scaffolds/steering/db-schema-design.md +11 -0
  197. package/scaffolds/steering/development/api-development.md +15 -4
  198. package/scaffolds/steering/development/backend-architecture.md +11 -0
  199. package/scaffolds/steering/development/frontend-development.md +11 -0
  200. package/scaffolds/steering/development/review-guidelines.md +11 -0
  201. package/scaffolds/steering/development/testing-strategy.md +85 -0
  202. package/scaffolds/steering/development-workflow.md +11 -0
  203. package/scaffolds/steering/infrastructure/deployment.md +11 -0
  204. package/scaffolds/steering/infrastructure/environment-variables.md +11 -0
  205. package/scaffolds/steering/product.md +11 -0
  206. package/scaffolds/steering/task-management.md +11 -0
  207. package/scaffolds/CLAUDE.md.template +0 -386
  208. /package/presets/{minimal → default}/.claude/agents/einja/docs/docs-updater.md +0 -0
  209. /package/presets/{minimal → default}/.claude/agents/einja/git/conflict-resolver.md +0 -0
  210. /package/presets/{minimal → default}/.claude/agents/einja/specs/spec-design-generator.md +0 -0
  211. /package/presets/{minimal → default}/.claude/agents/einja/specs/spec-qa-generator.md +0 -0
  212. /package/presets/{minimal → default}/.claude/agents/einja/specs/spec-requirements-generator.md +0 -0
  213. /package/presets/{minimal → default}/.claude/agents/einja/specs/spec-tasks-generator.md +0 -0
  214. /package/presets/{minimal → default}/.claude/agents/einja/task/task-modification-analyzer.md +0 -0
  215. /package/presets/{minimal → default}/.claude/agents/einja/task/task-qa.md +0 -0
  216. /package/presets/{minimal → default}/.claude/agents/einja/task/task-reviewer.md +0 -0
  217. /package/presets/{minimal → default}/.claude/commands/einja/spec-create.md +0 -0
  218. /package/presets/{minimal → default}/.claude/commands/einja/start-dev.md +0 -0
  219. /package/presets/{minimal → default}/.claude/commands/einja/sync-cursor-commands.md +0 -0
  220. /package/presets/{minimal → default}/.claude/commands/einja/task-exec.md +0 -0
  221. /package/presets/{minimal → default}/.claude/hooks/einja/biome-format.sh +0 -0
  222. /package/presets/{minimal → default}/.claude/hooks/einja/design-doc-check.sh +0 -0
  223. /package/presets/{minimal → default}/.claude/hooks/einja/detect-secrets.sh +0 -0
  224. /package/presets/{minimal → default}/.claude/hooks/einja/large-file-warning.sh +0 -0
  225. /package/presets/{minimal → default}/.claude/hooks/einja/playwright-resize.sh +0 -0
  226. /package/presets/{minimal → default}/.claude/hooks/einja/typecheck.sh +0 -0
  227. /package/presets/{minimal → default}/.claude/hooks/einja/unset-volta-recursion.sh +0 -0
  228. /package/presets/{minimal → default}/.claude/hooks/einja/validate-git-commit.sh +0 -0
  229. /package/presets/{minimal → default}/.claude/hooks/einja/warn-index-ts.sh +0 -0
  230. /package/presets/{minimal → default}/.claude/hooks/einja/warn-relative-import.sh +0 -0
  231. /package/presets/{minimal → default}/.claude/settings.json +0 -0
  232. /package/presets/{minimal/.claude/skills/einja/coding-standards → default/.claude/skills/einja-coding-standards}/reference/naming-conventions.md +0 -0
  233. /package/presets/{minimal/.claude/skills/einja/coding-standards → default/.claude/skills/einja-coding-standards}/reference/prohibited-patterns.md +0 -0
  234. /package/presets/{minimal/.claude/skills/einja/coding-standards → default/.claude/skills/einja-coding-standards}/reference/typescript-rules.md +0 -0
  235. /package/presets/{minimal/.claude/skills/einja/component-design → default/.claude/skills/einja-component-design}/reference/directory-structure.md +0 -0
  236. /package/presets/{minimal/.claude/skills/einja/component-design → default/.claude/skills/einja-component-design}/reference/props-patterns.md +0 -0
  237. /package/presets/{minimal/.claude/skills/einja/component-design → default/.claude/skills/einja-component-design}/reference/styling-guide.md +0 -0
  238. /package/presets/{minimal/.claude/skills/einja/conflict-resolver → default/.claude/skills/einja-conflict-resolver}/SKILL.md +0 -0
  239. /package/presets/{minimal/.claude/skills/einja/general-context-loader → default/.claude/skills/einja-general-context-loader}/SKILL.md +0 -0
  240. /package/presets/{minimal/.claude/skills/einja/spec-context-loader → default/.claude/skills/einja-spec-context-loader}/SKILL.md +0 -0
  241. /package/presets/{minimal/.claude/skills/einja/task-commit → default/.claude/skills/einja-task-commit}/SKILL.md +0 -0
  242. /package/presets/{minimal/.claude/skills/einja/task-qa → default/.claude/skills/einja-task-qa}/SKILL.md +0 -0
  243. /package/presets/{minimal/.claude/skills/einja/task-qa → default/.claude/skills/einja-task-qa}/reference/failure-patterns.md +0 -0
  244. /package/presets/{minimal/.claude/skills/einja/task-qa → default/.claude/skills/einja-task-qa}/reference/troubleshooting.md +0 -0
  245. /package/presets/{minimal/.claude/skills/einja/task-qa → default/.claude/skills/einja-task-qa}/reference/usage-patterns.md +0 -0
  246. /package/presets/{minimal/.claude/skills/einja/task-qa → default/.claude/skills/einja-task-qa}/templates/qa-test-template.md +0 -0
  247. /package/{templates → scaffolds/templates}/README.md +0 -0
  248. /package/{templates → scaffolds/templates}/design-simple.md.template +0 -0
  249. /package/{templates → scaffolds/templates}/design.md.template +0 -0
  250. /package/{templates → scaffolds/templates}/qa-test.md.template +0 -0
  251. /package/{templates → scaffolds/templates}/requirements.md.template +0 -0
@@ -0,0 +1,1129 @@
1
+ import { beforeEach, describe, expect, it, vi } from "vitest";
2
+ import { checkExternalIssueDependencies, detectCircularDependencies, getCompletedPhaseNumbers, isAllTasksCompleted, isDependencySatisfied, isPhaseCompleted, selectExecutableTaskGroups, selectNextTaskGroup, } from "./dependency-resolver.js";
3
+ import { parseIssueBody } from "./issue-parser.js";
4
+ // github-client.js のモック
5
+ vi.mock("./github-client.js", () => ({
6
+ isIssueClosed: vi.fn(),
7
+ }));
8
+ import { isIssueClosed } from "./github-client.js";
9
+ describe("dependency-resolver", () => {
10
+ beforeEach(() => {
11
+ // 各テスト前にモックをリセット
12
+ vi.clearAllMocks();
13
+ });
14
+ describe("isDependencySatisfied", () => {
15
+ it("依存なしの場合、trueを返す", () => {
16
+ // Given: 依存関係を持たないタスクグループ
17
+ const taskGroup = {
18
+ id: "1.1",
19
+ name: "タスク1",
20
+ phaseNumber: 1,
21
+ status: "pending",
22
+ dependencies: [],
23
+ completionCriteria: "完了",
24
+ tasks: [],
25
+ };
26
+ const completedGroups = new Set();
27
+ const phases = [];
28
+ // When: 依存関係チェック
29
+ const result = isDependencySatisfied(taskGroup, completedGroups, phases);
30
+ // Then: trueが返る
31
+ expect(result).toBe(true);
32
+ });
33
+ it("タスク依存'1.1'が完了済みの場合、trueを返す", () => {
34
+ // Given: タスク1.1に依存するタスク1.2
35
+ const taskGroup = {
36
+ id: "1.2",
37
+ name: "タスク2",
38
+ phaseNumber: 1,
39
+ status: "pending",
40
+ dependencies: ["1.1"],
41
+ completionCriteria: "完了",
42
+ tasks: [],
43
+ };
44
+ const completedGroups = new Set(["1.1"]);
45
+ const phases = [];
46
+ // When: 依存関係チェック
47
+ const result = isDependencySatisfied(taskGroup, completedGroups, phases);
48
+ // Then: trueが返る
49
+ expect(result).toBe(true);
50
+ });
51
+ it("タスク依存'1.1'が未完了の場合、falseを返す", () => {
52
+ // Given: タスク1.1に依存するタスク1.2(1.1は未完了)
53
+ const taskGroup = {
54
+ id: "1.2",
55
+ name: "タスク2",
56
+ phaseNumber: 1,
57
+ status: "pending",
58
+ dependencies: ["1.1"],
59
+ completionCriteria: "完了",
60
+ tasks: [],
61
+ };
62
+ const completedGroups = new Set();
63
+ const phases = [];
64
+ // When: 依存関係チェック
65
+ const result = isDependencySatisfied(taskGroup, completedGroups, phases);
66
+ // Then: falseが返る
67
+ expect(result).toBe(false);
68
+ });
69
+ it("Phase依存が満たされている場合、trueを返す", () => {
70
+ // Given: Phase 1完了に依存するタスク2.1
71
+ const taskGroup = {
72
+ id: "2.1",
73
+ name: "タスク2.1",
74
+ phaseNumber: 2,
75
+ status: "pending",
76
+ dependencies: ["Phase 1完了"],
77
+ completionCriteria: "完了",
78
+ tasks: [],
79
+ };
80
+ const completedGroups = new Set(["1.1", "1.2"]);
81
+ const phases = [
82
+ {
83
+ number: 1,
84
+ name: "Phase 1",
85
+ taskGroups: [
86
+ {
87
+ id: "1.1",
88
+ name: "タスク1.1",
89
+ phaseNumber: 1,
90
+ status: "completed",
91
+ dependencies: [],
92
+ completionCriteria: "完了",
93
+ tasks: [],
94
+ },
95
+ {
96
+ id: "1.2",
97
+ name: "タスク1.2",
98
+ phaseNumber: 1,
99
+ status: "completed",
100
+ dependencies: [],
101
+ completionCriteria: "完了",
102
+ tasks: [],
103
+ },
104
+ ],
105
+ },
106
+ ];
107
+ // When: 依存関係チェック
108
+ const result = isDependencySatisfied(taskGroup, completedGroups, phases);
109
+ // Then: trueが返る
110
+ expect(result).toBe(true);
111
+ });
112
+ it("Phase依存が未満たしの場合、falseを返す", () => {
113
+ // Given: Phase 1完了に依存するタスク2.1(Phase 1は未完了)
114
+ const taskGroup = {
115
+ id: "2.1",
116
+ name: "タスク2.1",
117
+ phaseNumber: 2,
118
+ status: "pending",
119
+ dependencies: ["Phase 1完了"],
120
+ completionCriteria: "完了",
121
+ tasks: [],
122
+ };
123
+ const completedGroups = new Set(["1.1"]);
124
+ const phases = [
125
+ {
126
+ number: 1,
127
+ name: "Phase 1",
128
+ taskGroups: [
129
+ {
130
+ id: "1.1",
131
+ name: "タスク1.1",
132
+ phaseNumber: 1,
133
+ status: "completed",
134
+ dependencies: [],
135
+ completionCriteria: "完了",
136
+ tasks: [],
137
+ },
138
+ {
139
+ id: "1.2",
140
+ name: "タスク1.2",
141
+ phaseNumber: 1,
142
+ status: "pending",
143
+ dependencies: [],
144
+ completionCriteria: "完了",
145
+ tasks: [],
146
+ },
147
+ ],
148
+ },
149
+ ];
150
+ // When: 依存関係チェック
151
+ const result = isDependencySatisfied(taskGroup, completedGroups, phases);
152
+ // Then: falseが返る
153
+ expect(result).toBe(false);
154
+ });
155
+ it("Issue依存'#123'の場合、同期チェックではtrueを返す", () => {
156
+ // Given: Issue #123に依存するタスク
157
+ const taskGroup = {
158
+ id: "1.1",
159
+ name: "タスク1",
160
+ phaseNumber: 1,
161
+ status: "pending",
162
+ dependencies: ["#123"],
163
+ completionCriteria: "完了",
164
+ tasks: [],
165
+ };
166
+ const completedGroups = new Set();
167
+ const phases = [];
168
+ // When: 依存関係チェック
169
+ const result = isDependencySatisfied(taskGroup, completedGroups, phases);
170
+ // Then: trueが返る(Issue依存は非同期でチェックされる)
171
+ expect(result).toBe(true);
172
+ });
173
+ it("複数依存が全て満たされている場合、trueを返す", () => {
174
+ // Given: 複数の依存関係を持つタスク
175
+ const taskGroup = {
176
+ id: "1.3",
177
+ name: "タスク3",
178
+ phaseNumber: 1,
179
+ status: "pending",
180
+ dependencies: ["1.1", "1.2"],
181
+ completionCriteria: "完了",
182
+ tasks: [],
183
+ };
184
+ const completedGroups = new Set(["1.1", "1.2"]);
185
+ const phases = [];
186
+ // When: 依存関係チェック
187
+ const result = isDependencySatisfied(taskGroup, completedGroups, phases);
188
+ // Then: trueが返る
189
+ expect(result).toBe(true);
190
+ });
191
+ it("複数依存のうち一部が未満たしの場合、falseを返す", () => {
192
+ // Given: 複数の依存関係を持つタスク(一部未完了)
193
+ const taskGroup = {
194
+ id: "1.3",
195
+ name: "タスク3",
196
+ phaseNumber: 1,
197
+ status: "pending",
198
+ dependencies: ["1.1", "1.2"],
199
+ completionCriteria: "完了",
200
+ tasks: [],
201
+ };
202
+ const completedGroups = new Set(["1.1"]);
203
+ const phases = [];
204
+ // When: 依存関係チェック
205
+ const result = isDependencySatisfied(taskGroup, completedGroups, phases);
206
+ // Then: falseが返る
207
+ expect(result).toBe(false);
208
+ });
209
+ });
210
+ describe("checkExternalIssueDependencies", () => {
211
+ it("Issue依存'#123'がクローズ済みの場合、trueを返す", async () => {
212
+ // Given: Issue #123に依存するタスク
213
+ const taskGroup = {
214
+ id: "1.1",
215
+ name: "タスク1",
216
+ phaseNumber: 1,
217
+ status: "pending",
218
+ dependencies: ["#123"],
219
+ completionCriteria: "完了",
220
+ tasks: [],
221
+ };
222
+ vi.mocked(isIssueClosed).mockReturnValue(true);
223
+ // When: 外部Issue依存をチェック
224
+ const result = await checkExternalIssueDependencies(taskGroup);
225
+ // Then: trueが返る
226
+ expect(result).toBe(true);
227
+ expect(isIssueClosed).toHaveBeenCalledWith(123);
228
+ });
229
+ it("Issue依存'#123'がオープンの場合、falseを返す", async () => {
230
+ // Given: Issue #123に依存するタスク(#123はオープン)
231
+ const taskGroup = {
232
+ id: "1.1",
233
+ name: "タスク1",
234
+ phaseNumber: 1,
235
+ status: "pending",
236
+ dependencies: ["#123"],
237
+ completionCriteria: "完了",
238
+ tasks: [],
239
+ };
240
+ vi.mocked(isIssueClosed).mockReturnValue(false);
241
+ // When: 外部Issue依存をチェック
242
+ const result = await checkExternalIssueDependencies(taskGroup);
243
+ // Then: falseが返る
244
+ expect(result).toBe(false);
245
+ expect(isIssueClosed).toHaveBeenCalledWith(123);
246
+ });
247
+ it("複数Issue依存が全てクローズ済みの場合、trueを返す", async () => {
248
+ // Given: 複数のIssue依存を持つタスク
249
+ const taskGroup = {
250
+ id: "1.1",
251
+ name: "タスク1",
252
+ phaseNumber: 1,
253
+ status: "pending",
254
+ dependencies: ["#123", "#456"],
255
+ completionCriteria: "完了",
256
+ tasks: [],
257
+ };
258
+ vi.mocked(isIssueClosed).mockReturnValue(true);
259
+ // When: 外部Issue依存をチェック
260
+ const result = await checkExternalIssueDependencies(taskGroup);
261
+ // Then: trueが返る
262
+ expect(result).toBe(true);
263
+ expect(isIssueClosed).toHaveBeenCalledWith(123);
264
+ expect(isIssueClosed).toHaveBeenCalledWith(456);
265
+ });
266
+ it("複数Issue依存のうち一部がオープンの場合、falseを返す", async () => {
267
+ // Given: 複数のIssue依存を持つタスク(一部オープン)
268
+ const taskGroup = {
269
+ id: "1.1",
270
+ name: "タスク1",
271
+ phaseNumber: 1,
272
+ status: "pending",
273
+ dependencies: ["#123", "#456"],
274
+ completionCriteria: "完了",
275
+ tasks: [],
276
+ };
277
+ vi.mocked(isIssueClosed).mockReturnValueOnce(true).mockReturnValueOnce(false);
278
+ // When: 外部Issue依存をチェック
279
+ const result = await checkExternalIssueDependencies(taskGroup);
280
+ // Then: falseが返る
281
+ expect(result).toBe(false);
282
+ });
283
+ it("Issue依存が存在しない場合、trueを返す", async () => {
284
+ // Given: Issue依存を持たないタスク
285
+ const taskGroup = {
286
+ id: "1.1",
287
+ name: "タスク1",
288
+ phaseNumber: 1,
289
+ status: "pending",
290
+ dependencies: ["1.2"],
291
+ completionCriteria: "完了",
292
+ tasks: [],
293
+ };
294
+ // When: 外部Issue依存をチェック
295
+ const result = await checkExternalIssueDependencies(taskGroup);
296
+ // Then: trueが返る(チェック対象なし)
297
+ expect(result).toBe(true);
298
+ expect(isIssueClosed).not.toHaveBeenCalled();
299
+ });
300
+ });
301
+ describe("isPhaseCompleted", () => {
302
+ it("Phase内の全タスクグループが完了済みの場合、trueを返す", () => {
303
+ // Given: Phase 1の全タスクグループが完了済み
304
+ const phases = [
305
+ {
306
+ number: 1,
307
+ name: "Phase 1",
308
+ taskGroups: [
309
+ {
310
+ id: "1.1",
311
+ name: "タスク1.1",
312
+ phaseNumber: 1,
313
+ status: "completed",
314
+ dependencies: [],
315
+ completionCriteria: "完了",
316
+ tasks: [],
317
+ },
318
+ {
319
+ id: "1.2",
320
+ name: "タスク1.2",
321
+ phaseNumber: 1,
322
+ status: "completed",
323
+ dependencies: [],
324
+ completionCriteria: "完了",
325
+ tasks: [],
326
+ },
327
+ ],
328
+ },
329
+ ];
330
+ const completedGroups = new Set(["1.1", "1.2"]);
331
+ // When: Phase完了チェック
332
+ const result = isPhaseCompleted(phases, 1, completedGroups);
333
+ // Then: trueが返る
334
+ expect(result).toBe(true);
335
+ });
336
+ it("Phase内に未完了タスクグループが存在する場合、falseを返す", () => {
337
+ // Given: Phase 1に未完了タスクグループが存在
338
+ const phases = [
339
+ {
340
+ number: 1,
341
+ name: "Phase 1",
342
+ taskGroups: [
343
+ {
344
+ id: "1.1",
345
+ name: "タスク1.1",
346
+ phaseNumber: 1,
347
+ status: "completed",
348
+ dependencies: [],
349
+ completionCriteria: "完了",
350
+ tasks: [],
351
+ },
352
+ {
353
+ id: "1.2",
354
+ name: "タスク1.2",
355
+ phaseNumber: 1,
356
+ status: "pending",
357
+ dependencies: [],
358
+ completionCriteria: "完了",
359
+ tasks: [],
360
+ },
361
+ ],
362
+ },
363
+ ];
364
+ const completedGroups = new Set(["1.1"]);
365
+ // When: Phase完了チェック
366
+ const result = isPhaseCompleted(phases, 1, completedGroups);
367
+ // Then: falseが返る
368
+ expect(result).toBe(false);
369
+ });
370
+ it("存在しないPhase番号を指定した場合、falseを返す", () => {
371
+ // Given: Phase 1のみ存在
372
+ const phases = [
373
+ {
374
+ number: 1,
375
+ name: "Phase 1",
376
+ taskGroups: [
377
+ {
378
+ id: "1.1",
379
+ name: "タスク1.1",
380
+ phaseNumber: 1,
381
+ status: "completed",
382
+ dependencies: [],
383
+ completionCriteria: "完了",
384
+ tasks: [],
385
+ },
386
+ ],
387
+ },
388
+ ];
389
+ const completedGroups = new Set(["1.1"]);
390
+ // When: 存在しないPhase 2をチェック
391
+ const result = isPhaseCompleted(phases, 2, completedGroups);
392
+ // Then: falseが返る
393
+ expect(result).toBe(false);
394
+ });
395
+ it("Phaseにタスクグループが存在しない場合、trueを返す", () => {
396
+ // Given: タスクグループが空のPhase
397
+ const phases = [
398
+ {
399
+ number: 1,
400
+ name: "Phase 1",
401
+ taskGroups: [],
402
+ },
403
+ ];
404
+ const completedGroups = new Set();
405
+ // When: Phase完了チェック
406
+ const result = isPhaseCompleted(phases, 1, completedGroups);
407
+ // Then: trueが返る(タスクグループが空=全て完了と見なす)
408
+ expect(result).toBe(true);
409
+ });
410
+ });
411
+ describe("getCompletedPhaseNumbers", () => {
412
+ it("完了済みPhaseの番号配列を返す", () => {
413
+ // Given: Phase 1が完了、Phase 2が未完了のIssue
414
+ const issue = {
415
+ number: 1,
416
+ title: "Test",
417
+ state: "open",
418
+ body: `### Phase 1: フェーズ1
419
+
420
+ - [x] **1.1 タスク1.1**
421
+ **依存関係**: なし
422
+ **完了条件**: 完了
423
+
424
+ - [x] **1.2 タスク1.2**
425
+ **依存関係**: 1.1
426
+ **完了条件**: 完了
427
+
428
+ ### Phase 2: フェーズ2
429
+
430
+ - [ ] **2.1 タスク2.1**
431
+ **依存関係**: Phase 1完了
432
+ **完了条件**: 完了
433
+ `,
434
+ };
435
+ const parsedIssue = parseIssueBody(issue);
436
+ // When: 完了済みPhase番号を取得
437
+ const result = getCompletedPhaseNumbers(parsedIssue);
438
+ // Then: Phase 1のみ返る
439
+ expect(result).toEqual([1]);
440
+ });
441
+ it("全Phase未完了の場合、空配列を返す", () => {
442
+ // Given: 全Phase未完了のIssue
443
+ const issue = {
444
+ number: 1,
445
+ title: "Test",
446
+ state: "open",
447
+ body: `### Phase 1: フェーズ1
448
+
449
+ - [ ] **1.1 タスク1.1**
450
+ **依存関係**: なし
451
+ **完了条件**: 完了
452
+ `,
453
+ };
454
+ const parsedIssue = parseIssueBody(issue);
455
+ // When: 完了済みPhase番号を取得
456
+ const result = getCompletedPhaseNumbers(parsedIssue);
457
+ // Then: 空配列が返る
458
+ expect(result).toEqual([]);
459
+ });
460
+ it("全Phase完了の場合、全Phase番号を返す", () => {
461
+ // Given: 全Phase完了のIssue
462
+ const issue = {
463
+ number: 1,
464
+ title: "Test",
465
+ state: "open",
466
+ body: `### Phase 1: フェーズ1
467
+
468
+ - [x] **1.1 タスク1.1**
469
+ **依存関係**: なし
470
+ **完了条件**: 完了
471
+
472
+ ### Phase 2: フェーズ2
473
+
474
+ - [x] **2.1 タスク2.1**
475
+ **依存関係**: Phase 1完了
476
+ **完了条件**: 完了
477
+ `,
478
+ };
479
+ const parsedIssue = parseIssueBody(issue);
480
+ // When: 完了済みPhase番号を取得
481
+ const result = getCompletedPhaseNumbers(parsedIssue);
482
+ // Then: 全Phase番号が返る
483
+ expect(result).toEqual([1, 2]);
484
+ });
485
+ });
486
+ describe("selectNextTaskGroup", () => {
487
+ it("実行可能なタスクがある場合、最初のタスクグループを返す", async () => {
488
+ // Given: 依存を満たす複数のタスクグループ
489
+ const issue = {
490
+ number: 1,
491
+ title: "Test",
492
+ state: "open",
493
+ body: `### Phase 1: フェーズ1
494
+
495
+ - [x] **1.1 タスク1.1**
496
+ **依存関係**: なし
497
+ **完了条件**: 完了
498
+
499
+ - [ ] **1.2 タスク1.2**
500
+ **依存関係**: 1.1
501
+ **完了条件**: 完了
502
+
503
+ - [ ] **1.3 タスク1.3**
504
+ **依存関係**: 1.1
505
+ **完了条件**: 完了
506
+ `,
507
+ };
508
+ const parsedIssue = parseIssueBody(issue);
509
+ // When: 次のタスクグループを選定
510
+ const result = await selectNextTaskGroup(parsedIssue);
511
+ // Then: 最初の実行可能タスクグループが返る
512
+ expect(result).not.toBeNull();
513
+ expect(result === null || result === void 0 ? void 0 : result.id).toBe("1.2");
514
+ });
515
+ it("maxTaskNumberで範囲外のタスクは除外される", async () => {
516
+ // Given: 複数のタスクグループ
517
+ const issue = {
518
+ number: 1,
519
+ title: "Test",
520
+ state: "open",
521
+ body: `### Phase 1: フェーズ1
522
+
523
+ - [ ] **1.1 タスク1.1**
524
+ **依存関係**: なし
525
+ **完了条件**: 完了
526
+
527
+ - [ ] **1.2 タスク1.2**
528
+ **依存関係**: なし
529
+ **完了条件**: 完了
530
+
531
+ - [ ] **1.3 タスク1.3**
532
+ **依存関係**: なし
533
+ **完了条件**: 完了
534
+ `,
535
+ };
536
+ const parsedIssue = parseIssueBody(issue);
537
+ // When: maxTaskNumberを指定して次のタスクグループを選定
538
+ const result = await selectNextTaskGroup(parsedIssue, "1.2");
539
+ // Then: 1.2以下の最初のタスクグループが返る
540
+ expect(result).not.toBeNull();
541
+ expect(result === null || result === void 0 ? void 0 : result.id).toBe("1.1");
542
+ });
543
+ it("依存関係が未解決のタスクは除外される", async () => {
544
+ // Given: 依存関係が未解決のタスク
545
+ const issue = {
546
+ number: 1,
547
+ title: "Test",
548
+ state: "open",
549
+ body: `### Phase 1: フェーズ1
550
+
551
+ - [ ] **1.1 タスク1.1**
552
+ **依存関係**: なし
553
+ **完了条件**: 完了
554
+
555
+ - [ ] **1.2 タスク1.2**
556
+ **依存関係**: 1.1
557
+ **完了条件**: 完了
558
+ `,
559
+ };
560
+ const parsedIssue = parseIssueBody(issue);
561
+ // When: 次のタスクグループを選定
562
+ const result = await selectNextTaskGroup(parsedIssue);
563
+ // Then: 依存のないタスクのみ返る(1.2は除外される)
564
+ expect(result).not.toBeNull();
565
+ expect(result === null || result === void 0 ? void 0 : result.id).toBe("1.1");
566
+ });
567
+ it("全て完了済みの場合、nullを返す", async () => {
568
+ // Given: 全て完了済みのIssue
569
+ const issue = {
570
+ number: 1,
571
+ title: "Test",
572
+ state: "open",
573
+ body: `### Phase 1: フェーズ1
574
+
575
+ - [x] **1.1 タスク1.1**
576
+ **依存関係**: なし
577
+ **完了条件**: 完了
578
+
579
+ - [x] **1.2 タスク1.2**
580
+ **依存関係**: 1.1
581
+ **完了条件**: 完了
582
+ `,
583
+ };
584
+ const parsedIssue = parseIssueBody(issue);
585
+ // When: 次のタスクグループを選定
586
+ const result = await selectNextTaskGroup(parsedIssue);
587
+ // Then: nullが返る
588
+ expect(result).toBeNull();
589
+ });
590
+ });
591
+ describe("selectExecutableTaskGroups", () => {
592
+ it("依存を満たす複数タスクの場合、実行可能配列を返す", async () => {
593
+ // Given: 依存を満たす複数のタスクグループ
594
+ const issue = {
595
+ number: 1,
596
+ title: "Test",
597
+ state: "open",
598
+ body: `### Phase 1: フェーズ1
599
+
600
+ - [x] **1.1 タスク1.1**
601
+ **依存関係**: なし
602
+ **完了条件**: 完了
603
+
604
+ - [ ] **1.2 タスク1.2**
605
+ **依存関係**: 1.1
606
+ **完了条件**: 完了
607
+
608
+ - [ ] **1.3 タスク1.3**
609
+ **依存関係**: 1.1
610
+ **完了条件**: 完了
611
+ `,
612
+ };
613
+ const parsedIssue = parseIssueBody(issue);
614
+ // When: 実行可能タスクグループを選定
615
+ const result = await selectExecutableTaskGroups(parsedIssue);
616
+ // Then: 依存を満たすタスクグループが返る
617
+ expect(result).toHaveLength(2);
618
+ expect(result[0].id).toBe("1.2");
619
+ expect(result[1].id).toBe("1.3");
620
+ });
621
+ it("maxTaskNumber='1.2'の場合、1.2以下のみ返す", async () => {
622
+ // Given: 複数のタスクグループ
623
+ const issue = {
624
+ number: 1,
625
+ title: "Test",
626
+ state: "open",
627
+ body: `### Phase 1: フェーズ1
628
+
629
+ - [ ] **1.1 タスク1.1**
630
+ **依存関係**: なし
631
+ **完了条件**: 完了
632
+
633
+ - [ ] **1.2 タスク1.2**
634
+ **依存関係**: なし
635
+ **完了条件**: 完了
636
+
637
+ - [ ] **1.3 タスク1.3**
638
+ **依存関係**: なし
639
+ **完了条件**: 完了
640
+ `,
641
+ };
642
+ const parsedIssue = parseIssueBody(issue);
643
+ // When: maxTaskNumberを指定して実行可能タスクグループを選定
644
+ const result = await selectExecutableTaskGroups(parsedIssue, "1.2");
645
+ // Then: 1.2以下のタスクグループのみ返る
646
+ expect(result).toHaveLength(2);
647
+ expect(result[0].id).toBe("1.1");
648
+ expect(result[1].id).toBe("1.2");
649
+ });
650
+ it("全て完了済みの場合、空配列を返す", async () => {
651
+ // Given: 全て完了済みのIssue
652
+ const issue = {
653
+ number: 1,
654
+ title: "Test",
655
+ state: "open",
656
+ body: `### Phase 1: フェーズ1
657
+
658
+ - [x] **1.1 タスク1.1**
659
+ **依存関係**: なし
660
+ **完了条件**: 完了
661
+
662
+ - [x] **1.2 タスク1.2**
663
+ **依存関係**: 1.1
664
+ **完了条件**: 完了
665
+ `,
666
+ };
667
+ const parsedIssue = parseIssueBody(issue);
668
+ // When: 実行可能タスクグループを選定
669
+ const result = await selectExecutableTaskGroups(parsedIssue);
670
+ // Then: 空配列が返る
671
+ expect(result).toHaveLength(0);
672
+ });
673
+ it("依存が満たされていないタスクは除外される", async () => {
674
+ // Given: 依存が満たされていないタスク
675
+ const issue = {
676
+ number: 1,
677
+ title: "Test",
678
+ state: "open",
679
+ body: `### Phase 1: フェーズ1
680
+
681
+ - [ ] **1.1 タスク1.1**
682
+ **依存関係**: なし
683
+ **完了条件**: 完了
684
+
685
+ - [ ] **1.2 タスク1.2**
686
+ **依存関係**: 1.1
687
+ **完了条件**: 完了
688
+ `,
689
+ };
690
+ const parsedIssue = parseIssueBody(issue);
691
+ // When: 実行可能タスクグループを選定
692
+ const result = await selectExecutableTaskGroups(parsedIssue);
693
+ // Then: 依存のないタスクのみ返る
694
+ expect(result).toHaveLength(1);
695
+ expect(result[0].id).toBe("1.1");
696
+ });
697
+ it("外部Issue依存がクローズ済みの場合、実行可能に含まれる", async () => {
698
+ // Given: 外部Issue依存を持つタスク
699
+ const issue = {
700
+ number: 1,
701
+ title: "Test",
702
+ state: "open",
703
+ body: `### Phase 1: フェーズ1
704
+
705
+ - [ ] **1.1 タスク1.1**
706
+ **依存関係**: #123
707
+ **完了条件**: 完了
708
+ `,
709
+ };
710
+ const parsedIssue = parseIssueBody(issue);
711
+ vi.mocked(isIssueClosed).mockReturnValue(true);
712
+ // When: 実行可能タスクグループを選定
713
+ const result = await selectExecutableTaskGroups(parsedIssue);
714
+ // Then: 実行可能に含まれる
715
+ expect(result).toHaveLength(1);
716
+ expect(result[0].id).toBe("1.1");
717
+ expect(isIssueClosed).toHaveBeenCalledWith(123);
718
+ });
719
+ it("外部Issue依存がオープンの場合、実行可能から除外される", async () => {
720
+ // Given: 外部Issue依存を持つタスク(Issue はオープン)
721
+ const issue = {
722
+ number: 1,
723
+ title: "Test",
724
+ state: "open",
725
+ body: `### Phase 1: フェーズ1
726
+
727
+ - [ ] **1.1 タスク1.1**
728
+ **依存関係**: #123
729
+ **完了条件**: 完了
730
+ `,
731
+ };
732
+ const parsedIssue = parseIssueBody(issue);
733
+ vi.mocked(isIssueClosed).mockReturnValue(false);
734
+ // When: 実行可能タスクグループを選定
735
+ const result = await selectExecutableTaskGroups(parsedIssue);
736
+ // Then: 実行可能から除外される
737
+ expect(result).toHaveLength(0);
738
+ expect(isIssueClosed).toHaveBeenCalledWith(123);
739
+ });
740
+ it("in-progressステータスのタスクも候補に含まれる", async () => {
741
+ // Given: in-progressステータスのタスク
742
+ const parsedIssue = {
743
+ issueNumber: 1,
744
+ title: "Test",
745
+ body: "",
746
+ phases: [
747
+ {
748
+ number: 1,
749
+ name: "Phase 1",
750
+ taskGroups: [
751
+ {
752
+ id: "1.1",
753
+ name: "タスク1.1",
754
+ phaseNumber: 1,
755
+ status: "in-progress",
756
+ dependencies: [],
757
+ completionCriteria: "完了",
758
+ tasks: [],
759
+ },
760
+ ],
761
+ },
762
+ ],
763
+ };
764
+ // When: 実行可能タスクグループを選定
765
+ const result = await selectExecutableTaskGroups(parsedIssue);
766
+ // Then: in-progressタスクが含まれる
767
+ expect(result).toHaveLength(1);
768
+ expect(result[0].id).toBe("1.1");
769
+ });
770
+ it("タスク番号順にソートされて返される", async () => {
771
+ // Given: タスク番号がバラバラのタスクグループ
772
+ const issue = {
773
+ number: 1,
774
+ title: "Test",
775
+ state: "open",
776
+ body: `### Phase 1: フェーズ1
777
+
778
+ - [ ] **1.3 タスク1.3**
779
+ **依存関係**: なし
780
+ **完了条件**: 完了
781
+
782
+ - [ ] **1.1 タスク1.1**
783
+ **依存関係**: なし
784
+ **完了条件**: 完了
785
+
786
+ - [ ] **1.2 タスク1.2**
787
+ **依存関係**: なし
788
+ **完了条件**: 完了
789
+ `,
790
+ };
791
+ const parsedIssue = parseIssueBody(issue);
792
+ // When: 実行可能タスクグループを選定
793
+ const result = await selectExecutableTaskGroups(parsedIssue);
794
+ // Then: タスク番号順にソートされて返る
795
+ expect(result).toHaveLength(3);
796
+ expect(result[0].id).toBe("1.1");
797
+ expect(result[1].id).toBe("1.2");
798
+ expect(result[2].id).toBe("1.3");
799
+ });
800
+ });
801
+ describe("isAllTasksCompleted", () => {
802
+ it("全タスクが完了済みの場合、trueを返す", () => {
803
+ // Given: 全タスクが完了済みのIssue
804
+ const issue = {
805
+ number: 1,
806
+ title: "Test",
807
+ state: "open",
808
+ body: `### Phase 1: フェーズ1
809
+
810
+ - [x] **1.1 タスク1.1**
811
+ **依存関係**: なし
812
+ **完了条件**: 完了
813
+
814
+ - [x] **1.2 タスク1.2**
815
+ **依存関係**: 1.1
816
+ **完了条件**: 完了
817
+
818
+ ### Phase 2: フェーズ2
819
+
820
+ - [x] **2.1 タスク2.1**
821
+ **依存関係**: Phase 1完了
822
+ **完了条件**: 完了
823
+ `,
824
+ };
825
+ const parsedIssue = parseIssueBody(issue);
826
+ // When: 全タスク完了チェック
827
+ const result = isAllTasksCompleted(parsedIssue);
828
+ // Then: trueが返る
829
+ expect(result).toBe(true);
830
+ });
831
+ it("未完了タスクがある場合、falseを返す", () => {
832
+ // Given: 未完了タスクがあるIssue
833
+ const issue = {
834
+ number: 1,
835
+ title: "Test",
836
+ state: "open",
837
+ body: `### Phase 1: フェーズ1
838
+
839
+ - [x] **1.1 タスク1.1**
840
+ **依存関係**: なし
841
+ **完了条件**: 完了
842
+
843
+ - [ ] **1.2 タスク1.2**
844
+ **依存関係**: 1.1
845
+ **完了条件**: 完了
846
+ `,
847
+ };
848
+ const parsedIssue = parseIssueBody(issue);
849
+ // When: 全タスク完了チェック
850
+ const result = isAllTasksCompleted(parsedIssue);
851
+ // Then: falseが返る
852
+ expect(result).toBe(false);
853
+ });
854
+ it("maxTaskNumber範囲内の全タスクが完了の場合、trueを返す", () => {
855
+ // Given: 1.2以下が完了、1.3が未完了のIssue
856
+ const issue = {
857
+ number: 1,
858
+ title: "Test",
859
+ state: "open",
860
+ body: `### Phase 1: フェーズ1
861
+
862
+ - [x] **1.1 タスク1.1**
863
+ **依存関係**: なし
864
+ **完了条件**: 完了
865
+
866
+ - [x] **1.2 タスク1.2**
867
+ **依存関係**: 1.1
868
+ **完了条件**: 完了
869
+
870
+ - [ ] **1.3 タスク1.3**
871
+ **依存関係**: 1.2
872
+ **完了条件**: 完了
873
+ `,
874
+ };
875
+ const parsedIssue = parseIssueBody(issue);
876
+ // When: maxTaskNumber=1.2で全タスク完了チェック
877
+ const result = isAllTasksCompleted(parsedIssue, "1.2");
878
+ // Then: trueが返る(1.3は範囲外なので無視される)
879
+ expect(result).toBe(true);
880
+ });
881
+ it("maxTaskNumber範囲内に未完了タスクがある場合、falseを返す", () => {
882
+ // Given: 1.2が未完了、1.3以降は完了のIssue
883
+ const issue = {
884
+ number: 1,
885
+ title: "Test",
886
+ state: "open",
887
+ body: `### Phase 1: フェーズ1
888
+
889
+ - [x] **1.1 タスク1.1**
890
+ **依存関係**: なし
891
+ **完了条件**: 完了
892
+
893
+ - [ ] **1.2 タスク1.2**
894
+ **依存関係**: 1.1
895
+ **完了条件**: 完了
896
+
897
+ - [x] **1.3 タスク1.3**
898
+ **依存関係**: 1.2
899
+ **完了条件**: 完了
900
+ `,
901
+ };
902
+ const parsedIssue = parseIssueBody(issue);
903
+ // When: maxTaskNumber=1.2で全タスク完了チェック
904
+ const result = isAllTasksCompleted(parsedIssue, "1.2");
905
+ // Then: falseが返る(1.2が未完了)
906
+ expect(result).toBe(false);
907
+ });
908
+ it("タスクグループが存在しない場合、trueを返す", () => {
909
+ // Given: タスクグループが空のIssue
910
+ const parsedIssue = {
911
+ issueNumber: 1,
912
+ title: "Test",
913
+ body: "",
914
+ phases: [],
915
+ };
916
+ // When: 全タスク完了チェック
917
+ const result = isAllTasksCompleted(parsedIssue);
918
+ // Then: trueが返る(タスクが存在しない=全て完了と見なす)
919
+ expect(result).toBe(true);
920
+ });
921
+ });
922
+ describe("detectCircularDependencies", () => {
923
+ it("循環なしの場合、nullを返す", () => {
924
+ // Given: 循環のない依存関係
925
+ const issue = {
926
+ number: 1,
927
+ title: "Test",
928
+ state: "open",
929
+ body: `### Phase 1: フェーズ1
930
+
931
+ - [ ] **1.1 タスク1.1**
932
+ **依存関係**: なし
933
+ **完了条件**: 完了
934
+
935
+ - [ ] **1.2 タスク1.2**
936
+ **依存関係**: 1.1
937
+ **完了条件**: 完了
938
+
939
+ - [ ] **1.3 タスク1.3**
940
+ **依存関係**: 1.2
941
+ **完了条件**: 完了
942
+ `,
943
+ };
944
+ const parsedIssue = parseIssueBody(issue);
945
+ // When: 循環依存をチェック
946
+ const result = detectCircularDependencies(parsedIssue);
947
+ // Then: nullが返る
948
+ expect(result).toBeNull();
949
+ });
950
+ it("A→B→Aの2ノード循環の場合、循環パスを返す", () => {
951
+ // Given: 2ノード循環依存
952
+ const issue = {
953
+ number: 1,
954
+ title: "Test",
955
+ state: "open",
956
+ body: `### Phase 1: フェーズ1
957
+
958
+ - [ ] **1.1 タスクA**
959
+ **依存関係**: 1.2
960
+ **完了条件**: 完了
961
+
962
+ - [ ] **1.2 タスクB**
963
+ **依存関係**: 1.1
964
+ **完了条件**: 完了
965
+ `,
966
+ };
967
+ const parsedIssue = parseIssueBody(issue);
968
+ // When: 循環依存をチェック
969
+ const result = detectCircularDependencies(parsedIssue);
970
+ // Then: 循環パスが返る
971
+ expect(result).not.toBeNull();
972
+ expect(result).toContain("1.1");
973
+ expect(result).toContain("1.2");
974
+ });
975
+ it("A→B→C→Aの3ノード循環の場合、循環パスを返す", () => {
976
+ // Given: 3ノード循環依存
977
+ const issue = {
978
+ number: 1,
979
+ title: "Test",
980
+ state: "open",
981
+ body: `### Phase 1: フェーズ1
982
+
983
+ - [ ] **1.1 タスクA**
984
+ **依存関係**: 1.2
985
+ **完了条件**: 完了
986
+
987
+ - [ ] **1.2 タスクB**
988
+ **依存関係**: 1.3
989
+ **完了条件**: 完了
990
+
991
+ - [ ] **1.3 タスクC**
992
+ **依存関係**: 1.1
993
+ **完了条件**: 完了
994
+ `,
995
+ };
996
+ const parsedIssue = parseIssueBody(issue);
997
+ // When: 循環依存をチェック
998
+ const result = detectCircularDependencies(parsedIssue);
999
+ // Then: 循環パスが返る
1000
+ expect(result).not.toBeNull();
1001
+ expect(result).toContain("1.1");
1002
+ expect(result).toContain("1.2");
1003
+ expect(result).toContain("1.3");
1004
+ });
1005
+ it("自己参照依存は循環として検出される", () => {
1006
+ // Given: 自己参照依存(1.1 → 1.1)
1007
+ const parsedIssue = {
1008
+ issueNumber: 1,
1009
+ title: "Test",
1010
+ body: "",
1011
+ phases: [
1012
+ {
1013
+ number: 1,
1014
+ name: "Phase 1",
1015
+ taskGroups: [
1016
+ {
1017
+ id: "1.1",
1018
+ name: "タスク1.1",
1019
+ phaseNumber: 1,
1020
+ status: "pending",
1021
+ dependencies: ["1.1"],
1022
+ completionCriteria: "完了",
1023
+ tasks: [],
1024
+ },
1025
+ ],
1026
+ },
1027
+ ],
1028
+ };
1029
+ // When: 循環依存をチェック
1030
+ const result = detectCircularDependencies(parsedIssue);
1031
+ // Then: 循環パスが返る
1032
+ expect(result).not.toBeNull();
1033
+ expect(result).toContain("1.1");
1034
+ });
1035
+ it("Phase依存やIssue依存は循環検出の対象外", () => {
1036
+ // Given: Phase依存とIssue依存を含むタスク
1037
+ const issue = {
1038
+ number: 1,
1039
+ title: "Test",
1040
+ state: "open",
1041
+ body: `### Phase 1: フェーズ1
1042
+
1043
+ - [ ] **1.1 タスク1.1**
1044
+ **依存関係**: なし
1045
+ **完了条件**: 完了
1046
+
1047
+ ### Phase 2: フェーズ2
1048
+
1049
+ - [ ] **2.1 タスク2.1**
1050
+ **依存関係**: Phase 1完了, #123
1051
+ **完了条件**: 完了
1052
+ `,
1053
+ };
1054
+ const parsedIssue = parseIssueBody(issue);
1055
+ // When: 循環依存をチェック
1056
+ const result = detectCircularDependencies(parsedIssue);
1057
+ // Then: nullが返る(タスクグループ依存のみが対象)
1058
+ expect(result).toBeNull();
1059
+ });
1060
+ it("複数の独立した依存チェーンがある場合、循環がなければnullを返す", () => {
1061
+ // Given: 複数の独立した依存チェーン
1062
+ const issue = {
1063
+ number: 1,
1064
+ title: "Test",
1065
+ state: "open",
1066
+ body: `### Phase 1: フェーズ1
1067
+
1068
+ - [ ] **1.1 タスク1.1**
1069
+ **依存関係**: なし
1070
+ **完了条件**: 完了
1071
+
1072
+ - [ ] **1.2 タスク1.2**
1073
+ **依存関係**: 1.1
1074
+ **完了条件**: 完了
1075
+
1076
+ ### Phase 2: フェーズ2
1077
+
1078
+ - [ ] **2.1 タスク2.1**
1079
+ **依存関係**: なし
1080
+ **完了条件**: 完了
1081
+
1082
+ - [ ] **2.2 タスク2.2**
1083
+ **依存関係**: 2.1
1084
+ **完了条件**: 完了
1085
+ `,
1086
+ };
1087
+ const parsedIssue = parseIssueBody(issue);
1088
+ // When: 循環依存をチェック
1089
+ const result = detectCircularDependencies(parsedIssue);
1090
+ // Then: nullが返る
1091
+ expect(result).toBeNull();
1092
+ });
1093
+ it("部分的な循環がある場合、循環パスを返す", () => {
1094
+ // Given: 一部のタスクで循環依存が発生
1095
+ const issue = {
1096
+ number: 1,
1097
+ title: "Test",
1098
+ state: "open",
1099
+ body: `### Phase 1: フェーズ1
1100
+
1101
+ - [ ] **1.1 タスク1.1**
1102
+ **依存関係**: なし
1103
+ **完了条件**: 完了
1104
+
1105
+ - [ ] **1.2 タスク1.2**
1106
+ **依存関係**: 1.3
1107
+ **完了条件**: 完了
1108
+
1109
+ - [ ] **1.3 タスク1.3**
1110
+ **依存関係**: 1.4
1111
+ **完了条件**: 完了
1112
+
1113
+ - [ ] **1.4 タスク1.4**
1114
+ **依存関係**: 1.2
1115
+ **完了条件**: 完了
1116
+ `,
1117
+ };
1118
+ const parsedIssue = parseIssueBody(issue);
1119
+ // When: 循環依存をチェック
1120
+ const result = detectCircularDependencies(parsedIssue);
1121
+ // Then: 循環パスが返る(1.2 → 1.3 → 1.4 → 1.2)
1122
+ expect(result).not.toBeNull();
1123
+ expect(result).toContain("1.2");
1124
+ expect(result).toContain("1.3");
1125
+ expect(result).toContain("1.4");
1126
+ });
1127
+ });
1128
+ });
1129
+ //# sourceMappingURL=dependency-resolver.test.js.map