@agile-vibe-coding/avc 0.2.3 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +475 -3
- package/cli/agents/agent-selector.md +23 -0
- package/cli/agents/code-implementer.md +117 -0
- package/cli/agents/code-validator.md +80 -0
- package/cli/agents/context-reviewer-epic.md +101 -0
- package/cli/agents/context-reviewer-story.md +92 -0
- package/cli/agents/context-writer-epic.md +145 -0
- package/cli/agents/context-writer-story.md +111 -0
- package/cli/agents/doc-writer-epic.md +42 -0
- package/cli/agents/doc-writer-story.md +43 -0
- package/cli/agents/duplicate-detector.md +110 -0
- package/cli/agents/epic-story-decomposer.md +318 -39
- package/cli/agents/mission-scope-generator.md +68 -4
- package/cli/agents/mission-scope-validator.md +40 -6
- package/cli/agents/project-context-extractor.md +21 -6
- package/cli/agents/scaffolding-generator.md +99 -0
- package/cli/agents/seed-validator.md +71 -0
- package/cli/agents/story-scope-reviewer.md +147 -0
- package/cli/agents/story-splitter.md +83 -0
- package/cli/agents/validator-documentation.json +31 -0
- package/cli/agents/validator-documentation.md +3 -1
- package/cli/api-reference-tool.js +368 -0
- package/cli/checks/catalog.json +76 -0
- package/cli/checks/code/quality.json +26 -0
- package/cli/checks/code/testing.json +14 -0
- package/cli/checks/code/traceability.json +26 -0
- package/cli/checks/cross-refs/epic.json +171 -0
- package/cli/checks/cross-refs/story.json +149 -0
- package/cli/checks/epic/api.json +114 -0
- package/cli/checks/epic/backend.json +126 -0
- package/cli/checks/epic/cloud.json +126 -0
- package/cli/checks/epic/data.json +102 -0
- package/cli/checks/epic/database.json +114 -0
- package/cli/checks/epic/developer.json +182 -0
- package/cli/checks/epic/devops.json +174 -0
- package/cli/checks/epic/frontend.json +162 -0
- package/cli/checks/epic/mobile.json +102 -0
- package/cli/checks/epic/qa.json +90 -0
- package/cli/checks/epic/security.json +184 -0
- package/cli/checks/epic/solution-architect.json +192 -0
- package/cli/checks/epic/test-architect.json +90 -0
- package/cli/checks/epic/ui.json +102 -0
- package/cli/checks/epic/ux.json +90 -0
- package/cli/checks/fixes/epic-fix-template.md +10 -0
- package/cli/checks/fixes/story-fix-template.md +10 -0
- package/cli/checks/story/api.json +186 -0
- package/cli/checks/story/backend.json +102 -0
- package/cli/checks/story/cloud.json +102 -0
- package/cli/checks/story/data.json +210 -0
- package/cli/checks/story/database.json +102 -0
- package/cli/checks/story/developer.json +168 -0
- package/cli/checks/story/devops.json +102 -0
- package/cli/checks/story/frontend.json +174 -0
- package/cli/checks/story/mobile.json +102 -0
- package/cli/checks/story/qa.json +210 -0
- package/cli/checks/story/security.json +198 -0
- package/cli/checks/story/solution-architect.json +230 -0
- package/cli/checks/story/test-architect.json +210 -0
- package/cli/checks/story/ui.json +102 -0
- package/cli/checks/story/ux.json +102 -0
- package/cli/coding-order.js +401 -0
- package/cli/dependency-checker.js +72 -0
- package/cli/epic-story-validator.js +284 -799
- package/cli/index.js +0 -0
- package/cli/init-model-config.js +17 -10
- package/cli/init.js +514 -92
- package/cli/kanban-server-manager.js +1 -2
- package/cli/llm-claude.js +98 -31
- package/cli/llm-gemini.js +29 -5
- package/cli/llm-local.js +493 -0
- package/cli/llm-openai.js +262 -41
- package/cli/llm-provider.js +147 -8
- package/cli/llm-token-limits.js +113 -4
- package/cli/llm-verifier.js +209 -1
- package/cli/llm-xiaomi.js +143 -0
- package/cli/message-constants.js +3 -12
- package/cli/messaging-api.js +6 -12
- package/cli/micro-check-fixer.js +335 -0
- package/cli/micro-check-runner.js +449 -0
- package/cli/micro-check-scorer.js +148 -0
- package/cli/micro-check-validator.js +538 -0
- package/cli/model-pricing.js +23 -0
- package/cli/model-selector.js +3 -2
- package/cli/prompt-logger.js +57 -0
- package/cli/repl-ink.js +106 -346
- package/cli/repl-old.js +1 -2
- package/cli/seed-processor.js +194 -24
- package/cli/sprint-planning-processor.js +2638 -289
- package/cli/template-processor.js +50 -3
- package/cli/token-tracker.js +50 -23
- package/cli/tools/generate-story-validators.js +1 -1
- package/cli/validation-router.js +70 -8
- package/cli/worktree-runner.js +654 -0
- package/kanban/client/dist/assets/index-D_KC5EQT.css +1 -0
- package/kanban/client/dist/assets/index-DjY5zqW7.js +351 -0
- package/kanban/client/dist/index.html +2 -2
- package/kanban/client/src/App.jsx +43 -14
- package/kanban/client/src/components/ceremony/AskArchPopup.jsx +7 -3
- package/kanban/client/src/components/ceremony/AskModelPopup.jsx +23 -10
- package/kanban/client/src/components/ceremony/CeremonyWorkflowModal.jsx +320 -133
- package/kanban/client/src/components/ceremony/ProviderSwitcherButton.jsx +290 -0
- package/kanban/client/src/components/ceremony/SponsorCallModal.jsx +80 -13
- package/kanban/client/src/components/ceremony/SprintPlanningModal.jsx +156 -22
- package/kanban/client/src/components/ceremony/steps/ArchitectureStep.jsx +11 -11
- package/kanban/client/src/components/ceremony/steps/CompleteStep.jsx +3 -21
- package/kanban/client/src/components/ceremony/steps/ReviewAnswersStep.jsx +214 -10
- package/kanban/client/src/components/ceremony/steps/RunningStep.jsx +23 -2
- package/kanban/client/src/components/kanban/CardDetailModal.jsx +97 -10
- package/kanban/client/src/components/kanban/GroupingSelector.jsx +7 -1
- package/kanban/client/src/components/kanban/KanbanCard.jsx +23 -14
- package/kanban/client/src/components/kanban/RefineWorkItemPopup.jsx +9 -14
- package/kanban/client/src/components/kanban/RunButton.jsx +162 -0
- package/kanban/client/src/components/kanban/SeedButton.jsx +176 -0
- package/kanban/client/src/components/settings/AgentsTab.jsx +103 -75
- package/kanban/client/src/components/settings/ApiKeysTab.jsx +31 -2
- package/kanban/client/src/components/settings/CeremonyModelsTab.jsx +9 -2
- package/kanban/client/src/components/settings/CheckEditorPopup.jsx +507 -0
- package/kanban/client/src/components/settings/CostThresholdsTab.jsx +3 -2
- package/kanban/client/src/components/settings/ModelPricingTab.jsx +72 -7
- package/kanban/client/src/components/settings/OpenAIAuthSection.jsx +412 -0
- package/kanban/client/src/components/settings/SettingsModal.jsx +4 -4
- package/kanban/client/src/components/stats/CostModal.jsx +34 -3
- package/kanban/client/src/hooks/useGrouping.js +59 -0
- package/kanban/client/src/lib/api.js +118 -4
- package/kanban/client/src/lib/status-grouping.js +10 -0
- package/kanban/client/src/store/kanbanStore.js +8 -0
- package/kanban/server/index.js +23 -2
- package/kanban/server/routes/ceremony.js +153 -4
- package/kanban/server/routes/costs.js +9 -3
- package/kanban/server/routes/openai-oauth.js +366 -0
- package/kanban/server/routes/settings.js +447 -14
- package/kanban/server/routes/websocket.js +7 -2
- package/kanban/server/routes/work-items.js +141 -1
- package/kanban/server/services/CeremonyService.js +275 -24
- package/kanban/server/services/TaskRunnerService.js +261 -0
- package/kanban/server/workers/run-task-worker.js +121 -0
- package/kanban/server/workers/seed-worker.js +94 -0
- package/kanban/server/workers/sponsor-call-worker.js +14 -6
- package/kanban/server/workers/sprint-planning-worker.js +94 -12
- package/package.json +2 -3
- package/cli/agents/solver-epic-api.json +0 -15
- package/cli/agents/solver-epic-api.md +0 -39
- package/cli/agents/solver-epic-backend.json +0 -15
- package/cli/agents/solver-epic-backend.md +0 -39
- package/cli/agents/solver-epic-cloud.json +0 -15
- package/cli/agents/solver-epic-cloud.md +0 -39
- package/cli/agents/solver-epic-data.json +0 -15
- package/cli/agents/solver-epic-data.md +0 -39
- package/cli/agents/solver-epic-database.json +0 -15
- package/cli/agents/solver-epic-database.md +0 -39
- package/cli/agents/solver-epic-developer.json +0 -15
- package/cli/agents/solver-epic-developer.md +0 -39
- package/cli/agents/solver-epic-devops.json +0 -15
- package/cli/agents/solver-epic-devops.md +0 -39
- package/cli/agents/solver-epic-frontend.json +0 -15
- package/cli/agents/solver-epic-frontend.md +0 -39
- package/cli/agents/solver-epic-mobile.json +0 -15
- package/cli/agents/solver-epic-mobile.md +0 -39
- package/cli/agents/solver-epic-qa.json +0 -15
- package/cli/agents/solver-epic-qa.md +0 -39
- package/cli/agents/solver-epic-security.json +0 -15
- package/cli/agents/solver-epic-security.md +0 -39
- package/cli/agents/solver-epic-solution-architect.json +0 -15
- package/cli/agents/solver-epic-solution-architect.md +0 -39
- package/cli/agents/solver-epic-test-architect.json +0 -15
- package/cli/agents/solver-epic-test-architect.md +0 -39
- package/cli/agents/solver-epic-ui.json +0 -15
- package/cli/agents/solver-epic-ui.md +0 -39
- package/cli/agents/solver-epic-ux.json +0 -15
- package/cli/agents/solver-epic-ux.md +0 -39
- package/cli/agents/solver-story-api.json +0 -15
- package/cli/agents/solver-story-api.md +0 -39
- package/cli/agents/solver-story-backend.json +0 -15
- package/cli/agents/solver-story-backend.md +0 -39
- package/cli/agents/solver-story-cloud.json +0 -15
- package/cli/agents/solver-story-cloud.md +0 -39
- package/cli/agents/solver-story-data.json +0 -15
- package/cli/agents/solver-story-data.md +0 -39
- package/cli/agents/solver-story-database.json +0 -15
- package/cli/agents/solver-story-database.md +0 -39
- package/cli/agents/solver-story-developer.json +0 -15
- package/cli/agents/solver-story-developer.md +0 -39
- package/cli/agents/solver-story-devops.json +0 -15
- package/cli/agents/solver-story-devops.md +0 -39
- package/cli/agents/solver-story-frontend.json +0 -15
- package/cli/agents/solver-story-frontend.md +0 -39
- package/cli/agents/solver-story-mobile.json +0 -15
- package/cli/agents/solver-story-mobile.md +0 -39
- package/cli/agents/solver-story-qa.json +0 -15
- package/cli/agents/solver-story-qa.md +0 -39
- package/cli/agents/solver-story-security.json +0 -15
- package/cli/agents/solver-story-security.md +0 -39
- package/cli/agents/solver-story-solution-architect.json +0 -15
- package/cli/agents/solver-story-solution-architect.md +0 -39
- package/cli/agents/solver-story-test-architect.json +0 -15
- package/cli/agents/solver-story-test-architect.md +0 -39
- package/cli/agents/solver-story-ui.json +0 -15
- package/cli/agents/solver-story-ui.md +0 -39
- package/cli/agents/solver-story-ux.json +0 -15
- package/cli/agents/solver-story-ux.md +0 -39
- package/cli/agents/validator-epic-api.json +0 -93
- package/cli/agents/validator-epic-api.md +0 -137
- package/cli/agents/validator-epic-backend.json +0 -93
- package/cli/agents/validator-epic-backend.md +0 -130
- package/cli/agents/validator-epic-cloud.json +0 -93
- package/cli/agents/validator-epic-cloud.md +0 -137
- package/cli/agents/validator-epic-data.json +0 -93
- package/cli/agents/validator-epic-data.md +0 -130
- package/cli/agents/validator-epic-database.json +0 -93
- package/cli/agents/validator-epic-database.md +0 -137
- package/cli/agents/validator-epic-developer.json +0 -74
- package/cli/agents/validator-epic-developer.md +0 -153
- package/cli/agents/validator-epic-devops.json +0 -74
- package/cli/agents/validator-epic-devops.md +0 -153
- package/cli/agents/validator-epic-frontend.json +0 -74
- package/cli/agents/validator-epic-frontend.md +0 -153
- package/cli/agents/validator-epic-mobile.json +0 -93
- package/cli/agents/validator-epic-mobile.md +0 -130
- package/cli/agents/validator-epic-qa.json +0 -93
- package/cli/agents/validator-epic-qa.md +0 -130
- package/cli/agents/validator-epic-security.json +0 -74
- package/cli/agents/validator-epic-security.md +0 -154
- package/cli/agents/validator-epic-solution-architect.json +0 -74
- package/cli/agents/validator-epic-solution-architect.md +0 -156
- package/cli/agents/validator-epic-test-architect.json +0 -93
- package/cli/agents/validator-epic-test-architect.md +0 -130
- package/cli/agents/validator-epic-ui.json +0 -93
- package/cli/agents/validator-epic-ui.md +0 -130
- package/cli/agents/validator-epic-ux.json +0 -93
- package/cli/agents/validator-epic-ux.md +0 -130
- package/cli/agents/validator-story-api.json +0 -104
- package/cli/agents/validator-story-api.md +0 -152
- package/cli/agents/validator-story-backend.json +0 -104
- package/cli/agents/validator-story-backend.md +0 -152
- package/cli/agents/validator-story-cloud.json +0 -104
- package/cli/agents/validator-story-cloud.md +0 -152
- package/cli/agents/validator-story-data.json +0 -104
- package/cli/agents/validator-story-data.md +0 -152
- package/cli/agents/validator-story-database.json +0 -104
- package/cli/agents/validator-story-database.md +0 -152
- package/cli/agents/validator-story-developer.json +0 -104
- package/cli/agents/validator-story-developer.md +0 -152
- package/cli/agents/validator-story-devops.json +0 -104
- package/cli/agents/validator-story-devops.md +0 -152
- package/cli/agents/validator-story-frontend.json +0 -104
- package/cli/agents/validator-story-frontend.md +0 -152
- package/cli/agents/validator-story-mobile.json +0 -104
- package/cli/agents/validator-story-mobile.md +0 -152
- package/cli/agents/validator-story-qa.json +0 -104
- package/cli/agents/validator-story-qa.md +0 -152
- package/cli/agents/validator-story-security.json +0 -104
- package/cli/agents/validator-story-security.md +0 -152
- package/cli/agents/validator-story-solution-architect.json +0 -104
- package/cli/agents/validator-story-solution-architect.md +0 -152
- package/cli/agents/validator-story-test-architect.json +0 -104
- package/cli/agents/validator-story-test-architect.md +0 -152
- package/cli/agents/validator-story-ui.json +0 -104
- package/cli/agents/validator-story-ui.md +0 -152
- package/cli/agents/validator-story-ux.json +0 -104
- package/cli/agents/validator-story-ux.md +0 -152
- package/kanban/client/dist/assets/index-CiD8PS2e.js +0 -306
- package/kanban/client/dist/assets/index-nLh0m82Q.css +0 -1
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
{
|
|
2
|
+
"perspective": "ui",
|
|
3
|
+
"scope": "story",
|
|
4
|
+
"checks": [
|
|
5
|
+
{
|
|
6
|
+
"id": "ui-story-01",
|
|
7
|
+
"tier": 1,
|
|
8
|
+
"perspective": "ui",
|
|
9
|
+
"severity": "critical",
|
|
10
|
+
"category": "acceptance-criteria",
|
|
11
|
+
"universal": false,
|
|
12
|
+
"applicabilityQuestion": "Does this story involve UI components, visual design, or design system elements? (Does it include component specs, styling, or visual layout?)",
|
|
13
|
+
"question": "Is each acceptance criterion testable and measurable?",
|
|
14
|
+
"failDescription": "One or more acceptance criteria are not testable or measurable",
|
|
15
|
+
"failSuggestion": "Make each AC testable: specify exact visual outcomes, component states, or measurable UI properties"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"id": "ui-story-02",
|
|
19
|
+
"tier": 1,
|
|
20
|
+
"perspective": "ui",
|
|
21
|
+
"severity": "major",
|
|
22
|
+
"category": "acceptance-criteria",
|
|
23
|
+
"universal": false,
|
|
24
|
+
"applicabilityQuestion": "Does this story involve UI components, visual design, or design system elements?",
|
|
25
|
+
"question": "Do criteria cover happy path, edge cases, and error scenarios?",
|
|
26
|
+
"failDescription": "Criteria do not cover UI edge cases or error states",
|
|
27
|
+
"failSuggestion": "Add UI edge cases: overflow text, empty states, loading skeletons, error displays, responsive breakpoints"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"id": "ui-story-03",
|
|
31
|
+
"tier": 1,
|
|
32
|
+
"perspective": "ui",
|
|
33
|
+
"severity": "major",
|
|
34
|
+
"category": "acceptance-criteria",
|
|
35
|
+
"universal": false,
|
|
36
|
+
"applicabilityQuestion": "Does this story involve UI components, visual design, or design system elements?",
|
|
37
|
+
"question": "Are UI requirements explicitly stated?",
|
|
38
|
+
"failDescription": "UI requirements are not explicitly stated",
|
|
39
|
+
"failSuggestion": "State UI requirements: component variants, colors, spacing, typography, responsive behavior"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"id": "ui-story-04",
|
|
43
|
+
"tier": 1,
|
|
44
|
+
"perspective": "ui",
|
|
45
|
+
"severity": "major",
|
|
46
|
+
"category": "implementation-clarity",
|
|
47
|
+
"universal": false,
|
|
48
|
+
"applicabilityQuestion": "Does this story involve UI components, visual design, or design system elements?",
|
|
49
|
+
"question": "Does the story provide enough UI detail for implementation?",
|
|
50
|
+
"failDescription": "Insufficient UI detail for implementation",
|
|
51
|
+
"failSuggestion": "Add UI details: component dimensions, color values, spacing units, font sizes, animation timing"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"id": "ui-story-05",
|
|
55
|
+
"tier": 1,
|
|
56
|
+
"perspective": "ui",
|
|
57
|
+
"severity": "minor",
|
|
58
|
+
"category": "implementation-clarity",
|
|
59
|
+
"universal": false,
|
|
60
|
+
"applicabilityQuestion": "Does this story involve UI components, visual design, or design system elements?",
|
|
61
|
+
"question": "Are UI patterns and approaches specified?",
|
|
62
|
+
"failDescription": "UI patterns and approaches are not specified",
|
|
63
|
+
"failSuggestion": "Specify UI patterns: component composition, design token usage, responsive strategy, theme support"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"id": "ui-story-06",
|
|
67
|
+
"tier": 1,
|
|
68
|
+
"perspective": "ui",
|
|
69
|
+
"severity": "major",
|
|
70
|
+
"category": "testability",
|
|
71
|
+
"universal": false,
|
|
72
|
+
"applicabilityQuestion": "Does this story involve UI components, visual design, or design system elements?",
|
|
73
|
+
"question": "Does the story have a test-boundary AC naming specific scenarios a developer must test?",
|
|
74
|
+
"failDescription": "Story lacks a test-boundary AC",
|
|
75
|
+
"failSuggestion": "Add test-boundary AC: 'Tests must cover: (1) default state, (2) hover/focus, (3) error state, (4) responsive, (5) accessibility'"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"id": "ui-story-07",
|
|
79
|
+
"tier": 1,
|
|
80
|
+
"perspective": "ui",
|
|
81
|
+
"severity": "minor",
|
|
82
|
+
"category": "scope-dependencies",
|
|
83
|
+
"universal": false,
|
|
84
|
+
"applicabilityQuestion": "Does this story involve UI components, visual design, or design system elements?",
|
|
85
|
+
"question": "Are dependencies on other stories explicit?",
|
|
86
|
+
"failDescription": "Dependencies on other stories are not explicit",
|
|
87
|
+
"failSuggestion": "Make dependencies explicit: reference design system stories, component library dependencies"
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"id": "ui-story-08",
|
|
91
|
+
"tier": 1,
|
|
92
|
+
"perspective": "ui",
|
|
93
|
+
"severity": "minor",
|
|
94
|
+
"category": "best-practices",
|
|
95
|
+
"universal": false,
|
|
96
|
+
"applicabilityQuestion": "Does this story involve UI components, visual design, or design system elements?",
|
|
97
|
+
"question": "Does the story follow UI best practices?",
|
|
98
|
+
"failDescription": "UI best practices are not followed",
|
|
99
|
+
"failSuggestion": "Follow UI best practices: consistent design tokens, accessibility contrast ratios, responsive design, visual hierarchy"
|
|
100
|
+
}
|
|
101
|
+
]
|
|
102
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
{
|
|
2
|
+
"perspective": "ux",
|
|
3
|
+
"scope": "story",
|
|
4
|
+
"checks": [
|
|
5
|
+
{
|
|
6
|
+
"id": "ux-story-01",
|
|
7
|
+
"tier": 1,
|
|
8
|
+
"perspective": "ux",
|
|
9
|
+
"severity": "critical",
|
|
10
|
+
"category": "acceptance-criteria",
|
|
11
|
+
"universal": false,
|
|
12
|
+
"applicabilityQuestion": "Does this story involve user experience, user flows, or interaction design? (Does it define user journeys, navigation, or usability requirements?)",
|
|
13
|
+
"question": "Is each acceptance criterion testable and measurable?",
|
|
14
|
+
"failDescription": "One or more acceptance criteria are not testable or measurable",
|
|
15
|
+
"failSuggestion": "Make each AC testable: specify user actions, expected outcomes, and measurable UX metrics"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"id": "ux-story-02",
|
|
19
|
+
"tier": 1,
|
|
20
|
+
"perspective": "ux",
|
|
21
|
+
"severity": "major",
|
|
22
|
+
"category": "acceptance-criteria",
|
|
23
|
+
"universal": false,
|
|
24
|
+
"applicabilityQuestion": "Does this story involve user experience, user flows, or interaction design?",
|
|
25
|
+
"question": "Do criteria cover happy path, edge cases, and error scenarios?",
|
|
26
|
+
"failDescription": "Criteria do not cover UX edge cases or error scenarios",
|
|
27
|
+
"failSuggestion": "Add UX edge cases: empty states, error recovery flows, timeout handling, progressive disclosure"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"id": "ux-story-03",
|
|
31
|
+
"tier": 1,
|
|
32
|
+
"perspective": "ux",
|
|
33
|
+
"severity": "major",
|
|
34
|
+
"category": "acceptance-criteria",
|
|
35
|
+
"universal": false,
|
|
36
|
+
"applicabilityQuestion": "Does this story involve user experience, user flows, or interaction design?",
|
|
37
|
+
"question": "Are UX requirements explicitly stated?",
|
|
38
|
+
"failDescription": "UX requirements are not explicitly stated",
|
|
39
|
+
"failSuggestion": "State UX requirements: user flow steps, interaction patterns, feedback mechanisms, accessibility needs"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"id": "ux-story-04",
|
|
43
|
+
"tier": 1,
|
|
44
|
+
"perspective": "ux",
|
|
45
|
+
"severity": "major",
|
|
46
|
+
"category": "implementation-clarity",
|
|
47
|
+
"universal": false,
|
|
48
|
+
"applicabilityQuestion": "Does this story involve user experience, user flows, or interaction design?",
|
|
49
|
+
"question": "Does the story provide enough UX detail for implementation?",
|
|
50
|
+
"failDescription": "Insufficient UX detail for implementation",
|
|
51
|
+
"failSuggestion": "Add UX details: user flow diagrams, interaction states, feedback timing, animation expectations"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"id": "ux-story-05",
|
|
55
|
+
"tier": 1,
|
|
56
|
+
"perspective": "ux",
|
|
57
|
+
"severity": "minor",
|
|
58
|
+
"category": "implementation-clarity",
|
|
59
|
+
"universal": false,
|
|
60
|
+
"applicabilityQuestion": "Does this story involve user experience, user flows, or interaction design?",
|
|
61
|
+
"question": "Are UX patterns and approaches specified?",
|
|
62
|
+
"failDescription": "UX patterns and approaches are not specified",
|
|
63
|
+
"failSuggestion": "Specify UX patterns: progressive disclosure, inline validation, optimistic feedback, error recovery"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"id": "ux-story-06",
|
|
67
|
+
"tier": 1,
|
|
68
|
+
"perspective": "ux",
|
|
69
|
+
"severity": "major",
|
|
70
|
+
"category": "testability",
|
|
71
|
+
"universal": false,
|
|
72
|
+
"applicabilityQuestion": "Does this story involve user experience, user flows, or interaction design?",
|
|
73
|
+
"question": "Does the story have a test-boundary AC naming specific scenarios a developer must test?",
|
|
74
|
+
"failDescription": "Story lacks a test-boundary AC",
|
|
75
|
+
"failSuggestion": "Add test-boundary AC: 'Tests must cover: (1) happy flow, (2) error recovery, (3) empty state, (4) loading state, (5) accessibility'"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"id": "ux-story-07",
|
|
79
|
+
"tier": 1,
|
|
80
|
+
"perspective": "ux",
|
|
81
|
+
"severity": "minor",
|
|
82
|
+
"category": "scope-dependencies",
|
|
83
|
+
"universal": false,
|
|
84
|
+
"applicabilityQuestion": "Does this story involve user experience, user flows, or interaction design?",
|
|
85
|
+
"question": "Are dependencies on other stories explicit?",
|
|
86
|
+
"failDescription": "Dependencies on other stories are not explicit",
|
|
87
|
+
"failSuggestion": "Make dependencies explicit: reference related user flow stories and shared UX patterns"
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"id": "ux-story-08",
|
|
91
|
+
"tier": 1,
|
|
92
|
+
"perspective": "ux",
|
|
93
|
+
"severity": "minor",
|
|
94
|
+
"category": "best-practices",
|
|
95
|
+
"universal": false,
|
|
96
|
+
"applicabilityQuestion": "Does this story involve user experience, user flows, or interaction design?",
|
|
97
|
+
"question": "Does the story follow UX best practices?",
|
|
98
|
+
"failDescription": "UX best practices are not followed",
|
|
99
|
+
"failSuggestion": "Follow UX best practices: minimize cognitive load, provide clear feedback, support undo, maintain consistency"
|
|
100
|
+
}
|
|
101
|
+
]
|
|
102
|
+
}
|
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* coding-order.js — Computes implementation ordering from epic/story dependency graphs.
|
|
3
|
+
*
|
|
4
|
+
* Pure functions: no side effects, no file I/O. All I/O is done by the caller
|
|
5
|
+
* (SprintPlanningProcessor.generateCodingOrder).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Build adjacency maps from hierarchy dependencies.
|
|
10
|
+
* @param {{ epics: Array }} hierarchy
|
|
11
|
+
* @returns {{ epicGraph: Map<string,Set<string>>, storyGraphs: Map<string,Map<string,Set<string>>>, allNodes: Map<string,{id,name,type,parentId?}> }}
|
|
12
|
+
*/
|
|
13
|
+
export function buildDependencyGraph(hierarchy) {
|
|
14
|
+
const epicGraph = new Map(); // epicId → Set<epicId it depends on>
|
|
15
|
+
const storyGraphs = new Map(); // epicId → Map<storyId, Set<storyId>>
|
|
16
|
+
const allNodes = new Map(); // id → { id, name, type, parentId? }
|
|
17
|
+
|
|
18
|
+
for (const epic of hierarchy.epics || []) {
|
|
19
|
+
const epicId = epic.id;
|
|
20
|
+
allNodes.set(epicId, { id: epicId, name: epic.name, type: 'epic' });
|
|
21
|
+
|
|
22
|
+
// Epic-level deps: only keep references that look like epic IDs (context-XXXX)
|
|
23
|
+
const epicDeps = new Set();
|
|
24
|
+
for (const dep of epic.dependencies || []) {
|
|
25
|
+
if (/^context-\d{4}$/.test(dep) && dep !== epicId) {
|
|
26
|
+
epicDeps.add(dep);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
epicGraph.set(epicId, epicDeps);
|
|
30
|
+
|
|
31
|
+
// Story-level: intra-epic deps only
|
|
32
|
+
const storyMap = new Map();
|
|
33
|
+
const epicStoryIds = new Set((epic.stories || []).map(s => s.id));
|
|
34
|
+
|
|
35
|
+
for (const story of epic.stories || []) {
|
|
36
|
+
allNodes.set(story.id, { id: story.id, name: story.name, type: 'story', parentId: epicId });
|
|
37
|
+
const storyDeps = new Set();
|
|
38
|
+
for (const dep of story.dependencies || []) {
|
|
39
|
+
if (epicStoryIds.has(dep) && dep !== story.id) {
|
|
40
|
+
storyDeps.add(dep); // intra-epic story dependency
|
|
41
|
+
}
|
|
42
|
+
// Cross-epic story deps → promote to epic dependency
|
|
43
|
+
if (/^context-\d{4}$/.test(dep) && dep !== epicId) {
|
|
44
|
+
epicDeps.add(dep);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
storyMap.set(story.id, storyDeps);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
storyGraphs.set(epicId, storyMap);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return { epicGraph, storyGraphs, allNodes };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Kahn's algorithm — topological sort with cycle detection.
|
|
58
|
+
* @param {Map<string,Set<string>>} graph — nodeId → Set<dependency nodeIds>
|
|
59
|
+
* @returns {{ sorted: string[], hasCycle: boolean, cycleNodes: string[] }}
|
|
60
|
+
*/
|
|
61
|
+
export function topologicalSort(graph) {
|
|
62
|
+
const inDegree = new Map();
|
|
63
|
+
const adjList = new Map(); // forward edges: dep → [dependents]
|
|
64
|
+
|
|
65
|
+
for (const [node] of graph) {
|
|
66
|
+
if (!inDegree.has(node)) inDegree.set(node, 0);
|
|
67
|
+
if (!adjList.has(node)) adjList.set(node, []);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
for (const [node, deps] of graph) {
|
|
71
|
+
for (const dep of deps) {
|
|
72
|
+
if (!graph.has(dep)) continue; // skip refs to non-existent nodes
|
|
73
|
+
if (!adjList.has(dep)) adjList.set(dep, []);
|
|
74
|
+
adjList.get(dep).push(node);
|
|
75
|
+
inDegree.set(node, (inDegree.get(node) || 0) + 1);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const queue = [];
|
|
80
|
+
for (const [node, deg] of inDegree) {
|
|
81
|
+
if (deg === 0) queue.push(node);
|
|
82
|
+
}
|
|
83
|
+
queue.sort(); // deterministic ordering for ties
|
|
84
|
+
|
|
85
|
+
const sorted = [];
|
|
86
|
+
while (queue.length > 0) {
|
|
87
|
+
const node = queue.shift();
|
|
88
|
+
sorted.push(node);
|
|
89
|
+
for (const dependent of adjList.get(node) || []) {
|
|
90
|
+
const newDeg = inDegree.get(dependent) - 1;
|
|
91
|
+
inDegree.set(dependent, newDeg);
|
|
92
|
+
if (newDeg === 0) {
|
|
93
|
+
queue.push(dependent);
|
|
94
|
+
queue.sort();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const hasCycle = sorted.length < graph.size;
|
|
100
|
+
const cycleNodes = hasCycle
|
|
101
|
+
? [...graph.keys()].filter(n => !sorted.includes(n))
|
|
102
|
+
: [];
|
|
103
|
+
|
|
104
|
+
// If cycle, append remaining nodes in ID order to still produce output
|
|
105
|
+
if (hasCycle) {
|
|
106
|
+
for (const node of cycleNodes.sort()) {
|
|
107
|
+
sorted.push(node);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return { sorted, hasCycle, cycleNodes };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Group epics into implementation phases by longest-path distance from root nodes.
|
|
116
|
+
* Phase 0 = no dependencies, Phase N = longest dep chain is N.
|
|
117
|
+
* @param {Map<string,Set<string>>} epicGraph
|
|
118
|
+
* @returns {Array<{ phase: number, epicIds: string[] }>}
|
|
119
|
+
*/
|
|
120
|
+
export function computePhases(epicGraph) {
|
|
121
|
+
const depth = new Map();
|
|
122
|
+
|
|
123
|
+
function getDepth(node, visited = new Set()) {
|
|
124
|
+
if (depth.has(node)) return depth.get(node);
|
|
125
|
+
if (visited.has(node)) return 0; // cycle guard
|
|
126
|
+
visited.add(node);
|
|
127
|
+
|
|
128
|
+
const deps = epicGraph.get(node) || new Set();
|
|
129
|
+
let maxDepth = 0;
|
|
130
|
+
for (const dep of deps) {
|
|
131
|
+
if (epicGraph.has(dep)) {
|
|
132
|
+
maxDepth = Math.max(maxDepth, getDepth(dep, visited) + 1);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
depth.set(node, maxDepth);
|
|
136
|
+
return maxDepth;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
for (const [node] of epicGraph) getDepth(node);
|
|
140
|
+
|
|
141
|
+
// Group by phase
|
|
142
|
+
const phaseMap = new Map();
|
|
143
|
+
for (const [node, d] of depth) {
|
|
144
|
+
if (!phaseMap.has(d)) phaseMap.set(d, []);
|
|
145
|
+
phaseMap.get(d).push(node);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return [...phaseMap.entries()]
|
|
149
|
+
.sort((a, b) => a[0] - b[0])
|
|
150
|
+
.map(([phase, epicIds]) => ({ phase, epicIds: epicIds.sort() }));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Find the longest path through the epic dependency graph (critical path).
|
|
155
|
+
* @param {Map<string,Set<string>>} epicGraph
|
|
156
|
+
* @returns {{ path: string[], length: number }}
|
|
157
|
+
*/
|
|
158
|
+
export function computeCriticalPath(epicGraph) {
|
|
159
|
+
const memo = new Map();
|
|
160
|
+
|
|
161
|
+
function longestFrom(node, visited = new Set()) {
|
|
162
|
+
if (memo.has(node)) return memo.get(node);
|
|
163
|
+
if (visited.has(node)) return [node]; // cycle guard
|
|
164
|
+
visited.add(node);
|
|
165
|
+
|
|
166
|
+
// Find all nodes that depend on this one
|
|
167
|
+
const dependents = [];
|
|
168
|
+
for (const [n, deps] of epicGraph) {
|
|
169
|
+
if (deps.has(node)) dependents.push(n);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
let best = [node];
|
|
173
|
+
for (const dep of dependents) {
|
|
174
|
+
const sub = longestFrom(dep, new Set(visited));
|
|
175
|
+
if (sub.length + 1 > best.length) {
|
|
176
|
+
best = [node, ...sub];
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
memo.set(node, best);
|
|
180
|
+
return best;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Find roots (nodes with no dependencies)
|
|
184
|
+
const roots = [];
|
|
185
|
+
for (const [node, deps] of epicGraph) {
|
|
186
|
+
if (deps.size === 0) roots.push(node);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
let criticalPath = [];
|
|
190
|
+
for (const root of roots) {
|
|
191
|
+
const path = longestFrom(root);
|
|
192
|
+
if (path.length > criticalPath.length) criticalPath = path;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return { path: criticalPath, length: criticalPath.length };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Topologically sort stories within a single epic.
|
|
200
|
+
* @param {Map<string,Set<string>>} storyGraph — storyId → Set<intra-epic dep storyIds>
|
|
201
|
+
* @returns {string[]}
|
|
202
|
+
*/
|
|
203
|
+
export function orderStoriesWithinEpic(storyGraph) {
|
|
204
|
+
return topologicalSort(storyGraph).sorted;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Render the coding order as a Markdown document.
|
|
209
|
+
*/
|
|
210
|
+
export function generateCodingOrderMd(phases, storyOrders, allNodes, criticalPath) {
|
|
211
|
+
const lines = [
|
|
212
|
+
'# Coding Order',
|
|
213
|
+
'',
|
|
214
|
+
`Generated: ${new Date().toISOString()}`,
|
|
215
|
+
'',
|
|
216
|
+
];
|
|
217
|
+
|
|
218
|
+
// Mermaid dependency graph
|
|
219
|
+
lines.push('## Dependency Graph', '', '```mermaid', 'graph LR');
|
|
220
|
+
for (const [id, node] of allNodes) {
|
|
221
|
+
if (node.type === 'epic') {
|
|
222
|
+
const label = node.name.replace(/"/g, "'");
|
|
223
|
+
lines.push(` ${id}["${label}"]`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
// Edges: for each epic, draw edges FROM its dependencies TO it
|
|
227
|
+
for (const phase of phases) {
|
|
228
|
+
for (const epicId of phase.epicIds) {
|
|
229
|
+
// Find this epic's node in allNodes and look up its deps
|
|
230
|
+
// We need the original graph — reconstruct from phases
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
// Use phases to reconstruct edges from allNodes
|
|
234
|
+
// Actually we need epicGraph — let's take it as parameter
|
|
235
|
+
lines.push('```', '');
|
|
236
|
+
|
|
237
|
+
// Critical path
|
|
238
|
+
lines.push('## Critical Path', '');
|
|
239
|
+
if (criticalPath.path.length > 0) {
|
|
240
|
+
const pathStr = criticalPath.path
|
|
241
|
+
.map(id => { const n = allNodes.get(id); return n ? `${id} (${n.name})` : id; })
|
|
242
|
+
.join(' → ');
|
|
243
|
+
lines.push(pathStr);
|
|
244
|
+
lines.push(``, `Length: ${criticalPath.length} epics`, '');
|
|
245
|
+
} else {
|
|
246
|
+
lines.push('No dependencies — all epics are independent.', '');
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Implementation phases
|
|
250
|
+
lines.push('## Implementation Phases', '');
|
|
251
|
+
for (const phase of phases) {
|
|
252
|
+
const parallel = phase.epicIds.length > 1 ? ' (parallel)' : '';
|
|
253
|
+
const depNote = phase.phase === 0 ? ' — no dependencies' : ` — depends on Phase ${phase.phase}`;
|
|
254
|
+
lines.push(`### Phase ${phase.phase + 1}${parallel}${depNote}`, '');
|
|
255
|
+
|
|
256
|
+
for (const epicId of phase.epicIds) {
|
|
257
|
+
const epic = allNodes.get(epicId);
|
|
258
|
+
const epicName = epic ? epic.name : epicId;
|
|
259
|
+
lines.push(`#### ${epicId}: ${epicName}`, '');
|
|
260
|
+
|
|
261
|
+
const stories = storyOrders.get(epicId) || [];
|
|
262
|
+
if (stories.length > 0) {
|
|
263
|
+
lines.push('| # | Story | ID |', '|---|-------|-----|');
|
|
264
|
+
stories.forEach((storyId, idx) => {
|
|
265
|
+
const story = allNodes.get(storyId);
|
|
266
|
+
const storyName = story ? story.name : storyId;
|
|
267
|
+
lines.push(`| ${idx + 1} | ${storyName} | ${storyId} |`);
|
|
268
|
+
});
|
|
269
|
+
lines.push('');
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return lines.join('\n');
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Render the coding order as structured JSON for the kanban API.
|
|
279
|
+
*/
|
|
280
|
+
export function generateCodingOrderJson(phases, storyOrders, allNodes, criticalPath) {
|
|
281
|
+
return {
|
|
282
|
+
generatedAt: new Date().toISOString(),
|
|
283
|
+
criticalPath: {
|
|
284
|
+
epicIds: criticalPath.path,
|
|
285
|
+
length: criticalPath.length,
|
|
286
|
+
epics: criticalPath.path.map(id => {
|
|
287
|
+
const n = allNodes.get(id);
|
|
288
|
+
return { id, name: n?.name || id };
|
|
289
|
+
}),
|
|
290
|
+
},
|
|
291
|
+
phases: phases.map(phase => ({
|
|
292
|
+
phase: phase.phase + 1,
|
|
293
|
+
parallel: phase.epicIds.length > 1,
|
|
294
|
+
epics: phase.epicIds.map(epicId => {
|
|
295
|
+
const epic = allNodes.get(epicId);
|
|
296
|
+
const stories = (storyOrders.get(epicId) || []).map((storyId, idx) => {
|
|
297
|
+
const story = allNodes.get(storyId);
|
|
298
|
+
return { order: idx + 1, id: storyId, name: story?.name || storyId };
|
|
299
|
+
});
|
|
300
|
+
return { id: epicId, name: epic?.name || epicId, stories };
|
|
301
|
+
}),
|
|
302
|
+
})),
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Main entry point — compute everything from a hierarchy.
|
|
308
|
+
* @param {{ epics: Array }} hierarchy
|
|
309
|
+
* @returns {{ md: string, json: object, phases: Array, criticalPath: object }}
|
|
310
|
+
*/
|
|
311
|
+
export function computeCodingOrder(hierarchy) {
|
|
312
|
+
const { epicGraph, storyGraphs, allNodes } = buildDependencyGraph(hierarchy);
|
|
313
|
+
const phases = computePhases(epicGraph);
|
|
314
|
+
const criticalPath = computeCriticalPath(epicGraph);
|
|
315
|
+
|
|
316
|
+
// Order stories within each epic
|
|
317
|
+
const storyOrders = new Map();
|
|
318
|
+
for (const [epicId, storyGraph] of storyGraphs) {
|
|
319
|
+
storyOrders.set(epicId, orderStoriesWithinEpic(storyGraph));
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Render mermaid edges using epicGraph
|
|
323
|
+
const md = generateCodingOrderMdWithGraph(phases, storyOrders, allNodes, criticalPath, epicGraph);
|
|
324
|
+
const json = generateCodingOrderJson(phases, storyOrders, allNodes, criticalPath);
|
|
325
|
+
|
|
326
|
+
return { md, json, phases, criticalPath };
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Full Markdown rendering with mermaid graph edges.
|
|
331
|
+
*/
|
|
332
|
+
function generateCodingOrderMdWithGraph(phases, storyOrders, allNodes, criticalPath, epicGraph) {
|
|
333
|
+
const lines = [
|
|
334
|
+
'# Coding Order',
|
|
335
|
+
'',
|
|
336
|
+
`Generated: ${new Date().toISOString()}`,
|
|
337
|
+
'',
|
|
338
|
+
'## Dependency Graph',
|
|
339
|
+
'',
|
|
340
|
+
'```mermaid',
|
|
341
|
+
'graph LR',
|
|
342
|
+
];
|
|
343
|
+
|
|
344
|
+
// Nodes
|
|
345
|
+
for (const [id, node] of allNodes) {
|
|
346
|
+
if (node.type === 'epic') {
|
|
347
|
+
const label = node.name.replace(/"/g, "'");
|
|
348
|
+
lines.push(` ${id}["${label}"]`);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Edges
|
|
353
|
+
for (const [epicId, deps] of epicGraph) {
|
|
354
|
+
for (const dep of deps) {
|
|
355
|
+
lines.push(` ${dep} --> ${epicId}`);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Highlight critical path
|
|
360
|
+
if (criticalPath.path.length > 1) {
|
|
361
|
+
lines.push(` style ${criticalPath.path.join(' stroke:#f44,stroke-width:3px; style ')} stroke:#f44,stroke-width:3px`);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
lines.push('```', '');
|
|
365
|
+
|
|
366
|
+
// Critical path
|
|
367
|
+
lines.push('## Critical Path', '');
|
|
368
|
+
if (criticalPath.path.length > 0) {
|
|
369
|
+
const pathStr = criticalPath.path
|
|
370
|
+
.map(id => { const n = allNodes.get(id); return n ? `**${n.name}**` : id; })
|
|
371
|
+
.join(' → ');
|
|
372
|
+
lines.push(pathStr, '', `Length: ${criticalPath.length} epics`, '');
|
|
373
|
+
} else {
|
|
374
|
+
lines.push('No dependencies — all epics are independent.', '');
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Phases
|
|
378
|
+
lines.push('## Implementation Phases', '');
|
|
379
|
+
for (const phase of phases) {
|
|
380
|
+
const parallel = phase.epicIds.length > 1 ? ' (parallel)' : '';
|
|
381
|
+
const depNote = phase.phase === 0 ? '' : ` — requires Phase ${phase.phase} complete`;
|
|
382
|
+
lines.push(`### Phase ${phase.phase + 1}${parallel}${depNote}`, '');
|
|
383
|
+
|
|
384
|
+
for (const epicId of phase.epicIds) {
|
|
385
|
+
const epic = allNodes.get(epicId);
|
|
386
|
+
lines.push(`#### ${epicId}: ${epic?.name || epicId}`, '');
|
|
387
|
+
|
|
388
|
+
const stories = storyOrders.get(epicId) || [];
|
|
389
|
+
if (stories.length > 0) {
|
|
390
|
+
lines.push('| # | Story | ID |', '|---|-------|-----|');
|
|
391
|
+
stories.forEach((storyId, idx) => {
|
|
392
|
+
const story = allNodes.get(storyId);
|
|
393
|
+
lines.push(`| ${idx + 1} | ${story?.name || storyId} | \`${storyId}\` |`);
|
|
394
|
+
});
|
|
395
|
+
lines.push('');
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return lines.join('\n');
|
|
401
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dependency-checker.js — Pure-function module for checking work item dependency readiness.
|
|
3
|
+
*
|
|
4
|
+
* No side effects, no file I/O. All data passed in as arguments.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Extract the parent ID from a work item ID by stripping the last segment.
|
|
9
|
+
* context-0001-0001-0001 → context-0001-0001
|
|
10
|
+
* context-0001-0001 → context-0001
|
|
11
|
+
* context-0001 → null
|
|
12
|
+
* @param {string} id
|
|
13
|
+
* @returns {string|null}
|
|
14
|
+
*/
|
|
15
|
+
export function getParentId(id) {
|
|
16
|
+
const match = id.match(/^(context-\d{4}(?:-\d{4})*)-\d{4}$/);
|
|
17
|
+
return match ? match[1] : null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Check if all dependencies of a work item are completed.
|
|
22
|
+
* Walks up the parent chain: task checks parent story deps,
|
|
23
|
+
* story checks parent epic deps, etc.
|
|
24
|
+
*
|
|
25
|
+
* @param {string} itemId - The work item to check
|
|
26
|
+
* @param {Map<string,object>|object} items - Map of id → work item, or plain object
|
|
27
|
+
* @returns {{ ready: boolean, blockers: Array<{id: string, name: string, type: string, status: string}> }}
|
|
28
|
+
*/
|
|
29
|
+
export function checkDependenciesReady(itemId, items) {
|
|
30
|
+
const get = (id) => items instanceof Map ? items.get(id) : items[id];
|
|
31
|
+
const blockers = [];
|
|
32
|
+
const seen = new Set();
|
|
33
|
+
|
|
34
|
+
function collectBlockers(id) {
|
|
35
|
+
if (!id || seen.has(id)) return;
|
|
36
|
+
seen.add(id);
|
|
37
|
+
|
|
38
|
+
const item = get(id);
|
|
39
|
+
if (!item) return;
|
|
40
|
+
|
|
41
|
+
// Check direct dependencies
|
|
42
|
+
for (const depId of item.dependencies || []) {
|
|
43
|
+
if (seen.has(depId)) continue;
|
|
44
|
+
const dep = get(depId);
|
|
45
|
+
if (!dep) continue;
|
|
46
|
+
if (dep.status !== 'completed') {
|
|
47
|
+
blockers.push({
|
|
48
|
+
id: depId,
|
|
49
|
+
name: dep.name || depId,
|
|
50
|
+
type: dep.type || 'unknown',
|
|
51
|
+
status: dep.status || 'planned',
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Walk up to parent and check its dependencies too
|
|
57
|
+
const parentId = getParentId(id);
|
|
58
|
+
if (parentId) {
|
|
59
|
+
collectBlockers(parentId);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
collectBlockers(itemId);
|
|
64
|
+
|
|
65
|
+
// Deduplicate blockers by id
|
|
66
|
+
const unique = [...new Map(blockers.map(b => [b.id, b])).values()];
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
ready: unique.length === 0,
|
|
70
|
+
blockers: unique,
|
|
71
|
+
};
|
|
72
|
+
}
|